fix: 读写

This commit is contained in:
2026-03-11 10:14:17 +08:00
parent e8445bb286
commit 12e8483804
7 changed files with 162 additions and 91 deletions

View File

@@ -16,8 +16,6 @@ import org.nl.iot.modular.iot.entity.IotConnect;
@NoArgsConstructor
@AllArgsConstructor
public class RValue {
// private String connectId;
// private String deviceCode;
/**
* 配置
*/

View File

@@ -1,5 +1,6 @@
package org.nl.iot.core.driver.protocol.modbustcp;
import com.alibaba.fastjson.JSONObject;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.nl.common.exception.CommonException;
@@ -41,7 +42,7 @@ public class ModBusProtocolDriverImpl implements DriverCustomService {
@Override
@PostConstruct
public void initial() {
connectMap = new ConcurrentHashMap<>(16);
connectMap = new ConcurrentHashMap<>(256);
}
@Override
@@ -50,20 +51,20 @@ public class ModBusProtocolDriverImpl implements DriverCustomService {
}
@Override
public RValue read(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig, IotConnect connect, IotConfig config) {
return new RValue(config, connect, readValue(getConnector(connect.getId().toString(), driverConfig), pointConfig));
public RValue read(IotConnect connect, IotConfig config) {
return new RValue(config, connect, readValue(getConnector(connect), connect, config));
}
@Override
public Boolean write(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig, IotConnect connect, IotConfig config, WValue wValue) {
public Boolean write(IotConnect connect, IotConfig config, WValue wValue) {
/*
* 写入设备点位数据
*
* 提示: 此处逻辑仅供参考, 请务必结合实际应用场景进行修改。
* 通过 Modbus 连接器将指定值写入设备的点位, 并返回写入结果。
*/
ModbusMaster modbusMaster = getConnector(connect.getId().toString(), driverConfig);
return writeValue(modbusMaster, pointConfig, wValue);
ModbusMaster modbusMaster = getConnector(connect);
return writeValue(modbusMaster,connect, config, wValue);
}
/**
@@ -78,19 +79,19 @@ public class ModBusProtocolDriverImpl implements DriverCustomService {
* @return ModbusMaster 返回与设备关联的 Modbus Master 连接器
* @throws CommonException 如果连接器初始化失败, 抛出此异常
*/
private ModbusMaster getConnector(String connectId, Map<String, AttributeBO> driverConfig) {
log.debug("Modbus Tcp Connection Info: {}", driverConfig);
ModbusMaster modbusMaster = connectMap.get(connectId);
private ModbusMaster getConnector(IotConnect connect) {
log.debug("Modbus Tcp Connection Info: {}", connect);
ModbusMaster modbusMaster = connectMap.get(connect.getId().toString());
if (Objects.isNull(modbusMaster)) {
IpParameters params = new IpParameters();
params.setHost(driverConfig.get("host").getValueByClass(String.class));
params.setPort(driverConfig.get("port").getValueByClass(Integer.class));
params.setHost(connect.getHost());
params.setPort(connect.getPort());
modbusMaster = modbusFactory.createTcpMaster(params, true);
try {
modbusMaster.init();
connectMap.put(connectId, modbusMaster);
connectMap.put(connect.getId().toString(), modbusMaster);
} catch (ModbusInitException e) {
connectMap.entrySet().removeIf(next -> next.getKey().equals(connectId));
connectMap.entrySet().removeIf(next -> next.getKey().equals(connect.getId().toString()));
log.error("Connect modbus master error: {}", e.getMessage(), e);
throw new CommonException(e.getMessage());
}
@@ -113,10 +114,11 @@ public class ModBusProtocolDriverImpl implements DriverCustomService {
* @param type 点位值类型, 用于确定寄存器中数据的解析方式
* @return String 返回读取到的点位值, 以字符串形式表示。如果功能码不支持, 则返回 "0"。
*/
private String readValue(ModbusMaster modbusMaster, Map<String, AttributeBO> pointConfig) {
String type = pointConfig.get("data_type").getValueByClass(String.class);
int slaveId = pointConfig.get("slaveId").getValueByClass(Integer.class);
int offset = pointConfig.get("offset").getValueByClass(Integer.class);
private String readValue(ModbusMaster modbusMaster, IotConnect connect, IotConfig config) {
JSONObject pointConfig = JSONObject.parseObject(connect.getProperties());
String type = config.getDataType();
int slaveId = pointConfig.getIntValue("slaveId");
int offset = Integer.parseInt(config.getRegisterAddress());
int functionCode = ModBusTcpUtils.getFunctionCode(offset);
// 计算实际的寄存器地址Modbus协议地址从0开始
@@ -161,12 +163,13 @@ public class ModBusProtocolDriverImpl implements DriverCustomService {
* @param wValue 待写入的值, 包含值类型和具体数值
* @return boolean 返回写入结果, true 表示写入成功, false 表示写入失败或不支持的功能码
*/
private boolean writeValue(ModbusMaster modbusMaster, Map<String, AttributeBO> pointConfig, WValue wValue) {
String type = pointConfig.get("data_type").getValueByClass(String.class);
wValue.setType(type);
int slaveId = pointConfig.get("slaveId").getValueByClass(Integer.class);
int offset = pointConfig.get("offset").getValueByClass(Integer.class);
private boolean writeValue(ModbusMaster modbusMaster, IotConnect connect, IotConfig config, WValue wValue) {
JSONObject pointConfig = JSONObject.parseObject(connect.getProperties());
String type = config.getDataType();
int slaveId = pointConfig.getIntValue("slaveId");
int offset = Integer.parseInt(config.getRegisterAddress());
int functionCode = ModBusTcpUtils.getFunctionCode(offset);
wValue.setType(type);
// 计算实际的寄存器地址Modbus协议地址从0开始
int actualAddress = ModBusTcpUtils.getActualAddress(offset, functionCode);

View File

@@ -1,6 +1,7 @@
package org.nl.iot.core.driver.protocol.opcda;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.jinterop.dcom.common.JIException;
@@ -52,14 +53,14 @@ public class OpcDaProtocolDriverImpl implements DriverCustomService {
}
@Override
public RValue read(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig, IotConnect connect, IotConfig config) {
return new RValue(config, connect, readValue(getConnector(connect.getId().toString(), driverConfig), pointConfig));
public RValue read(IotConnect connect, IotConfig config) {
return new RValue(config, connect, readValue(getConnector(connect), connect, config));
}
@Override
public Boolean write(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig, IotConnect device, IotConfig point, WValue wValue) {
Server server = getConnector(device.getId().toString(), driverConfig);
return writeValue(server, pointConfig, wValue);
public Boolean write(IotConnect connect, IotConfig config, WValue wValue) {
Server server = getConnector(connect);
return writeValue(server, connect, config, wValue);
}
/**
@@ -72,21 +73,22 @@ public class OpcDaProtocolDriverImpl implements DriverCustomService {
* @return Server 返回与设备ID对应的 OPC DA 服务器连接
* @throws ConnectorException 如果连接 OPC DA 服务器时发生异常, 则抛出此异常
*/
private Server getConnector(String deviceId, Map<String, AttributeBO> driverConfig) {
log.debug("Opc Da Server Connection Info {}", driverConfig);
Server server = connectMap.get(deviceId);
private Server getConnector(IotConnect connect) {
log.debug("Opc Da Server Connection Info {}", connect);
JSONObject config = JSONObject.parseObject(connect.getProtocol());
Server server = connectMap.get(connect.getId().toString());
if (Objects.isNull(server)) {
String host = driverConfig.get("host").getValueByClass(String.class);
String clsId = driverConfig.get("clsId").getValueByClass(String.class);
String user = driverConfig.get("username").getValueByClass(String.class);
String password = ObjectUtil.isEmpty(driverConfig.get("password").getValue()) ? "" : driverConfig.get("password").getValueByClass(String.class);
String host = connect.getHost();
String clsId = config.getString("clsId");
String user = config.getString("username");
String password = ObjectUtil.isEmpty(config.get("password")) ? "" : config.getString("password");
ConnectionInformation connectionInformation = new ConnectionInformation(host, clsId, user, password);
server = new Server(connectionInformation, Executors.newSingleThreadScheduledExecutor());
try {
server.connect();
connectMap.put(deviceId, server);
connectMap.put(connect.getId().toString(), server);
} catch (AlreadyConnectedException | UnknownHostException | JIException e) {
connectMap.entrySet().removeIf(next -> next.getKey().equals(deviceId));
connectMap.entrySet().removeIf(next -> next.getKey().equals(connect.getId().toString()));
log.error("Connect opc da server error: {}", e.getMessage(), e);
throw new CommonException(e.getMessage());
}
@@ -105,9 +107,9 @@ public class OpcDaProtocolDriverImpl implements DriverCustomService {
* @return String 返回读取到的位号值
* @throws ReadPointException 如果读取位号值时发生异常, 则抛出此异常
*/
private String readValue(Server server, Map<String, AttributeBO> pointConfig) {
private String readValue(Server server, IotConnect connect, IotConfig config) {
try {
Item item = getItem(server, pointConfig);
Item item = getItem(server, connect, config);
return readItem(item);
} catch (NotConnectedException | JIException | AddFailedException | DuplicateGroupException |
UnknownHostException e) {
@@ -132,15 +134,15 @@ public class OpcDaProtocolDriverImpl implements DriverCustomService {
* @throws DuplicateGroupException 如果尝试添加已存在的组, 则抛出此异常
* @throws AddFailedException 如果添加组或 Item 失败, 则抛出此异常
*/
public Item getItem(Server server, Map<String, AttributeBO> pointConfig) throws NotConnectedException, JIException, UnknownHostException, DuplicateGroupException, AddFailedException {
public Item getItem(Server server, IotConnect connect, IotConfig config) throws NotConnectedException, JIException, UnknownHostException, DuplicateGroupException, AddFailedException {
Group group;
String groupName = pointConfig.get("group").getValueByClass(String.class);
String groupName = connect.getCode();
try {
group = server.findGroup(groupName);
} catch (UnknownGroupException e) {
group = server.addGroup(groupName);
}
return group.addItem(pointConfig.get("tag").getValueByClass(String.class));
return group.addItem(groupName + "." + config.getDeviceCode() + "." + config.getAlias());
}
/**
@@ -194,9 +196,9 @@ public class OpcDaProtocolDriverImpl implements DriverCustomService {
* @return boolean 返回写入操作是否成功
* @throws WritePointException 如果写入位号值时发生异常, 则抛出此异常
*/
private boolean writeValue(Server server, Map<String, AttributeBO> pointConfig, WValue wValue) {
private boolean writeValue(Server server, IotConnect connect, IotConfig config, WValue wValue) {
try {
Item item = getItem(server, pointConfig);
Item item = getItem(server, connect, config);
return writeItem(item, wValue);
} catch (NotConnectedException | AddFailedException | DuplicateGroupException | UnknownHostException |
JIException e) {

View File

@@ -1,5 +1,6 @@
package org.nl.iot.core.driver.protocol.opcua;
import com.alibaba.fastjson.JSONObject;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
@@ -51,8 +52,8 @@ public class OpcUaProtocolDriverImpl implements DriverCustomService {
@Override
public RValue read(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig, IotConnect connect, IotConfig config) {
return new RValue(config, connect, readValue(getConnector(connect.getId().toString(), driverConfig), pointConfig));
public RValue read(IotConnect connect, IotConfig config) {
return new RValue(config, connect, readValue(getConnector(connect), config));
}
/**
@@ -63,13 +64,14 @@ public class OpcUaProtocolDriverImpl implements DriverCustomService {
* @return OpcUaClient 返回与指定设备关联的 OPC UA 客户端实例
* @throws CommonException 如果连接 OPC UA 服务器失败, 抛出此异常
*/
private OpcUaClient getConnector(String deviceId, Map<String, AttributeBO> driverConfig) {
log.debug("OPC UA server connection info: {}", driverConfig);
OpcUaClient opcUaClient = connectMap.get(deviceId);
private OpcUaClient getConnector(IotConnect connect) {
log.debug("OPC UA server connection info: {}", connect);
OpcUaClient opcUaClient = connectMap.get(connect.getId().toString());
if (Objects.isNull(opcUaClient)) {
String host = driverConfig.get("host").getValueByClass(String.class);
int port = driverConfig.get("port").getValueByClass(Integer.class);
String path = driverConfig.get("path").getValueByClass(String.class);
JSONObject driverConfig = JSONObject.parseObject(connect.getProperties());
String host = connect.getHost();
int port = connect.getPort();
String path = driverConfig.getString("path");
String url = String.format("opc.tcp://%s:%s%s", host, port, path);
try {
opcUaClient = OpcUaClient.create(
@@ -80,9 +82,9 @@ public class OpcUaProtocolDriverImpl implements DriverCustomService {
.setRequestTimeout(Unsigned.uint(5000)) // 设置请求超时时间为 5000 毫秒
.build()
);
connectMap.put(deviceId, opcUaClient);
connectMap.put(connect.getId().toString(), opcUaClient);
} catch (UaException e) {
connectMap.entrySet().removeIf(next -> next.getKey().equals(deviceId));
connectMap.entrySet().removeIf(next -> next.getKey().equals(connect.getId().toString()));
log.error("Failed to connect OPC UA client: {}", e.getMessage(), e);
throw new CommonException(e.getMessage());
}
@@ -94,13 +96,13 @@ public class OpcUaProtocolDriverImpl implements DriverCustomService {
* 读取 OPC UA 节点的值
*
* @param client OPC UA 客户端实例
* @param pointConfig 点位配置信息
* @param config 点位配置信息
* @return 读取到的节点值
* @throws CommonException 如果读取操作失败, 抛出此异常
*/
private String readValue(OpcUaClient client, Map<String, AttributeBO> pointConfig) {
private String readValue(OpcUaClient client, IotConfig config) {
try {
NodeId nodeId = getNode(pointConfig);
NodeId nodeId = getNode(config);
// 确保客户端已连接
client.connect().get(10, TimeUnit.SECONDS);
@@ -126,23 +128,23 @@ public class OpcUaProtocolDriverImpl implements DriverCustomService {
}
@Override
public Boolean write(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig, IotConnect device, IotConfig point, WValue wValue) {
OpcUaClient client = getConnector(device.getId().toString(), driverConfig);
return writeValue(client, pointConfig, wValue);
public Boolean write(IotConnect connect, IotConfig config, WValue wValue) {
OpcUaClient client = getConnector(connect);
return writeValue(client, config, wValue);
}
/**
* 写入 OPC UA 节点的值
*
* @param client OPC UA 客户端实例
* @param pointConfig 点位配置信息
* @param config 点位配置信息
* @param wValue 写入值
* @return 写入操作是否成功
* @throws CommonException 如果写入操作失败, 抛出此异常
*/
private boolean writeValue(OpcUaClient client, Map<String, AttributeBO> pointConfig, WValue wValue) {
private boolean writeValue(OpcUaClient client, IotConfig config, WValue wValue) {
try {
NodeId nodeId = getNode(pointConfig);
NodeId nodeId = getNode(config);
// 确保客户端已连接,设置超时时间
client.connect().get(10, TimeUnit.SECONDS);
return writeNode(client, nodeId, wValue);
@@ -162,9 +164,69 @@ public class OpcUaProtocolDriverImpl implements DriverCustomService {
* @param pointConfig 点位配置信息, 包含命名空间和标签
* @return OPC UA 节点标识
*/
private NodeId getNode(Map<String, AttributeBO> pointConfig) {
int namespace = pointConfig.get("namespace").getValueByClass(Integer.class);
String tag = pointConfig.get("tag").getValueByClass(String.class);
private NodeId getNode(IotConfig config) {
// 解析地址ns=3;s=Temperature
return parseAddress(config.getRegisterAddress());
}
/**
* 解析地址字符串返回NodeId对象
* @param address 地址字符串格式要求ns=3;s=xxx
* @return 包含namespace(固定3)和tag的NodeId对象
* @throws IllegalArgumentException 输入格式错误或ns值不为3时抛出异常
*/
public static NodeId parseAddress(String address) {
// 校验输入不为空
if (address == null || address.trim().isEmpty()) {
throw new IllegalArgumentException("地址字符串不能为空");
}
// 按分号拆分字符串
String[] parts = address.split(";");
if (parts.length != 2) {
throw new IllegalArgumentException("地址格式错误正确格式应为ns=x;s=xxx");
}
int namespace = -1;
String tag = null;
// 遍历拆分后的部分提取ns和s的值
for (String part : parts) {
String[] keyValue = part.split("=");
if (keyValue.length != 2) {
throw new IllegalArgumentException("地址格式错误键值对格式应为key=value");
}
String key = keyValue[0].trim();
String value = keyValue[1].trim();
switch (key) {
case "ns":
// 解析ns的值并校验是否为3
try {
namespace = Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("ns的值必须是数字", e);
}
if (namespace != 3) {
throw new IllegalArgumentException("ns的值必须等于3");
}
break;
case "s":
// 提取s对应的标签值
tag = value;
break;
default:
throw new IllegalArgumentException("不支持的键:" + key + "仅支持ns和s");
}
}
// 校验s的值不为空
if (tag == null || tag.isEmpty()) {
throw new IllegalArgumentException("s的标签值不能为空");
}
// 返回NodeId对象
return new NodeId(namespace, tag);
}

View File

@@ -54,7 +54,7 @@ public class PlcS7ProtocolDriverImpl implements DriverCustomService {
}
@Override
public RValue read(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig, IotConnect connect, IotConfig config) {
public RValue read(IotConnect connect, IotConfig config) {
/*
* PLC S7 数据读取逻辑
*
@@ -66,14 +66,14 @@ public class PlcS7ProtocolDriverImpl implements DriverCustomService {
* 4. 将读取到的数据封装为 RValue 对象返回。
* 5. 捕获并记录异常, 确保锁在 finally 块中释放。
*/
log.debug("Plc S7 Read, device: {}, point: {}", driverConfig, pointConfig);
MyS7Connector myS7Connector = getS7Connector(connect.getId().toString(), driverConfig);
log.debug("Plc S7 Read, connect: {}, config: {}", connect, config);
MyS7Connector myS7Connector = getS7Connector(connect);
try {
myS7Connector.lock.writeLock().lock();
S7Serializer serializer = S7SerializerFactory.buildSerializer(myS7Connector.getConnector());
String type = pointConfig.get("data_type").getValueByClass(String.class);
PlcS7PointVariable plcs7PointVariable = getPointVariable(pointConfig, type);
String type = config.getDataType();
PlcS7PointVariable plcs7PointVariable = getPointVariable(config, type);
return new RValue(config, connect, String.valueOf(serializer.dispense(plcs7PointVariable)));
} catch (Exception e) {
log.error("Plc S7 Read Error: {}", e.getMessage());
@@ -84,12 +84,12 @@ public class PlcS7ProtocolDriverImpl implements DriverCustomService {
}
@Override
public Boolean write(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig, IotConnect device, IotConfig point, WValue wValue) {
public Boolean write(IotConnect device, IotConfig point, WValue wValue) {
log.debug("Plc S7 Write, device: {}, value: {}", device, wValue);
MyS7Connector myS7Connector = getS7Connector(String.valueOf(device.getId()), driverConfig);
MyS7Connector myS7Connector = getS7Connector(device);
myS7Connector.lock.writeLock().lock();
S7Serializer serializer = S7SerializerFactory.buildSerializer(myS7Connector.getConnector());
PlcS7PointVariable plcs7PointVariable = getPointVariable(pointConfig, wValue.getType());
PlcS7PointVariable plcs7PointVariable = getPointVariable(point, wValue.getType());
try {
store(serializer, plcs7PointVariable, wValue.getType(), wValue.getValue());
@@ -173,23 +173,23 @@ public class PlcS7ProtocolDriverImpl implements DriverCustomService {
* @return 返回与设备ID对应的 {@link MyS7Connector} 对象, 包含 S7 连接器和读写锁
* @throws CommonException 如果连接器创建失败, 抛出此异常
*/
private MyS7Connector getS7Connector(String connectId, Map<String, AttributeBO> driverConfig) {
MyS7Connector myS7Connector = connectMap.get(connectId);
private MyS7Connector getS7Connector(IotConnect connect) {
MyS7Connector myS7Connector = connectMap.get(connect.getId().toString());
if (Objects.isNull(myS7Connector)) {
myS7Connector = new MyS7Connector();
log.debug("Plc S7 Connection Info {}", driverConfig);
log.debug("Plc S7 Connection Info {}", connect);
try {
S7Connector s7Connector = S7ConnectorFactory.buildTCPConnector()
.withHost(driverConfig.get("host").getValueByClass(String.class))
.withPort(driverConfig.get("port").getValueByClass(Integer.class))
.withHost(connect.getHost())
.withPort(connect.getPort())
.build();
myS7Connector.setLock(new ReentrantReadWriteLock());
myS7Connector.setConnector(s7Connector);
} catch (Exception e) {
throw new CommonException("new s7connector fail" + e.getMessage());
}
connectMap.put(connectId, myS7Connector);
connectMap.put(connect.getId().toString(), myS7Connector);
}
return myS7Connector;
}
@@ -212,14 +212,16 @@ public class PlcS7ProtocolDriverImpl implements DriverCustomService {
* @return 返回封装好的 {@link PlcS7PointVariable} 对象, 包含点位变量的详细信息
* @throws NullPointerException 如果点位配置中缺少必要的属性, 抛出此异常
*/
private PlcS7PointVariable getPointVariable(Map<String, AttributeBO> pointConfig, String type) {
log.debug("Plc S7 Point Attribute Config {}", pointConfig);
return new PlcS7PointVariable(
pointConfig.get("dbNum").getValueByClass(Integer.class),
pointConfig.get("byteOffset").getValueByClass(Integer.class),
pointConfig.get("bitOffset").getValueByClass(Integer.class),
pointConfig.get("blockSize").getValueByClass(Integer.class),
type);
private PlcS7PointVariable getPointVariable(IotConfig config, String type) {
log.debug("Plc S7 Point Attribute Config {}", config);
// todo: DB 块地址解析
// return new PlcS7PointVariable(
// config.get("dbNum").getValueByClass(Integer.class),
// config.get("byteOffset").getValueByClass(Integer.class),
// config.get("bitOffset").getValueByClass(Integer.class),
// config.get("blockSize").getValueByClass(Integer.class),
// type);
return null;
}
/**

View File

@@ -43,7 +43,7 @@ public interface DriverCustomService {
* @param point 位号对象, 包含位号的基本信息和属性
* @return 返回读取到的数据, 封装在 {@link RValue} 对象中
*/
RValue read(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig, IotConnect device, IotConfig point);
RValue read(IotConnect device, IotConfig point);
/**
* 执行写操作
@@ -59,6 +59,6 @@ public interface DriverCustomService {
* @param wValue 待写入的数据, 封装在 {@link WValue} 对象中
* @return 返回写入操作是否成功, 若成功则返回 {@code true}, 否则返回 {@code false} 或抛出异常
*/
Boolean write(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig, IotConnect device, IotConfig point, WValue wValue);
Boolean write(IotConnect device, IotConfig point, WValue wValue);
}

View File

@@ -1,6 +1,7 @@
package org.nl.iot.modular.iot.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -68,4 +69,7 @@ public class IotConfig implements Serializable {
@Schema(description = "修改用户")
private String updateName;
@TableField(exist = false)
private String deviceCode;
}