diff --git a/DLT645-plugin/src/main/java/cc/iotkit/plugins/dlt645/conf/TcpClientConfig.java b/DLT645-plugin/src/main/java/cc/iotkit/plugins/dlt645/conf/TcpClientConfig.java index 412089d..413a4c5 100755 --- a/DLT645-plugin/src/main/java/cc/iotkit/plugins/dlt645/conf/TcpClientConfig.java +++ b/DLT645-plugin/src/main/java/cc/iotkit/plugins/dlt645/conf/TcpClientConfig.java @@ -16,4 +16,6 @@ public class TcpClientConfig { private String host; private int port; + + private int interval; } diff --git a/DLT645-plugin/src/main/java/cc/iotkit/plugins/dlt645/service/TcpClientVerticle.java b/DLT645-plugin/src/main/java/cc/iotkit/plugins/dlt645/service/TcpClientVerticle.java index 4414e33..b3132bc 100755 --- a/DLT645-plugin/src/main/java/cc/iotkit/plugins/dlt645/service/TcpClientVerticle.java +++ b/DLT645-plugin/src/main/java/cc/iotkit/plugins/dlt645/service/TcpClientVerticle.java @@ -1,6 +1,5 @@ package cc.iotkit.plugins.dlt645.service; -import cc.iotkit.common.utils.JsonUtils; import cc.iotkit.plugin.core.thing.IThingService; import cc.iotkit.plugin.core.thing.actions.DeviceState; import cc.iotkit.plugin.core.thing.actions.IDeviceAction; @@ -57,6 +56,8 @@ public class TcpClientVerticle extends AbstractVerticle { private int connectState = 0; + private long timerID; + @Override public void start() { log.info("init start"); @@ -74,10 +75,8 @@ public class TcpClientVerticle extends AbstractVerticle { options.setReconnectAttempts(Integer.MAX_VALUE); options.setReconnectInterval(20000L); netClient = vertx.createNetClient(options); - log.info("start1 connect->" + config.getHost() + ":" + config.getPort()); netClient.connect(config.getPort(), config.getHost()) .onComplete(result -> { - System.out.println("connect result:" + JsonUtils.toJsonString(result)); if (result.succeeded()) { connectState = 2; log.info("connect dlt645 server success"); @@ -91,7 +90,7 @@ public class TcpClientVerticle extends AbstractVerticle { Object func = ret.get(DLT645Analysis.FUN); DLT645FunCode funCode = DLT645FunCode.decodeEntity((byte) func); if (funCode.isError()) { - log.error("message erroe:{}", hexStr); + log.info("message erroe:{}", hexStr); return; } //获取设备地址 @@ -100,26 +99,31 @@ public class TcpClientVerticle extends AbstractVerticle { ByteUtils.byteInvertedOrder(adrrTmp, addr); //获取数据 byte[] dat = (byte[]) ret.get(DLT645Analysis.DAT); + String strAddr=ByteUtils.byteArrayToHexString(addr,false); DLT645V2007Data dataEntity = new DLT645V2007Data(); dataEntity.decodeValue(dat, DLT645Analysis.din2entity); Map params = new HashMap<>(); params.put("p" + dataEntity.getKey(), dataEntity.getValue());//数据标识 thingService.post(pluginInfo.getPluginId(), - applyPkDn(PropertyReport.builder() + PropertyReport.builder().deviceName(strAddr).productKey("PwMfpXmp4ZWkGahn") .params(params) .build() - ) ); }).closeHandler(res -> { connectState = 0; + vertx.cancelTimer(timerID); log.info("dlt645 tcp connection closed!"); stateChange(DeviceState.OFFLINE); } ).exceptionHandler(res -> { connectState = 0; + vertx.cancelTimer(timerID); log.info("dlt645 tcp connection exce!"); stateChange(DeviceState.OFFLINE); }); + timerID = vertx.setPeriodic(config.getInterval(), t -> { + readDataTask(); + }); } else { connectState = 0; log.info("connect dlt645 tcp error", result.cause()); @@ -132,7 +136,6 @@ public class TcpClientVerticle extends AbstractVerticle { ; } - @Scheduled(initialDelay = 5, fixedRate = 60, timeUnit = TimeUnit.SECONDS) private void readDataTask() { log.info("readData:" + socket); if (socket != null) { @@ -146,6 +149,8 @@ public class TcpClientVerticle extends AbstractVerticle { if (netClient != null) { netClient.close(); } + vertx.cancelTimer(timerID); + connectState = 0; super.stop(); } @@ -160,7 +165,7 @@ public class TcpClientVerticle extends AbstractVerticle { private IDeviceAction applyPkDn(IDeviceAction action) { action.setProductKey("BRD3x4fkKxkaxXFt"); - action.setDeviceName("123456789123"); + action.setDeviceName("WG123456"); return action; } diff --git a/DLT645-plugin/src/main/resources/application.yml b/DLT645-plugin/src/main/resources/application.yml index ff21f9b..c29e15f 100755 --- a/DLT645-plugin/src/main/resources/application.yml +++ b/DLT645-plugin/src/main/resources/application.yml @@ -3,5 +3,6 @@ plugin: mainPackage: cc.iotkit.plugin tcp: - host: xxx + host: 25on621889.goho.co port: 43161 + interval: 10000 diff --git a/DLT645-plugin/src/main/resources/config.json b/DLT645-plugin/src/main/resources/config.json index 4e2881f..2651447 100755 --- a/DLT645-plugin/src/main/resources/config.json +++ b/DLT645-plugin/src/main/resources/config.json @@ -3,14 +3,21 @@ "id": "host", "name": "服务端ip", "type": "text", - "value": "0.0.0.0", + "value": "25on621889.goho.co", "desc": "服务端ip" }, { "id": "port", "name": "服务端端口", "type": "number", - "value": 1111, + "value": 43161, "desc": "服务端端口" + }, + { + "id": "interval", + "name": "采集频率", + "type": "number", + "value": 10000, + "desc": "采集频率" } ] \ No newline at end of file diff --git a/hydrovalve-plugin/pom.xml b/hydrovalve-plugin/pom.xml new file mode 100644 index 0000000..2e1a147 --- /dev/null +++ b/hydrovalve-plugin/pom.xml @@ -0,0 +1,64 @@ + + + + iot-iita-plugins + cc.iotkit.plugins + 1.0.0 + + 4.0.0 + + hydrovalve-plugin + + + + dev + + true + + + dev + + + + + prod + + prod + + + + + + + + com.gitee.starblues + spring-brick-maven-packager + ${spring-brick-package.version} + + ${plugin.build.mode} + + hydrovalve-plugin + cc.iotkit.plugins.hydrovalve.Application + ${project.version} + iita + modbus插件 + application.yml + + + jar + + + + + + repackage + + + + + + + + \ No newline at end of file diff --git a/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/Application.java b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/Application.java new file mode 100644 index 0000000..a9ccbd5 --- /dev/null +++ b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/Application.java @@ -0,0 +1,22 @@ +package cc.iotkit.plugins.hydrovalve; + +import com.gitee.starblues.bootstrap.SpringPluginBootstrap; +import com.gitee.starblues.bootstrap.annotation.OneselfConfig; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * @Author:tfd + * @Date:2024/1/8 14:57 + */ +@SpringBootApplication(scanBasePackages = {"cc.iotkit.plugin.core", "cc.iotkit.plugins.hydrovalve"}) +@OneselfConfig(mainConfigFileName = {"application.yml"}) +@EnableConfigurationProperties +@EnableScheduling +public class Application extends SpringPluginBootstrap { + + public static void main(String[] args) { + new Application().run(Application.class, args); + } +} diff --git a/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/analysis/ModBusAnalysis.java b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/analysis/ModBusAnalysis.java new file mode 100644 index 0000000..807f69c --- /dev/null +++ b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/analysis/ModBusAnalysis.java @@ -0,0 +1,23 @@ +package cc.iotkit.plugins.hydrovalve.analysis; + +/** + * @Author:tfd + * @Date:2024/1/9 15:41 + */ +public abstract class ModBusAnalysis { + /** + * 编码:将实体打包成报文 + * + * @param entity 实体 + * @return 数据报文 + */ + public abstract byte[] packCmd4Entity(ModBusEntity entity); + + /** + * 解包:将报文解码成实体 + * + * @param arrCmd 报文 + * @return 实体 + */ + public abstract ModBusEntity unPackCmd2Entity(byte[] arrCmd); +} diff --git a/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/analysis/ModBusConstants.java b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/analysis/ModBusConstants.java new file mode 100644 index 0000000..ca1f24f --- /dev/null +++ b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/analysis/ModBusConstants.java @@ -0,0 +1,34 @@ +package cc.iotkit.plugins.hydrovalve.analysis; + +/** + * @Author:tfd + * @Date:2024/1/9 15:45 + */ +public class ModBusConstants { + public static final String MODE = "modbusMode"; + public static final String MODE_ASCII = "ASCII"; + public static final String MODE_RTU = "RTU"; + public static final String MODE_TCP = "TCP"; + + /** + * ModBus的报文结构 + */ + public static final String SN = "sn"; + public static final String ADDR = "devAddr"; + public static final String FUNC = "func"; + public static final String DATA = "data"; + + /** + * 寄存器地址和数量 + */ + public static final String REG_ADDR = "regAddr"; + public static final String REG_CNT = "regCnt"; + + public static final String REG_HOLD_STATUS = "regHoldStatus"; + + //读多个保持寄存器 + public static final byte FUN_CODE3 = 0x03; + + //写单个保持寄存器 + public static final byte FUN_CODE6 = 0x06; +} diff --git a/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/analysis/ModBusEntity.java b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/analysis/ModBusEntity.java new file mode 100644 index 0000000..a5cc274 --- /dev/null +++ b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/analysis/ModBusEntity.java @@ -0,0 +1,42 @@ +package cc.iotkit.plugins.hydrovalve.analysis; + +import lombok.Getter; +import lombok.Setter; + +/** + * @Author:tfd + * @Date:2024/1/9 15:44 + */ +@Getter +@Setter +public class ModBusEntity { + /** + * 流水号 + */ + private int sn = 0; + + /** + * 地址 + */ + private byte devAddr = 0x01; + + /** + * 功能码 + */ + private byte func = 0x01; + + /** + * 数据域 + */ + private byte[] data = new byte[0]; + + /** + * 出错信息 + */ + private int errCode = 0; + + /** + * 出错信息 + */ + private String errMsg = ""; +} diff --git a/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/analysis/ModBusError.java b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/analysis/ModBusError.java new file mode 100644 index 0000000..4beb772 --- /dev/null +++ b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/analysis/ModBusError.java @@ -0,0 +1,50 @@ +package cc.iotkit.plugins.hydrovalve.analysis; + +/** + * @Author:tfd + * @Date:2024/1/9 15:48 + */ +public class ModBusError { + static final String err01 = "err=01:非法的功能码"; + static final String err02 = "err=02:非法的数据地址"; + static final String err03 = "err=03:非法的数据值"; + static final String err04 = "err=04:服务器故障"; + static final String err05 = "err=05:确认。"; + static final String err06 = "err=06:服务器繁忙"; + static final String err10 = "err=10:网关故障:网关路经是无效的"; + static final String err11 = "err=11:网关故障:目标设备没有响应"; + + /** + * 获取出错信息 + * @param code 出错代码 + * @return 出错信息 + */ + static String getError(int code) { + if (code == 1) { + return err01; + } + if (code == 2) { + return err02; + } + if (code == 3) { + return err03; + } + if (code == 4) { + return err04; + } + if (code == 5) { + return err05; + } + if (code == 6) { + return err06; + } + if (code == 10) { + return err10; + } + if (code == 11) { + return err11; + } + + return ""; + } +} diff --git a/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/analysis/ModBusRtuAnalysis.java b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/analysis/ModBusRtuAnalysis.java new file mode 100644 index 0000000..c9626ba --- /dev/null +++ b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/analysis/ModBusRtuAnalysis.java @@ -0,0 +1,171 @@ +package cc.iotkit.plugins.hydrovalve.analysis; + +import cc.iotkit.common.utils.StringUtils; +import cc.iotkit.plugins.hydrovalve.utils.ByteUtils; +import lombok.extern.slf4j.Slf4j; + +/** + * @Author:tfd + * @Date:2024/1/9 15:53 + */ +@Slf4j +public class ModBusRtuAnalysis extends ModBusAnalysis { + /** + * 校验CRC16 + * + * @param arrCmd + * @return + */ + public static int getCRC16(byte[] arrCmd) { + int iSize = arrCmd.length - 2; + + // 检查:帧长度 + if (iSize < 2) { + return 0; + } + + int wCrcMathematics = 0xA001; + + int usCrc16 = 0x00; + + //16位的CRC寄存器 + int byteCrc16Lo = 0xFF; + int byteCrc16Hi = 0xFF; + //临时变量 + int byteSaveHi = 0x00; + int byteSaveLo = 0x00; + + //CRC多项式码的寄存器 + int byteCl = wCrcMathematics % 0x100; + int byteCh = wCrcMathematics / 0x100; + + for (int i = 0; i < iSize; i++) { + byteCrc16Lo &= 0xFF; + byteCrc16Hi &= 0xFF; + byteSaveHi &= 0xFF; + byteSaveLo &= 0xFF; + + byteCrc16Lo ^= arrCmd[i]; //每一个数据与CRC寄存器进行异或 + for (int k = 0; k < 8; k++) { + byteCrc16Lo &= 0xFF; + byteCrc16Hi &= 0xFF; + + byteSaveHi = byteCrc16Hi; + byteSaveLo = byteCrc16Lo; + byteCrc16Hi /= 2; //高位右移一位 + byteCrc16Lo /= 2; //低位右移一位 + if ((byteSaveHi & 0x01) == 0x01) //如果高位字节最后一位为1 + { + byteCrc16Lo |= 0x80; //则低位字节右移后前面补1 + } //否则自动补0 + if ((byteSaveLo & 0x01) == 0x01) //如果高位字节最后一位为1,则与多项式码进行异或 + { + byteCrc16Hi ^= byteCh; + byteCrc16Lo ^= byteCl; + } + } + } + + + usCrc16 = (byteCrc16Hi & 0xff) * 0x100 + (byteCrc16Lo & 0xff); + + return usCrc16; + } + + /** + * 解码 + * + * @param arrCmd 报文 + * @return 是否成功 + */ + @Override + public ModBusEntity unPackCmd2Entity(byte[] arrCmd) { + ModBusEntity entity = new ModBusEntity(); + + int iSize = arrCmd.length; + if (iSize < 4) { + return null; + } + + // 地址码 + byte byAddr = arrCmd[0]; + entity.setDevAddr(byAddr); + + // 功能码 + byte byFun = arrCmd[1]; + entity.setFunc(byFun); + + // 数据域 + int iDataSize = iSize - 4; + entity.setData(new byte[iDataSize]); + byte[] arrData = entity.getData(); + System.arraycopy(arrCmd, 2, arrData, 0, iDataSize); + + // 校验CRC + int wCrc16OK = getCRC16(arrCmd); + byte crcH = (byte) (wCrc16OK & 0xff); + byte crcL = (byte) ((wCrc16OK & 0xff00) >> 8); + if (arrCmd[arrCmd.length - 1] == crcL && arrCmd[arrCmd.length - 2] == crcH) { + return entity; + } + + return null; + } + + /** + * 编码 + * + * @return 编码是否成功 + */ + @Override + public byte[] packCmd4Entity(ModBusEntity entity) { + int iSize = entity.getData().length; + + byte[] arrCmd = new byte[iSize + 4]; + + // 地址码 + arrCmd[0] = entity.getDevAddr(); + + // 功能码 + arrCmd[1] = entity.getFunc(); + + // 数据域 + System.arraycopy(entity.getData(), 0, arrCmd, 2, iSize); + + // 校验CRC + int wCrc16 = getCRC16(arrCmd); + arrCmd[arrCmd.length - 2] = (byte) (wCrc16 % 0x100); + arrCmd[arrCmd.length - 1] = (byte) (wCrc16 / 0x100); + + return arrCmd; + } + + public static void main(String[] args) { +// String hexString = "0103020457FB7A"; + String hexString = "01060001000119CA"; + ModBusRtuAnalysis a=new ModBusRtuAnalysis(); + ModBusEntity b=a.unPackCmd2Entity(ByteUtils.hexStrToBinaryStr(hexString)); + int lenth=b.getData()[0]; + byte[] val = new byte[lenth]; + System.arraycopy(b.getData(), 1, val, 0, lenth); + log.info("ret:"+Integer.parseInt(ByteUtils.BinaryToHexString(val,false), 16)); + ModBusEntity c=new ModBusEntity(); + c.setDevAddr((byte) 1); + c.setFunc(ModBusConstants.FUN_CODE3); + Integer dz=0; + Integer dzsl=10; + String a1=StringUtils.leftPad(dz.toHexString(dz),4,'0')+StringUtils.leftPad(dz.toHexString(dzsl),4,'0'); + c.setData(ByteUtils.hexStrToBinaryStr(a1)); + byte[] d=a.packCmd4Entity(c); + log.info("ret1:"+ByteUtils.BinaryToHexString(d,false)); + ModBusEntity e=new ModBusEntity(); + e.setDevAddr((byte) 1); + e.setFunc(ModBusConstants.FUN_CODE6); + Integer dze=0; + Integer dzsle=1; + String a1e=StringUtils.leftPad(dz.toHexString(dze),4,'0')+StringUtils.leftPad(dz.toHexString(dzsle),4,'0'); + e.setData(ByteUtils.hexStrToBinaryStr(a1e)); + byte[] f=a.packCmd4Entity(e); + log.info("rete:"+ByteUtils.BinaryToHexString(f,false)); + } +} diff --git a/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/conf/BeanConfig.java b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/conf/BeanConfig.java new file mode 100644 index 0000000..3bcea2a --- /dev/null +++ b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/conf/BeanConfig.java @@ -0,0 +1,37 @@ +package cc.iotkit.plugins.hydrovalve.conf; + +import cc.iotkit.plugin.core.IPluginConfig; +import cc.iotkit.plugin.core.IPluginScript; +import cc.iotkit.plugin.core.LocalPluginConfig; +import cc.iotkit.plugin.core.LocalPluginScript; +import cc.iotkit.plugin.core.thing.IThingService; +import cc.iotkit.plugins.hydrovalve.service.FakeThingService; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; + +/** + * @Author:tfd + * @Date:2024/1/8 14:58 + */ +@Component +public class BeanConfig { + + @Bean + @ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev") + IThingService getThingService() { + return new FakeThingService(); + } + + @Bean + @ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev") + IPluginScript getPluginScript() { + return new LocalPluginScript("script.js"); + } + + @Bean + @ConditionalOnProperty(name = "plugin.runMode", havingValue = "dev") + IPluginConfig getPluginConfig(){ + return new LocalPluginConfig(); + } +} diff --git a/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/conf/HydrovalveConfig.java b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/conf/HydrovalveConfig.java new file mode 100644 index 0000000..0625e32 --- /dev/null +++ b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/conf/HydrovalveConfig.java @@ -0,0 +1,21 @@ +package cc.iotkit.plugins.hydrovalve.conf; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @Author:tfd + * @Date:2024/1/8 15:04 + */ +@Data +@Component +@ConfigurationProperties(prefix = "hydrovalve") +public class HydrovalveConfig { + + private String host; + + private int port; + + private int interval; +} diff --git a/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/service/FakeThingService.java b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/service/FakeThingService.java new file mode 100644 index 0000000..fa508e5 --- /dev/null +++ b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/service/FakeThingService.java @@ -0,0 +1,46 @@ +package cc.iotkit.plugins.hydrovalve.service; + +import cc.iotkit.model.device.DeviceInfo; +import cc.iotkit.model.product.Product; +import cc.iotkit.plugin.core.thing.IThingService; +import cc.iotkit.plugin.core.thing.actions.ActionResult; +import cc.iotkit.plugin.core.thing.actions.IDeviceAction; +import lombok.extern.slf4j.Slf4j; + +import java.util.HashMap; +import java.util.Map; + +/** + * @Author:tfd + * @Date:2024/1/8 14:58 + */ +@Slf4j +public class FakeThingService implements IThingService { + + @Override + public ActionResult post(String pluginId, IDeviceAction action) { + log.info("post action:{}", action); + return ActionResult.builder().code(0).build(); + } + + @Override + public Product getProduct(String pk) { + return Product.builder() + .productKey("cGCrkK7Ex4FESAwe") + .productSecret("aaaaaaaa") + .build(); + } + + @Override + public DeviceInfo getDevice(String dn) { + return DeviceInfo.builder() + .productKey("cGCrkK7Ex4FESAwe") + .deviceName(dn) + .build(); + } + + @Override + public Map getProperty(String dn) { + return new HashMap<>(0); + } +} diff --git a/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/service/ModBusDevice.java b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/service/ModBusDevice.java new file mode 100644 index 0000000..a9ad8b9 --- /dev/null +++ b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/service/ModBusDevice.java @@ -0,0 +1,62 @@ +package cc.iotkit.plugins.hydrovalve.service; + +import cc.iotkit.common.utils.StringUtils; +import cc.iotkit.plugin.core.thing.IDevice; +import cc.iotkit.plugin.core.thing.actions.ActionResult; +import cc.iotkit.plugin.core.thing.actions.down.DeviceConfig; +import cc.iotkit.plugin.core.thing.actions.down.PropertyGet; +import cc.iotkit.plugin.core.thing.actions.down.PropertySet; +import cc.iotkit.plugin.core.thing.actions.down.ServiceInvoke; +import cc.iotkit.plugins.hydrovalve.analysis.ModBusConstants; +import cc.iotkit.plugins.hydrovalve.analysis.ModBusEntity; +import cc.iotkit.plugins.hydrovalve.analysis.ModBusRtuAnalysis; +import cc.iotkit.plugins.hydrovalve.utils.ByteUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * @Author:tfd + * @Date:2024/1/10 11:06 + */ +@Service +public class ModBusDevice implements IDevice { + + @Autowired + private ModbusVerticle modbusVerticle; + + ModBusRtuAnalysis analysis=new ModBusRtuAnalysis(); + + @Override + public ActionResult config(DeviceConfig action) { + return ActionResult.builder().code(0).reason("").build(); + } + + @Override + public ActionResult propertyGet(PropertyGet action) { + return null; + } + + @Override + public ActionResult propertySet(PropertySet action) { + ModBusEntity read=new ModBusEntity(); + String devAddr=action.getDeviceName().split("_")[1]; + read.setFunc(ModBusConstants.FUN_CODE6); + read.setDevAddr(Byte.parseByte(devAddr)); + Integer addr=0; + for (Map.Entry entry : action.getParams().entrySet()) { + int val = Integer.parseInt((String) entry.getValue()); + String a1= StringUtils.leftPad(addr.toHexString(addr),4,'0')+StringUtils.leftPad(addr.toHexString(val),4,'0'); + read.setData(ByteUtils.hexStrToBinaryStr(a1)); + byte[] msg = analysis.packCmd4Entity(read); + modbusVerticle.sendMsg(msg); + } + return ActionResult.builder().code(0).reason("success").build(); + } + + @Override + public ActionResult serviceInvoke(ServiceInvoke action) { + return null; + } +} diff --git a/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/service/ModbusPlugin.java b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/service/ModbusPlugin.java new file mode 100644 index 0000000..d313f60 --- /dev/null +++ b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/service/ModbusPlugin.java @@ -0,0 +1,99 @@ +package cc.iotkit.plugins.hydrovalve.service; + +import cc.iotkit.common.utils.JsonUtils; +import cc.iotkit.plugin.core.IPluginConfig; +import cc.iotkit.plugin.core.IPluginScript; +import cc.iotkit.plugin.core.thing.IThingService; +import cc.iotkit.plugins.hydrovalve.conf.HydrovalveConfig; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; +import com.gitee.starblues.bootstrap.annotation.AutowiredType; +import com.gitee.starblues.bootstrap.realize.PluginCloseListener; +import com.gitee.starblues.core.PluginCloseType; +import com.gitee.starblues.core.PluginInfo; +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + * @Author:tfd + * @Date:2024/1/8 14:57 + */ +@Slf4j +@Service +public class ModbusPlugin implements PluginCloseListener { + + @Autowired + private PluginInfo pluginInfo; + + @Autowired + @AutowiredType(AutowiredType.Type.MAIN_PLUGIN) + private IPluginScript pluginScript; + + @Autowired + @AutowiredType(AutowiredType.Type.MAIN_PLUGIN) + private IThingService thingService; + + @Autowired + @AutowiredType(AutowiredType.Type.MAIN_PLUGIN) + private IPluginConfig pluginConfig; + + @Autowired + private HydrovalveConfig modbusConfig; + + @Autowired + private ModbusVerticle modbusVerticle; + + private Vertx vertx; + private String deployedId; + + @PostConstruct + public void init(){ + vertx = Vertx.vertx(); + try { + //获取插件最新配置替换当前配置 + Map config = pluginConfig.getConfig(pluginInfo.getPluginId()); + BeanUtil.copyProperties(config, modbusConfig, CopyOptions.create().ignoreNullValue()); + modbusVerticle.setModbusConfig(modbusConfig); + + Future future = vertx.deployVerticle(modbusVerticle); + future.onSuccess((s -> { + deployedId = s; + log.info("modbus plugin started success,config:"+ JsonUtils.toJsonString(modbusConfig)); + })); + future.onFailure(Throwable::printStackTrace); + } catch (Throwable e) { + log.error("modbus plugin error", e); + } + } + + @SneakyThrows + @Override + public void close(GenericApplicationContext applicationContext, PluginInfo pluginInfo, PluginCloseType closeType) { + log.info("plugin close,type:{},pluginId:{}", closeType, pluginInfo.getPluginId()); + if (deployedId != null) { + CountDownLatch wait = new CountDownLatch(1); + Future future = vertx.undeploy(deployedId); + future.onSuccess(unused -> { + log.info("modbus plugin stopped success"); + wait.countDown(); + }); + future.onFailure(h -> { + log.info("modbus plugin stopped failed"); + h.printStackTrace(); + wait.countDown(); + }); + wait.await(5, TimeUnit.SECONDS); + } + } + +} diff --git a/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/service/ModbusVerticle.java b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/service/ModbusVerticle.java new file mode 100644 index 0000000..09f7ca6 --- /dev/null +++ b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/service/ModbusVerticle.java @@ -0,0 +1,182 @@ +package cc.iotkit.plugins.hydrovalve.service; + +import cc.iotkit.common.utils.StringUtils; +import cc.iotkit.plugin.core.thing.IThingService; +import cc.iotkit.plugin.core.thing.actions.DeviceState; +import cc.iotkit.plugin.core.thing.actions.up.DeviceRegister; +import cc.iotkit.plugin.core.thing.actions.up.DeviceStateChange; +import cc.iotkit.plugin.core.thing.actions.up.PropertyReport; +import cc.iotkit.plugins.hydrovalve.analysis.ModBusConstants; +import cc.iotkit.plugins.hydrovalve.analysis.ModBusEntity; +import cc.iotkit.plugins.hydrovalve.analysis.ModBusRtuAnalysis; +import cc.iotkit.plugins.hydrovalve.conf.HydrovalveConfig; +import cc.iotkit.plugins.hydrovalve.utils.ByteUtils; +import cn.hutool.core.util.IdUtil; +import com.gitee.starblues.bootstrap.annotation.AutowiredType; +import com.gitee.starblues.core.PluginInfo; +import io.vertx.core.AbstractVerticle; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.net.NetClient; +import io.vertx.core.net.NetClientOptions; +import io.vertx.core.net.NetSocket; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +/** + * @Author:tfd + * @Date:2024/1/9 17:06 + */ +@Slf4j +@Service +public class ModbusVerticle extends AbstractVerticle { + @Getter + @Setter + private HydrovalveConfig modbusConfig; + + private NetClient netClient; + + private NetSocket socket; + + @Autowired + @AutowiredType(AutowiredType.Type.MAIN_PLUGIN) + private IThingService thingService; + + ModBusRtuAnalysis analysis=new ModBusRtuAnalysis(); + + @Autowired + private PluginInfo pluginInfo; + + private int connectState = 0; + + private long timerID; + + @Override + public void start() { + log.info("init start"); + } + + @Scheduled(initialDelay = 2, fixedRate = 5, timeUnit = TimeUnit.SECONDS) + public void initClient() { + if (connectState > 0) { + return; + } + connectState = 1; + NetClientOptions options = new NetClientOptions(); + options.setReconnectAttempts(Integer.MAX_VALUE); + options.setReconnectInterval(20000L); + netClient = vertx.createNetClient(options); + netClient.connect(modbusConfig.getPort(), modbusConfig.getHost()) + .onComplete(result -> { + if (result.succeeded()) { + connectState = 2; + log.info("connect modbus slave success"); + socket = result.result(); + thingService.post(pluginInfo.getPluginId(), DeviceRegister.builder() + .id(UUID.randomUUID().toString()) + .productKey("PYWH4r8xBzsfn3XB") + .deviceName(String.format("modbus_%d", 1)) + .build()); + stateChange(DeviceState.ONLINE,String.format("modbus_%d", 1)); + socket.handler(data -> { + String hexStr = ByteUtils.BinaryToHexString(data.getBytes(), false); + log.info("modbus received message:{}", hexStr); + //获取功能码 + if(0x03==data.getBytes()[1]){ + ModBusEntity ret = analysis.unPackCmd2Entity(data.getBytes()); + Map params = new HashMap<>(); + params.put("devSwith" , Integer.parseInt(ByteUtils.BinaryToHexString(getData(ret.getData()),false)));//数据标识 + thingService.post(pluginInfo.getPluginId(), + PropertyReport.builder().deviceName(String.format("modbus_%d", ret.getDevAddr())).productKey("PwMfpXmp4ZWkGahn") + .params(params) + .build() + ); + } + }).closeHandler(res -> { + connectState = 0; + vertx.cancelTimer(timerID); + log.info("modbus tcp connection closed!"); + stateChange(DeviceState.OFFLINE,String.format("modbus_%d", 1)); + } + ).exceptionHandler(res -> { + connectState = 0; + vertx.cancelTimer(timerID); + log.info("modbus tcp connection exce!"); + stateChange(DeviceState.OFFLINE,String.format("modbus_%d", 1)); + }); + timerID = vertx.setPeriodic(modbusConfig.getInterval(), t -> { + readDataTask(); + }); + } else { + connectState = 0; + log.info("connect modbus tcp error", result.cause()); + } + }) + .onFailure(e -> { + log.error("modbus connect failed", e); + connectState = 0; + }) + ; + } + + private void readDataTask() { + log.info("readData:" + socket); + if (socket != null) { + ModBusEntity read=new ModBusEntity(); + read.setFunc(ModBusConstants.FUN_CODE3); + read.setDevAddr((byte) 1); + Integer addr=1; + Integer length=1; + String a1= StringUtils.leftPad(addr.toHexString(addr),4,'0')+StringUtils.leftPad(addr.toHexString(length),4,'0'); + read.setData(ByteUtils.hexStrToBinaryStr(a1)); + byte[] msg = analysis.packCmd4Entity(read); + sendMsg(msg); + } + } + + @Override + public void stop() throws Exception { + if (netClient != null) { + netClient.close(); + } + vertx.cancelTimer(timerID); + connectState = 0; + super.stop(); + } + + private byte[] getData(byte[] data) { + int lenth=data[0]; + byte[] val = new byte[lenth]; + System.arraycopy(data, 1, val, 0, lenth); + return val; + } + + private void stateChange(DeviceState state,String deviceName) { + thingService.post(pluginInfo.getPluginId(), + DeviceStateChange.builder() + .id(IdUtil.simpleUUID()) + .state(state).productKey("PYWH4r8xBzsfn3XB").deviceName(deviceName) + .time(System.currentTimeMillis()) + .build()); + } + + public void sendMsg(byte[] msg) { + log.info("modbus send msg data:{}", ByteUtils.BinaryToHexString(msg,false)); + Buffer data = Buffer.buffer(msg); + socket.write(data, r -> { + if (r.succeeded()) { + log.info("modbus msg send success:{}", ByteUtils.BinaryToHexString(msg,false)); + } else { + log.error("modbus msg send failed", r.cause()); + } + }); + } +} diff --git a/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/utils/ByteUtils.java b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/utils/ByteUtils.java new file mode 100644 index 0000000..f66a7e1 --- /dev/null +++ b/hydrovalve-plugin/src/main/java/cc/iotkit/plugins/hydrovalve/utils/ByteUtils.java @@ -0,0 +1,58 @@ +package cc.iotkit.plugins.hydrovalve.utils; + +/** + * @Author:tfd + * @Date:2024/1/8 15:15 + */ +public class ByteUtils { + /** + * 将十六进制的字符串转换成字节数组 + * + * @param hexString + * @return + */ + public static byte[] hexStrToBinaryStr(String hexString) { + + if (hexString==null) { + return null; + } + try { + hexString = hexString.replaceAll(" ", ""); + int len = hexString.length(); + int index = 0; + byte[] bytes = new byte[len / 2]; + while (index < len) { + String sub = hexString.substring(index, index + 2); + bytes[index/2] = (byte)Integer.parseInt(sub,16); + index += 2; + } + return bytes; + }catch (Exception e){ + return null; + } + + } + + /** + * 将字节数组转换成十六进制的字符串 + * + * @return + */ + public static String BinaryToHexString(byte[] bytes,boolean isBalank) { + String hexStr = "0123456789ABCDEF"; + String result = ""; + String hex = ""; + Boolean feStart=true; + for (byte b : bytes) { + hex = String.valueOf(hexStr.charAt((b & 0xF0) >> 4)); + hex += String.valueOf(hexStr.charAt(b & 0x0F)); + if("FE".equals(hex) && feStart){ + continue; + }else { + feStart=false; + } + result += hex + (isBalank?" ":""); + } + return result; + } +} diff --git a/hydrovalve-plugin/src/main/resources/application.yml b/hydrovalve-plugin/src/main/resources/application.yml new file mode 100644 index 0000000..9ff6468 --- /dev/null +++ b/hydrovalve-plugin/src/main/resources/application.yml @@ -0,0 +1,8 @@ +plugin: + runMode: prod + mainPackage: cc.iotkit.plugin + +hydrovalve: + host: 25on621889.goho.co + port: 38807 + interval: 20000 diff --git a/hydrovalve-plugin/src/main/resources/config.json b/hydrovalve-plugin/src/main/resources/config.json new file mode 100644 index 0000000..22abf0c --- /dev/null +++ b/hydrovalve-plugin/src/main/resources/config.json @@ -0,0 +1,23 @@ +[ + { + "id": "host", + "name": "服务端ip", + "type": "text", + "value": "25on621889.goho.co", + "desc": "服务端ip" + }, + { + "id": "port", + "name": "服务端端口", + "type": "number", + "value": 38807, + "desc": "服务端端口" + }, + { + "id": "interval", + "name": "采集频率", + "type": "number", + "value": 20000, + "desc": "采集频率" + } +] \ No newline at end of file diff --git a/pom.xml b/pom.xml index 85f7ca8..ce0eaf9 100755 --- a/pom.xml +++ b/pom.xml @@ -9,6 +9,7 @@ modbus-plugin tcp-plugin DLT645-plugin + hydrovalve-plugin