1、电表和modbus设备控制完成

master
tangfudong 2024-01-10 14:48:34 +08:00
parent 5fed47a2c8
commit 94b39b4f6d
21 changed files with 969 additions and 11 deletions

View File

@ -16,4 +16,6 @@ public class TcpClientConfig {
private String host;
private int port;
private int interval;
}

View File

@ -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<String, Object> 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;
}

View File

@ -3,5 +3,6 @@ plugin:
mainPackage: cc.iotkit.plugin
tcp:
host: xxx
host: 25on621889.goho.co
port: 43161
interval: 10000

View File

@ -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": "采集频率"
}
]

64
hydrovalve-plugin/pom.xml Normal file
View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>iot-iita-plugins</artifactId>
<groupId>cc.iotkit.plugins</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>hydrovalve-plugin</artifactId>
<profiles>
<profile>
<id>dev</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<plugin.build.mode>dev</plugin.build.mode>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<plugin.build.mode>prod</plugin.build.mode>
</properties>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>com.gitee.starblues</groupId>
<artifactId>spring-brick-maven-packager</artifactId>
<version>${spring-brick-package.version}</version>
<configuration>
<mode>${plugin.build.mode}</mode>
<pluginInfo>
<id>hydrovalve-plugin</id>
<bootstrapClass>cc.iotkit.plugins.hydrovalve.Application</bootstrapClass>
<version>${project.version}</version>
<provider>iita</provider>
<description>modbus插件</description>
<configFileName>application.yml</configFileName>
</pluginInfo>
<prodConfig>
<packageType>jar</packageType>
</prodConfig>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -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;
/**
* @Authortfd
* @Date2024/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);
}
}

View File

@ -0,0 +1,23 @@
package cc.iotkit.plugins.hydrovalve.analysis;
/**
* @Authortfd
* @Date2024/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);
}

View File

@ -0,0 +1,34 @@
package cc.iotkit.plugins.hydrovalve.analysis;
/**
* @Authortfd
* @Date2024/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;
}

View File

@ -0,0 +1,42 @@
package cc.iotkit.plugins.hydrovalve.analysis;
import lombok.Getter;
import lombok.Setter;
/**
* @Authortfd
* @Date2024/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 = "";
}

View File

@ -0,0 +1,50 @@
package cc.iotkit.plugins.hydrovalve.analysis;
/**
* @Authortfd
* @Date2024/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 "";
}
}

View File

@ -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;
/**
* @Authortfd
* @Date2024/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));
}
}

View File

@ -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;
/**
* @Authortfd
* @Date2024/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();
}
}

View File

@ -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;
/**
* @Authortfd
* @Date2024/1/8 15:04
*/
@Data
@Component
@ConfigurationProperties(prefix = "hydrovalve")
public class HydrovalveConfig {
private String host;
private int port;
private int interval;
}

View File

@ -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;
/**
* @Authortfd
* @Date2024/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<String, ?> getProperty(String dn) {
return new HashMap<>(0);
}
}

View File

@ -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;
/**
* @Authortfd
* @Date2024/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<String, ?> 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;
}
}

View File

@ -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;
/**
* @Authortfd
* @Date2024/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<String, Object> config = pluginConfig.getConfig(pluginInfo.getPluginId());
BeanUtil.copyProperties(config, modbusConfig, CopyOptions.create().ignoreNullValue());
modbusVerticle.setModbusConfig(modbusConfig);
Future<String> 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<Void> 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);
}
}
}

View File

@ -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;
/**
* @Authortfd
* @Date2024/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<String, Object> 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());
}
});
}
}

View File

@ -0,0 +1,58 @@
package cc.iotkit.plugins.hydrovalve.utils;
/**
* @Authortfd
* @Date2024/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;
}
}

View File

@ -0,0 +1,8 @@
plugin:
runMode: prod
mainPackage: cc.iotkit.plugin
hydrovalve:
host: 25on621889.goho.co
port: 38807
interval: 20000

View File

@ -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": "采集频率"
}
]

View File

@ -9,6 +9,7 @@
<module>modbus-plugin</module>
<module>tcp-plugin</module>
<module>DLT645-plugin</module>
<module>hydrovalve-plugin</module>
</modules>
<parent>