Commit e9aecf8a authored by wanghao's avatar wanghao

1 扫码绑定托盘,上料,机械臂整体测试

parent 9eb47dd7
......@@ -108,6 +108,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
// 对于 websocket 匿名访问
.antMatchers("/ws-robot-arm").permitAll()
// 对于 websocket 匿名访问
.antMatchers("/ws-aging-cabinet").permitAll()
.antMatchers("/profile/**").anonymous()
.antMatchers("/common/download**").anonymous()
......
package com.zehong.system.domain;
import java.util.Date;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
......@@ -78,6 +80,11 @@ public class TEquipmentInfo extends BaseEntity
@Excel(name = "状态:0空闲,1运行,2故障")
private String fStatus;
/**
* 寄存器值
*/
private Map<Integer,Object> registerValues;
/**
* 故障原因
*/
......@@ -263,6 +270,14 @@ public class TEquipmentInfo extends BaseEntity
this.errorReason = errorReason;
}
public Map<Integer, Object> getRegisterValues() {
return registerValues;
}
public void setRegisterValues(Map<Integer, Object> registerValues) {
this.registerValues = registerValues;
}
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
......
......@@ -39,6 +39,9 @@ public class TStoreyInfo extends BaseEntity
@Excel(name = "层编号")
private String fStoreyCode;
/** 托盘编号 */
private String fTrayCode;
/** IP地址 */
@Excel(name = "IP地址")
private String fIp;
......@@ -79,7 +82,15 @@ public class TStoreyInfo extends BaseEntity
@Excel(name = "报警时间", width = 30, dateFormat = "yyyy-MM-dd")
private Date fAlarmTime;
public void setfStoreyId(Long fStoreyId)
public String getfTrayCode() {
return fTrayCode;
}
public void setfTrayCode(String fTrayCode) {
this.fTrayCode = fTrayCode;
}
public void setfStoreyId(Long fStoreyId)
{
this.fStoreyId = fStoreyId;
}
......
......@@ -52,6 +52,8 @@ public interface TEquipmentInfoMapper
*/
public int updateTEquipmentInfo(TEquipmentInfo tEquipmentInfo);
public int batchUpdate(@Param("equipmentInfos") List<TEquipmentInfo> equipmentInfos);
/**
* 删除生产设备信息
*
......
......@@ -58,6 +58,8 @@ public interface ITEquipmentInfoService
*/
public int updateTEquipmentInfo(TEquipmentInfo tEquipmentInfo);
public int batchUpdate(List<TEquipmentInfo> equipmentInfos);
/**
* 批量删除生产设备信息
*
......
......@@ -79,6 +79,8 @@ public class RobotArmCommandServiceImpl implements IRobotArmCommandService
if (!loadingCommands.isEmpty()) {
boolean[] roboticArmEntryConveyorData = Modbus4jUtils.getRoboticArmEntryConveyorData();
log.info("机械臂入口 conveyor 0状态: " + roboticArmEntryConveyorData[0]);
log.info("机械臂入口 conveyor 1状态: " + roboticArmEntryConveyorData[1]);
if(roboticArmEntryConveyorData[1]) {
sendCommand(loadingCommands.get(0), "LOAD");
return;
......@@ -221,6 +223,10 @@ public class RobotArmCommandServiceImpl implements IRobotArmCommandService
if(tStoreyInfo != null) {
robotArmCommand.setStatus("1");
robotArmCommand.setStoreyCode(tStoreyInfo.getfStoreyCode());
tTrayInfo.setfStoreyCode(tStoreyInfo.getfStoreyCode());
tTrayInfo.setfBindingTime(new Date());
tTrayInfoMapper.updateTTrayInfo(tTrayInfo);
} else {
robotArmCommand.setStoreyCode("待分配位置");
}
......
......@@ -4,7 +4,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import com.sun.org.apache.xpath.internal.operations.Mod;
import com.zehong.system.domain.modbus.ModbusDeviceData;
import org.springframework.stereotype.Service;
import com.zehong.system.mapper.TEquipmentInfoMapper;
......@@ -91,6 +90,17 @@ public class TEquipmentInfoServiceImpl implements ITEquipmentInfoService
return tEquipmentInfoMapper.updateTEquipmentInfo(tEquipmentInfo);
}
/**
* 批量修改生产设备信息
*
* @param equipmentInfos 生产设备信息集合
* @return 结果
*/
@Override
public int batchUpdate(List<TEquipmentInfo> equipmentInfos) {
return 0;
}
/**
* 批量删除生产设备信息
*
......
package com.zehong.system.service.websocket;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import javax.annotation.Resource;
/**
* @author lenovo
* @date 2025/8/14
* @description 老化柜WebSocket配置类
*/
@Configuration
@EnableWebSocket
public class AgingCabinetWebSocketConfig implements WebSocketConfigurer {
@Resource
private AgingCabinetWebSocketHandler agingCabinetWebSocketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 注册处理器,设置访问路径为/ws-aging-cabinet,允许所有跨域
registry.addHandler(agingCabinetWebSocketHandler, "/ws-aging-cabinet")
.setAllowedOrigins("*");
}
}
\ No newline at end of file
package com.zehong.system.service.websocket;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zehong.system.domain.TEquipmentInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* @author lenovo
* @date 2025/8/14
* @description 老化柜WebSocket处理器
*/
@Component
public class AgingCabinetWebSocketHandler extends TextWebSocketHandler {
// 线程安全的会话列表,存储所有连接的客户端
private static final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
private static final Logger log = LoggerFactory.getLogger(AgingCabinetWebSocketHandler.class);
/**
* 连接建立后添加会话
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
log.info("新的老化柜WebSocket连接建立,当前连接数: {}", sessions.size());
}
/**
* 连接关闭后移除会话
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
sessions.remove(session);
log.info("老化柜WebSocket连接关闭,当前连接数: {}", sessions.size());
}
/**
* 广播老化柜数据给所有客户端
* @param equipmentList 处理后的老化柜设备列表
*/
public void broadcastAgingCabinetData(List<TEquipmentInfo> equipmentList) {
try {
String message = createAgingCabinetMessage(equipmentList);
for (WebSocketSession session : sessions) {
if (session.isOpen()) {
session.sendMessage(new TextMessage(message));
}
}
} catch (IOException e) {
log.error("广播老化柜数据失败", e);
}
}
/**
* 构建老化柜数据消息
* 转换为前端需要的格式(与cabinetRows所需字段匹配)
*/
private String createAgingCabinetMessage(List<TEquipmentInfo> equipmentList) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
List<Map<String, Object>> cabinetData = new ArrayList<>();
for (TEquipmentInfo equipment : equipmentList) {
Map<String, Object> dataMap = new HashMap<>();
// 前端需要的核心字段
dataMap.put("fEquipmentId", equipment.getfEquipmentId());
dataMap.put("fEquipmentCode", equipment.getfEquipmentCode());
dataMap.put("fStatus", equipment.getfStatus());
dataMap.put("errorReason", equipment.getErrorReason());
dataMap.put("registerValues", equipment.getRegisterValues()); // 可选:层状态数据
cabinetData.add(dataMap);
}
Map<String, Object> message = new HashMap<>();
message.put("type", "aging_cabinet_data"); // 消息类型标识
message.put("data", cabinetData);
return mapper.writeValueAsString(message);
}
}
\ No newline at end of file
......@@ -129,6 +129,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</trim>
where f_equipment_id = #{fEquipmentId}
</update>
<update id="batchUpdate" parameterType="list" >
<foreach collection="equipmentInfos" item="item" index="index" separator=";">
update t_equipment_info
<trim prefix="SET" suffixOverrides=",">
<if test="item.fStatus != null">f_status = #{item.fStatus},</if>
<if test="item.errorReason != null">f_error_Reason = #{item.errorReason},</if>
</trim>
where f_equipment_id = #{item.fEquipmentId}
</foreach>
</update>
<delete id="deleteTEquipmentInfoById" parameterType="Long">
delete from t_equipment_info where f_equipment_id = #{fEquipmentId}
......
......@@ -8,6 +8,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="fStoreyId" column="f_storey_id" />
<result property="fEquipmentId" column="f_equipment_id" />
<result property="fStoreyCode" column="f_storey_code" />
<result property="fTrayCode" column="f_tray_code" />
<result property="fIp" column="f_ip" />
<result property="fStatus" column="f_status" />
<result property="fPort" column="f_port" />
......@@ -18,7 +19,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<sql id="selectTStoreyInfoVo">
select f_storey_id, f_equipment_id, f_storey_code, f_ip, f_status, f_port, f_aging_start_time, f_update_time, f_create_time, f_alarm_time from t_storey_info
select storeyInfo.f_storey_id,
storeyInfo.f_equipment_id,
storeyInfo.f_storey_code,
storeyInfo.f_ip,
storeyInfo.f_status,
storeyInfo.f_port,
storeyInfo.f_aging_start_time,
storeyInfo.f_update_time,
storeyInfo.f_create_time,
storeyInfo.f_alarm_time,
trayInfo.f_tray_code
from t_storey_info storeyInfo
left join t_tray_info trayInfo on storeyInfo.f_storey_code = trayInfo.f_storey_code
</sql>
<select id="selectTStoreyInfoList" parameterType="TStoreyInfo" resultMap="TStoreyInfoResult">
......
......@@ -56,7 +56,7 @@
font-weight: bold;"
@click.native="handleCardClick(item)"
>
{{ item.fEquipmentId + "号柜" }}
{{ item.fEquipmentCode + "号柜" }}
</el-card>
</el-tooltip>
</el-col>
......@@ -68,52 +68,13 @@
</template>
<script>
import { getAgingCabinetAndPowerCheck } from "@/api/testScheduledTasks/testTasks";
import { getAllEquipmentList } from "@/api/equipment/equipment"
export default {
name: "AgingCabinetBoard",
data() {
return {
// 示例数据格式,实际从后端获取
cabinets: [
// { id: 1, deviceStatus: '1' },
// { id: 2, deviceStatus: '2' },
// { id: 3, deviceStatus: '0' },
// { id: 4, deviceStatus: '0' },
// { id: 5, deviceStatus: '0' },
// { id: 6, deviceStatus: '0' },
// { id: 7, deviceStatus: '0' },
// { id: 8, deviceStatus: '0' },
// { id: 9, deviceStatus: '0' },
// { id: 10, deviceStatus: '0' },
// { id: 11, deviceStatus: '0' },
// { id: 12, deviceStatus: '0' },
// { id: 13, deviceStatus: '0' },
// { id: 14, deviceStatus: '0' },
// { id: 15, deviceStatus: '0' },
// { id: 16, deviceStatus: '0' },
// { id: 17, deviceStatus: '0' },
// { id: 18, deviceStatus: '0' },
// { id: 19, deviceStatus: '0' },
// { id: 20, deviceStatus: '0' },
// { id: 21, deviceStatus: '0' },
// { id: 22, deviceStatus: '0' },
// { id: 23, deviceStatus: '0' },
// { id: 24, deviceStatus: '0' },
// { id: 25, deviceStatus: '0' },
// { id: 26, deviceStatus: '0' },
// { id: 27, deviceStatus: '0' },
// { id: 28, deviceStatus: '0' },
// { id: 29, deviceStatus: '0' },
// { id: 30, deviceStatus: '0' },
// { id: 31, deviceStatus: '0' },
// { id: 32, deviceStatus: '0' },
// { id: 33, deviceStatus: '0' },
// { id: 34, deviceStatus: '0' },
// { id: 35, deviceStatus: '0' },
// { id: 36, deviceStatus: '0' },
// 共36个
],
cabinets: [],
// 状态对应的颜色类名
statusMap: {
0: 'default',
......@@ -138,8 +99,50 @@ export default {
},
mounted() {
this.testAgingCabinetAndPowerCheck();
this.initWebSocket(); // 初始化WebSocket连接
},
methods: {
// 初始化WebSocket连接
initWebSocket() {
// 根据当前页面协议选择ws/wss
const backendUrl = process.env.VUE_APP_API_BASE_URL || 'http://localhost:8080';
const wsUrl = backendUrl.replace('http', 'ws') + '/ws-aging-cabinet';
this.ws = new WebSocket(wsUrl);
// 连接建立
this.ws.onopen = () => {
console.log('老化柜WebSocket连接已建立');
this.pageLoading = false;
};
// 接收消息
this.ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
// 只处理老化柜数据类型的消息
if (message.type === 'aging_cabinet_data') {
this.cabinets = message.data; // 更新数据,自动触发cabinetRows重新计算
console.log('收到老化柜更新数据,共', message.data.length, '条');
}
} catch (e) {
console.error('解析WebSocket消息失败', e);
}
};
// 连接关闭(自动重连)
this.ws.onclose = () => {
console.log('老化柜WebSocket连接关闭,3秒后尝试重连');
setTimeout(() => this.initWebSocket(), 3000);
};
// 连接错误
this.ws.onerror = (error) => {
console.error('老化柜WebSocket错误', error);
this.pageLoading = false;
};
},
handleCardClick(item) {
// 触发事件传递 cabinetId 给父组件
// 3 是 AgingLayer 组件的索引
......@@ -159,15 +162,17 @@ export default {
testAgingCabinetAndPowerCheck() {
// 开始加载:显示全局loading
this.pageLoading = true;
// getAgingCabinetAndPowerCheck().then(response => {
// this.cabinets = response;
// this.pageLoading = false;
// });
getAllEquipmentList().then(response => {
this.cabinets = response;
this.pageLoading = false;
})
},
// 组件销毁时关闭WebSocket连接
beforeDestroy() {
if (this.ws) {
this.ws.close();
this.ws = null;
}
}
}
};
......
......@@ -5,7 +5,7 @@
<!-- 头部标题区域 -->
<div class="tray-header">
<div class="tray-title">
<div class="title-text">{{ modbusDeviceData.id }}号柜</div>
<div class="title-text">{{ modbusDeviceData.fEquipmentCode }}号柜</div>
<div class="title-line"></div>
</div>
</div>
......@@ -30,7 +30,12 @@
:class="[
'layer-depth',
`layer-${index}`,
getStatusClass(layer.fStatus) // 动态状态类
{
'status-running': layer.fStatus === '1',
'status-fault': layer.fStatus === '2',
'status-idle': layer.fStatus === '0',
'status-power_outage': layer.fStatus === '3'
}
]"
@click="selectLayer(index)"
>
......@@ -51,18 +56,14 @@
<!-- 托盘信息展示区域 -->
<div class="tray-header-inner">
<span class="tray-label">托盘</span>
<span class="tray-id">{{ modbusDeviceData.fTrayCode }}</span>
<span class="tray-id">{{ trayInfo.fTrayCode }}</span>
</div>
<div class="tray-info">
<div class="info-row">
<div class="info-label">所在柜体:</div>
<div class="info-value">{{ modbusDeviceData.id }}号柜 - {{ trayInfo.layer.split("-")[1] || '--' }}</div>
<div class="info-value">{{ modbusDeviceData.fEquipmentCode }}号柜 - {{ trayInfo.layer ? trayInfo.layer.split("-")[1] : '--' }}</div>
</div>
<!-- <div class="info-row">-->
<!-- <div class="info-label">产品型号:</div>-->
<!-- <div class="info-value">{{ trayInfo.productModel || '&#45;&#45;' }}</div>-->
<!-- </div>-->
<div class="info-row">
<div class="info-label">当前状态:</div>
<div class="info-value" :class="trayInfo.statusClass || 'status-idle'">
......@@ -136,8 +137,8 @@ export default {
}
},
mounted() {
if (this.modbusDeviceData && this.modbusDeviceData.id) {
this.loadCabinetData(this.modbusDeviceData.id);
if (this.modbusDeviceData && this.modbusDeviceData.fEquipmentId) {
this.loadCabinetData(this.modbusDeviceData.fEquipmentId);
}
},
beforeDestroy() {
......@@ -331,27 +332,107 @@ export default {
border-radius: 2px;
}
/* 状态背景色 */
.status-idle {
background: linear-gradient(to bottom, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.2));
border-color: rgba(240, 240, 240, 1) !important;
/* 层状态样式 - 运行中 */
.status-running {
/* 运行中状态 - 绿色 #67c23a */
color: #67c23a;
border: 1px solid rgba(103, 194, 58, 0.8);
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.6),
inset 0 0 8px rgba(103, 194, 58, 0.5);
}
.status-running.layer-depth {
border-width: 1px 3px 3px 1px;
border-color:
rgba(103, 194, 58, 0.4)
rgba(103, 194, 58, 0.8)
rgba(103, 194, 58, 0.8)
rgba(103, 194, 58, 0.4);
}
.status-running {
background: linear-gradient(to bottom, rgba(100, 255, 100, 0.15), rgba(0, 80, 0, 0.2)) !important;
border-color: rgba(100, 255, 100, 0.8) !important;
.status-running:hover {
background: linear-gradient(to bottom, rgba(103, 194, 58, 0.2), rgba(0,0,0,0.3));
box-shadow:
0 4px 15px rgba(103, 194, 58, 0.4),
inset 0 0 10px rgba(103, 194, 58, 0.4);
}
/* 层状态样式 - 故障 */
.status-fault {
background: linear-gradient(to bottom, rgba(255, 100, 100, 0.15), rgba(80, 0, 0, 0.2)) !important;
border-color: rgba(255, 100, 100, 0.8) !important;
/* 故障状态 - 红色 #f56c6c */
color: #f56c6c;
border: 1px solid rgba(245, 108, 108, 0.8);
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.6),
inset 0 0 8px rgba(245, 108, 108, 0.5);
}
.status-fault.layer-depth {
border-width: 1px 3px 3px 1px;
border-color:
rgba(245, 108, 108, 0.4)
rgba(245, 108, 108, 0.8)
rgba(245, 108, 108, 0.8)
rgba(245, 108, 108, 0.4);
}
.status-fault:hover {
background: linear-gradient(to bottom, rgba(245, 108, 108, 0.2), rgba(0,0,0,0.3));
box-shadow:
0 4px 15px rgba(245, 108, 108, 0.4),
inset 0 0 10px rgba(245, 108, 108, 0.4);
}
/* 层状态样式 - 空闲 */
.status-idle {
/* 空闲状态 - 浅灰 #f0f0f0 */
color: #f0f0f0;
border: 1px solid rgba(240, 240, 240, 0.8);
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.6),
inset 0 0 8px rgba(240, 240, 240, 0.5);
}
.status-idle.layer-depth {
border-width: 1px 3px 3px 1px;
border-color:
rgba(240, 240, 240, 0.4)
rgba(240, 240, 240, 0.8)
rgba(240, 240, 240, 0.8)
rgba(240, 240, 240, 0.4);
}
.status-idle:hover {
background: linear-gradient(to bottom, rgba(240, 240, 240, 0.2), rgba(0,0,0,0.3));
box-shadow:
0 4px 15px rgba(240, 240, 240, 0.4),
inset 0 0 10px rgba(240, 240, 240, 0.4);
}
/* 层状态样式 - 断电 */
.status-power_outage {
background: linear-gradient(to bottom, rgba(150, 150, 150, 0.15), rgba(50, 50, 50, 0.2)) !important;
border-color: rgba(150, 150, 150, 0.8) !important;
color: #9e9e9e;
border: 1px solid rgba(158, 158, 158, 0.8);
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.6),
inset 0 0 8px rgba(158, 158, 158, 0.5);
}
.status-power_outage.layer-depth {
border-width: 1px 3px 3px 1px;
border-color:
rgba(158, 158, 158, 0.4)
rgba(158, 158, 158, 0.8)
rgba(158, 158, 158, 0.8)
rgba(158, 158, 158, 0.4);
}
.status-power_outage:hover {
background: linear-gradient(to bottom, rgba(158, 158, 158, 0.2), rgba(0,0,0,0.3));
box-shadow:
0 4px 15px rgba(158, 158, 158, 0.4),
inset 0 0 10px rgba(158, 158, 158, 0.4);
}
/* 层内容样式 */
......@@ -368,11 +449,13 @@ export default {
font-size: 16px;
font-weight: bold;
margin-bottom: 4px;
text-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
}
.layer-status {
font-size: 12px;
opacity: 0.9;
text-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
}
/* 老化时间样式 */
......@@ -424,10 +507,6 @@ export default {
.layer {
position: absolute;
left: 0;
border: 1px solid rgba(100, 180, 255, 0.6);
box-shadow:
0 4px 12px rgba(0, 0, 0, 0.6),
inset 0 0 8px rgba(100, 200, 255, 0.3);
display: flex;
align-items: center;
justify-content: center;
......@@ -438,22 +517,9 @@ export default {
}
.layer:hover {
background: linear-gradient(to bottom, rgba(100, 180, 255, 0.2), rgba(0,0,0,0.3));
box-shadow:
0 4px 15px rgba(100, 200, 255, 0.4),
inset 0 0 10px rgba(100, 220, 255, 0.4);
transform: translateY(-2px);
}
.layer-depth {
border-width: 1px 3px 3px 1px;
border-color:
rgba(100, 180, 255, 0.4)
rgba(100, 180, 255, 0.8)
rgba(100, 180, 255, 0.8)
rgba(100, 180, 255, 0.4);
}
.right-content {
flex: 1;
margin-left: 80px;
......@@ -553,18 +619,19 @@ export default {
color: #fff;
}
/* 右侧状态文本样式 */
.status-idle {
color: #ffcc00;
color: #f0f0f0;
font-weight: bold;
}
.status-running {
color: #64ffaa;
color: #67c23a;
font-weight: bold;
}
.status-fault {
color: #ff3d3d;
color: #f56c6c;
font-weight: bold;
}
......@@ -656,5 +723,4 @@ export default {
color: #66b1ff;
transform: translateX(-5px);
}
</style>
......@@ -152,7 +152,7 @@ export default {
name: 'RoboticArm',
data() {
return {
status: 'unknown', // idle, running, error
status: 'idle', // idle, running, error
showAddDialog: false,
trayCode: '',
loadingCommands: [
......@@ -235,7 +235,7 @@ export default {
this.websocket.onopen = () => {
console.log('机械臂指令WebSocket连接成功');
this.status = 'unknown';
this.status = 'idle';
this.sendWebSocketMessage({ type: 'request', commands: ['loading', 'unloading'] });
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment