作者 钟来

plc终端功能

正在显示 68 个修改的文件 包含 3977 行增加59 行删除
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>lh-jar</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>lh-device-mqtt-terminal-jar</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!-- mqtt -->
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
... ...
package com.zhonglai.luhui.device.mqtt.terminal.jar;
import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
/**
* 数据解析工厂
*/
public interface ParseDataFactory {
void parse( Topic topicDto,byte[] paylaod, MqttService mqttService);
}
... ...
package com.zhonglai.luhui.device.mqtt.terminal.jar.config;
import java.io.FileReader;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
public class MqttConfig {
public static String mqttBroker;
public static String mqttUserName;
public static String mqttPassword;
public static String mqttClientId;
public static Set<String> subTopic;
public static void init(String path)
{
//加载properties配置文件
Properties properties = loadProperties(path);
mqttBroker = properties.getProperty("mqtt.broker");
mqttUserName = properties.getProperty("mqtt.username");
mqttPassword = properties.getProperty("mqtt.password");
mqttClientId = properties.getProperty("mqtt.clientId");
subTopic = new HashSet<>();
String topicsStr = properties.getProperty("mqtt.subTopic");
if(null != topicsStr && !"".equals(topicsStr))
{
if (topicsStr != null && !topicsStr.trim().isEmpty()) {
String[] topics = topicsStr.split(",");
for (String topic : topics) {
subTopic.add(topic.trim());
}
}
}
}
private static Properties loadProperties(String path)
{
Properties properties = new Properties();
try {
if(null != path && !"".equals(path))
{
properties.load(new FileReader(path+"/mqtt.properties"));
}else{
properties.load(MqttConfig.class.getClassLoader().getResourceAsStream("configs/mqtt.properties"));
}
} catch (Exception e) {
throw new RuntimeException("加载mqtt.properties失败,未找到配置文件或内容为空");
}
return properties;
}
}
... ...
package com.zhonglai.luhui.device.mqtt.terminal.jar.dto;
public class Topic {
private String topicType;
private String time;
public Topic()
{
}
public Topic(String stopicStr)
{
String[] strs = stopicStr.split("/");
if (strs.length >= 2) {
topicType = strs[0];
time = strs[1];
} else {
topicType = stopicStr; // 或者给默认值
time = "";
}
}
public String getTopicType() {
return topicType;
}
public void setTopicType(String topicType) {
this.topicType = topicType;
}
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
}
... ...
package com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt;
import com.alibaba.fastjson.JSONObject;
import com.zhonglai.luhui.device.mqtt.terminal.jar.ParseDataFactory;
import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Set;
/**
* mqtt回调
*/
public class MqttCallback implements MqttCallbackExtended {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private final MqttService mqttclient;
private final ParseDataFactory parseDataFactory;
public MqttCallback(MqttService mqttService, ParseDataFactory parseDataFactory) {
this.mqttclient = mqttService;
this.parseDataFactory = parseDataFactory;
}
@Override
public void connectComplete(boolean b, String s) {
log.info("连接成功");
Set<String> topics = mqttclient.subscribe();
log.info("-----------订阅成功:{}--------------------",topics);
}
@Override
public void connectionLost(Throwable cause) {
log.error("连接丢失重新链接",cause);
while (!mqttclient.connect())
{
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
log.info("mqtt发来消息:topic={} payload={}", topic, message.toString());
Topic topicDto = new Topic(topic);
parseDataFactory.parse(topicDto, message.getPayload(),mqttclient);
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
log.info("消息发送完成");
}
}
... ...
package com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt;
import com.zhonglai.luhui.device.mqtt.terminal.jar.ParseDataFactory;
import com.zhonglai.luhui.device.mqtt.terminal.jar.config.MqttConfig;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PreDestroy;
import java.util.Set;
public class MqttService {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private MqttClient mqttclient;
private MqttConnectOptions options;
// 使用 volatile 确保多线程内存可见性和禁止指令重排
private static volatile MqttService instance;
private ParseDataFactory parseDataFactory;
private MqttService() {
}
public static MqttService getInstance() {
if (instance == null) {
synchronized (MqttService.class) {
if (instance == null) {
instance = new MqttService();
}
}
}
return instance;
}
private void init() throws MqttException {
if(null == mqttclient)
{
mqttclient = new MqttClient(MqttConfig.mqttBroker, MqttConfig.mqttClientId, new MemoryPersistence());
}
options = new MqttConnectOptions();
options.setCleanSession(true);
options.setConnectionTimeout(15);
options.setKeepAliveInterval(60); // 添加心跳设置,单位为秒
//设置断开后重新连接
options.setAutomaticReconnect(true);
mqttclient.setCallback(new MqttCallback(this,parseDataFactory));
}
public boolean connect() {
try {
options.setUserName(MqttConfig.mqttUserName);
options.setPassword(MqttConfig.mqttPassword.toCharArray());
mqttclient.connect(options);
return true;
} catch (MqttException e) {
log.error("-----------mqtt连接服务器失败--------------------",e);
}
return false;
}
public void start(ParseDataFactory factory) throws MqttException{
this.parseDataFactory = factory;
log.info("-----------开始启动mqtt监听服务--------------------");
init();
connect();
log.info("-----------mqtt连接服务器成功--------------------");
}
@PreDestroy
public void stop() throws MqttException {
if(null != mqttclient)
{
log.info("退出mqtt服务");
mqttclient.unsubscribe(MqttConfig.subTopic.toArray(new String[MqttConfig.subTopic.size()]));
mqttclient.disconnect();
mqttclient.close();
}
instance = null;
}
public void publish(String topic, String messageStr) throws MqttException {
MqttMessage message = new MqttMessage();
message.setPayload(messageStr.getBytes());
mqttclient.publish(topic,message);
}
public Set<String> subscribe()
{
try {
mqttclient.subscribe(MqttConfig.subTopic.toArray(new String[MqttConfig.subTopic.size()]));
} catch (MqttException e) {
e.printStackTrace();
}
return MqttConfig.subTopic;
}
public boolean isConnect()
{
return mqttclient != null && mqttclient.isConnected();
}
}
... ...
... ... @@ -7,6 +7,10 @@ import com.ruoyi.common.utils.StringUtils;
import com.zhonglai.luhui.device.analysis.comm.dto.thingsmodels.specs.*;
import com.zhonglai.luhui.device.domain.IotThingsModel;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.function.BiConsumer;
/**
* 物模型工厂的公用方法
... ... @@ -15,6 +19,8 @@ import lombok.Data;
@Data
public abstract class ThingsModelItemBase<T> implements ThingsModelBase<T>
{
private static final Logger log = LoggerFactory.getLogger(ThingsModelItemBase.class);
/**
* 下位机端数据转换物模型
* @param thingsModelDataTypeEnum
... ... @@ -24,14 +30,7 @@ public abstract class ThingsModelItemBase<T> implements ThingsModelBase<T>
*/
public static ThingsModelItemBase newhingsModel(ThingsModelDataTypeEnum thingsModelDataTypeEnum,IotThingsModel thingsModel,JsonElement jsonElement)
{
if (!jsonElement.isJsonNull())
{
ThingsModelItemBase thingsModelItemBase = createThingsModelItemBase(thingsModelDataTypeEnum,thingsModel,jsonElement);
thingsModelItemBase.conversionThingsModel(thingsModel);
thingsModelItemBase.setSaveView(jsonElement.getAsString());
return thingsModelItemBase;
}
return null;
return safeNewhingsModel(thingsModelDataTypeEnum, thingsModel, jsonElement, ThingsModelItemBase::setSaveView);
}
/**
... ... @@ -43,12 +42,31 @@ public abstract class ThingsModelItemBase<T> implements ThingsModelBase<T>
*/
public static ThingsModelItemBase newhingsModelReverse(ThingsModelDataTypeEnum thingsModelDataTypeEnum,IotThingsModel thingsModel,JsonElement jsonElement)
{
if (!jsonElement.isJsonNull())
{
ThingsModelItemBase thingsModelItemBase = createThingsModelItemBase(thingsModelDataTypeEnum,thingsModel,jsonElement);
thingsModelItemBase.conversionThingsModel(thingsModel);
thingsModelItemBase.reverse(jsonElement.getAsString());
return thingsModelItemBase;
return safeNewhingsModel(thingsModelDataTypeEnum, thingsModel, jsonElement, ThingsModelItemBase::reverse);
}
private static ThingsModelItemBase safeNewhingsModel(
ThingsModelDataTypeEnum type,
IotThingsModel model,
JsonElement element,
BiConsumer<ThingsModelItemBase, String> consumer) {
if (element != null && !element.isJsonNull()) {
String rawValue = element.getAsString();
ThingsModelItemBase item = createThingsModelItemBase(type, model, element);
item.conversionThingsModel(model);
if (rawValue != null && !rawValue.trim().isEmpty()) {
try {
consumer.accept(item, rawValue);
} catch (Exception e) {
log.error("字段 [{}] 处理失败,value={}", model.getModel_name(), rawValue, e);
consumer.accept(item, "0"); // 默认值
}
} else {
consumer.accept(item, "0");
}
return item;
}
return null;
}
... ...
... ... @@ -4,6 +4,7 @@ public enum CommandType {
read,
write,
notice,
host,
cleanDeviceHost,
cleanDeviceInfo,
upIotThingsModel,
... ...
... ... @@ -20,6 +20,7 @@
<module>lh-jar-order-service</module>
<module>lh-jar-plugins-init</module>
<module>lh-jar-ssh-proxy</module>
<module>lh-device-mqtt-terminal-jar</module>
</modules>
<properties>
... ...
... ... @@ -8,6 +8,7 @@ import com.ruoyi.common.core.domain.Message;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.GsonConstructor;
import com.zhonglai.luhui.action.BaseController;
import com.zhonglai.luhui.api.service.RocketMqSendService;
import com.zhonglai.luhui.device.domain.IotDevice;
import com.zhonglai.luhui.device.dto.CommandType;
import com.zhonglai.luhui.device.dto.DeviceCommand;
... ... @@ -32,11 +33,9 @@ import java.io.IOException;
@RestController
@RequestMapping("/iot/controlDevice")
public class ControlDeviceConreoller extends BaseController {
@Autowired
private RocketMqService rocketMqService;
@Autowired
private IIotDeviceService iotDeviceService;
private RocketMqSendService rocketMqSendService;
@ApiOperation(value = "写指令",notes = "body参数描述:\r\n" +
"{\n" +
" \"0\":{\n" +
... ... @@ -71,7 +70,7 @@ public class ControlDeviceConreoller extends BaseController {
deviceCommand.setCommandType(CommandType.write);
deviceCommand.setData(GsonConstructor.get().fromJson(body,JsonObject.class));
return deviceControl(deviceCommand);
return rocketMqSendService.deviceControl(deviceCommand);
}
@ApiOperation(value = "读指令",notes = "body参数描述:\r\n" +
... ... @@ -108,7 +107,7 @@ public class ControlDeviceConreoller extends BaseController {
deviceCommand.setDeviceId(deviceId);
deviceCommand.setCommandType(CommandType.write);
deviceCommand.setData(GsonConstructor.get().fromJson(body,JsonObject.class));
return deviceControl(deviceCommand);
return rocketMqSendService.deviceControl(deviceCommand);
}
@ApiOperation("更新缓存模型")
... ... @@ -121,7 +120,7 @@ public class ControlDeviceConreoller extends BaseController {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("product_id",product_id);
deviceCommand.setData(jsonObject);
return sysControl(deviceCommand);
return rocketMqSendService.sysControl(deviceCommand);
}
@ApiOperation("更新缓存翻译模型")
... ... @@ -134,7 +133,7 @@ public class ControlDeviceConreoller extends BaseController {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("product_id",product_id);
deviceCommand.setData(jsonObject);
return sysControl(deviceCommand);
return rocketMqSendService.sysControl(deviceCommand);
}
@ApiOperation("清除网关缓存")
... ... @@ -145,7 +144,7 @@ public class ControlDeviceConreoller extends BaseController {
DeviceCommand deviceCommand = new DeviceCommand();
deviceCommand.setDeviceId(deviceId);
deviceCommand.setCommandType(CommandType.cleanDeviceHost);
return sysControl(deviceCommand);
return rocketMqSendService.sysControl(deviceCommand);
}
@ApiOperation("更新终端缓存")
... ... @@ -162,7 +161,7 @@ public class ControlDeviceConreoller extends BaseController {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("sensor_number",sensor_number);
deviceCommand.setData(jsonObject);
return sysControl(deviceCommand);
return rocketMqSendService.sysControl(deviceCommand);
}
@ApiOperation("添加订阅")
... ... @@ -179,7 +178,7 @@ public class ControlDeviceConreoller extends BaseController {
jsonObject.addProperty("product_ids",product_ids);
jsonObject.addProperty("ip",ip);
deviceCommand.setData(jsonObject);
return sysControl(deviceCommand);
return rocketMqSendService.sysControl(deviceCommand);
}
@ApiOperation(value = "通知",notes = "body参数描述:\r\n" +
... ... @@ -202,32 +201,8 @@ public class ControlDeviceConreoller extends BaseController {
deviceCommand.setDeviceId(deviceId);
deviceCommand.setCommandType(CommandType.notice);
deviceCommand.setData(GsonConstructor.get().fromJson(body,JsonObject.class));
return deviceControl(deviceCommand);
return rocketMqSendService.deviceControl(deviceCommand);
}
private AjaxResult deviceControl(DeviceCommand deviceCommand)
{
IotDevice iotDevice = iotDeviceService.selectIotDeviceByClient_id(deviceCommand.getDeviceId());
if(null == iotDevice || iotDevice.getStatus()!=3)
{
return AjaxResult.error("设备不存在或者不在线");
}
Message message = rocketMqService.send("deviceCommandListen",GsonConstructor.get().toJson(deviceCommand).getBytes(),iotDevice.getOperation_token());
if(message.getCode()==1)
{
return new AjaxResult(HttpStatus.SUCCESS, message.getMessage(), message.getData());
}
return new AjaxResult(HttpStatus.ERROR, message.getMessage(), message.getData());
}
private AjaxResult sysControl(DeviceCommand deviceCommand)
{
Message message = rocketMqService.send("deviceCommandListen",GsonConstructor.get().toJson(deviceCommand).getBytes(),"SysCommand");
if(message.getCode()==1)
{
return new AjaxResult(HttpStatus.SUCCESS, message.getMessage(), message.getData());
}
return new AjaxResult(HttpStatus.ERROR, message.getMessage(), message.getData());
}
}
... ...
package com.zhonglai.luhui.api.controller.iot;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.JsonObject;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.GsonConstructor;
import com.zhonglai.luhui.action.BaseController;
import com.zhonglai.luhui.api.controller.iot.dto.HostCommand;
import com.zhonglai.luhui.api.controller.iot.dto.HostCommandFunction;
import com.zhonglai.luhui.api.controller.iot.dto.camera.StartStream;
import com.zhonglai.luhui.api.controller.iot.dto.camera.StartStreamBySerial;
import com.zhonglai.luhui.api.controller.iot.dto.camera.StopStream;
import com.zhonglai.luhui.api.service.RocketMqSendService;
import com.zhonglai.luhui.device.dto.CommandType;
import com.zhonglai.luhui.device.dto.DeviceCommand;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
@Api(tags = "工控机控制器")
@RestController
@RequestMapping("/iot/controlDevice")
public class ControlGkjController extends BaseController {
@Autowired
private RocketMqSendService rocketMqSendService;
@ApiOperation(value = "ls")
@ApiImplicitParams({
@ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
})
@GetMapping("/ls/{deviceId}")
public AjaxResult write(@PathVariable String deviceId) throws IOException {
HostCommand hostCommand = new HostCommand();
hostCommand.setFunction("ls");
DeviceCommand deviceCommand = new DeviceCommand();
deviceCommand.setDeviceId(deviceId);
deviceCommand.setCommandType(CommandType.host);
deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
return rocketMqSendService.deviceControl(deviceCommand);
}
@ApiOperation(value = "cd")
@ApiImplicitParams({
@ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "path", value = "路径", required = true, dataType = "String", paramType = "path"),
})
@GetMapping("/cd/{deviceId}")
public AjaxResult cd(@PathVariable String deviceId,String path) {
HostCommand hostCommand = new HostCommand();
hostCommand.setFunction("cd");
JSONObject data = new JSONObject();
data.put("path", path);
hostCommand.setData(data);
DeviceCommand deviceCommand = new DeviceCommand();
deviceCommand.setDeviceId(deviceId);
deviceCommand.setCommandType(CommandType.host);
deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
return rocketMqSendService.deviceControl(deviceCommand);
}
@ApiOperation(value = "mkdir")
@ApiImplicitParams({
@ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "name", value = "名称", required = true, dataType = "String", paramType = "path"),
})
@GetMapping("/mkdir/{deviceId}")
public AjaxResult mkdir(@PathVariable String deviceId,String name) {
HostCommand hostCommand = new HostCommand();
hostCommand.setFunction("mkdir");
JSONObject data = new JSONObject();
data.put("name", name);
hostCommand.setData(data);
DeviceCommand deviceCommand = new DeviceCommand();
deviceCommand.setDeviceId(deviceId);
deviceCommand.setCommandType(CommandType.host);
deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
return rocketMqSendService.deviceControl(deviceCommand);
}
@ApiOperation(value = "rm")
@ApiImplicitParams({
@ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "name", value = "名称", required = true, dataType = "String", paramType = "path"),
})
@GetMapping("/rm/{deviceId}")
public AjaxResult rm(@PathVariable String deviceId,String name) {
HostCommand hostCommand = new HostCommand();
hostCommand.setFunction("rm");
JSONObject data = new JSONObject();
data.put("name", name);
hostCommand.setData(data);
DeviceCommand deviceCommand = new DeviceCommand();
deviceCommand.setDeviceId(deviceId);
deviceCommand.setCommandType(CommandType.host);
deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
return rocketMqSendService.deviceControl(deviceCommand);
}
@ApiOperation(value = "copy")
@ApiImplicitParams({
@ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "source", value = "源", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "target", value = "目标", required = true, dataType = "String", paramType = "path"),
})
@GetMapping("/copy/{deviceId}")
public AjaxResult copy(@PathVariable String deviceId,String source,String target) {
HostCommand hostCommand = new HostCommand();
hostCommand.setFunction("copy");
JSONObject data = new JSONObject();
data.put("source", source);
data.put("target", target);
hostCommand.setData(data);
DeviceCommand deviceCommand = new DeviceCommand();
deviceCommand.setDeviceId(deviceId);
deviceCommand.setCommandType(CommandType.host);
deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
return rocketMqSendService.deviceControl(deviceCommand);
}
@ApiOperation(value = "mk")
@ApiImplicitParams({
@ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "name", value = "名称", required = true, dataType = "String", paramType = "path"),
})
@GetMapping("/mk/{deviceId}")
public AjaxResult mk(@PathVariable String deviceId,String name) {
HostCommand hostCommand = new HostCommand();
hostCommand.setFunction("mk");
JSONObject data = new JSONObject();
data.put("name", name);
hostCommand.setData(data);
DeviceCommand deviceCommand = new DeviceCommand();
deviceCommand.setDeviceId(deviceId);
deviceCommand.setCommandType(CommandType.host);
deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
return rocketMqSendService.deviceControl(deviceCommand);
}
@ApiOperation(value = "download")
@ApiImplicitParams({
@ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "url", value = "url", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "name", value = "名称", required = true, dataType = "String", paramType = "path"),
})
@GetMapping("/download/{deviceId}")
public AjaxResult download(@PathVariable String deviceId,String url,String name) {
HostCommand hostCommand = new HostCommand();
hostCommand.setFunction("download");
JSONObject data = new JSONObject();
data.put("name", name);
data.put("url", url);
hostCommand.setData(data);
DeviceCommand deviceCommand = new DeviceCommand();
deviceCommand.setDeviceId(deviceId);
deviceCommand.setCommandType(CommandType.host);
deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
return rocketMqSendService.deviceControl(deviceCommand);
}
@ApiOperation(value = "upload")
@ApiImplicitParams({
@ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "url", value = "url", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "name", value = "名称", required = true, dataType = "String", paramType = "path"),
})
@GetMapping("/upload/{deviceId}")
public AjaxResult upload(@PathVariable String deviceId,String url,String name) {
HostCommand hostCommand = new HostCommand();
hostCommand.setFunction("upload");
JSONObject data = new JSONObject();
data.put("name", name);
data.put("url", url);
hostCommand.setData(data);
DeviceCommand deviceCommand = new DeviceCommand();
deviceCommand.setDeviceId(deviceId);
deviceCommand.setCommandType(CommandType.host);
deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
return rocketMqSendService.deviceControl(deviceCommand);
}
@ApiOperation(value = "摄像头拉流")
@ApiImplicitParams({
@ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
})
@GetMapping("/cameraStartStream/{deviceId}")
public AjaxResult camera(@PathVariable String deviceId, StartStream startStream) {
HostCommand hostCommand = new HostCommand();
hostCommand.setFunction("camera");
JSONObject data = new JSONObject();
data.put("commd", "startStream");
data.put("param", GsonConstructor.get().toJson(startStream));
hostCommand.setData(data);
DeviceCommand deviceCommand = new DeviceCommand();
deviceCommand.setDeviceId(deviceId);
deviceCommand.setCommandType(CommandType.host);
deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
return rocketMqSendService.deviceControl(deviceCommand);
}
@ApiOperation(value = "摄像头根据序列号拉流")
@ApiImplicitParams({
@ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
})
@GetMapping("/cameraStartStreamBySerial/{deviceId}")
public AjaxResult cameraStartStreamBySerial(@PathVariable String deviceId, StartStreamBySerial startStreamBySerial) {
HostCommand hostCommand = new HostCommand();
hostCommand.setFunction("camera");
JSONObject data = new JSONObject();
data.put("commd", "startStreamBySerial");
data.put("param", GsonConstructor.get().toJson(startStreamBySerial));
hostCommand.setData(data);
DeviceCommand deviceCommand = new DeviceCommand();
deviceCommand.setDeviceId(deviceId);
deviceCommand.setCommandType(CommandType.host);
deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
return rocketMqSendService.deviceControl(deviceCommand);
}
@ApiOperation(value = "摄像头关流")
@ApiImplicitParams({
@ApiImplicitParam(name = "deviceId", value = "设备ID", required = true, dataType = "String", paramType = "path"),
})
@GetMapping("/cameraStopStream/{deviceId}")
public AjaxResult cameraStopStream(@PathVariable String deviceId, StopStream stopStream) {
HostCommand hostCommand = new HostCommand();
hostCommand.setFunction("camera");
JSONObject data = new JSONObject();
data.put("commd", "stopStream");
data.put("param", GsonConstructor.get().toJson(stopStream));
hostCommand.setData(data);
DeviceCommand deviceCommand = new DeviceCommand();
deviceCommand.setDeviceId(deviceId);
deviceCommand.setCommandType(CommandType.host);
deviceCommand.setData(GsonConstructor.get().fromJson(GsonConstructor.get().toJson(hostCommand), JsonObject.class));
return rocketMqSendService.deviceControl(deviceCommand);
}
}
... ...
package com.zhonglai.luhui.api.controller.iot.dto;
import com.alibaba.fastjson.JSONObject;
public class HostCommand {
private String function;
private JSONObject data;
public String getFunction() {
return function;
}
public void setFunction(String function) {
this.function = function;
}
public JSONObject getData() {
return data;
}
public void setData(JSONObject data) {
this.data = data;
}
}
... ...
package com.zhonglai.luhui.api.controller.iot.dto;
public enum HostCommandFunction {
startStream,
stopStream,
startStreamBySerial,
getLocalIpAddress
}
... ...
package com.zhonglai.luhui.api.controller.iot.dto.camera;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel("摄像头启动指令")
public class StartStream {
@ApiModelProperty("流id,推荐为摄像头的序列号")
private String stream;
@ApiModelProperty("摄像头的rtsp地址")
private String rtspUrl;
@ApiModelProperty("无人观看自动关闭流")
private String auto_close;
public String getStream() {
return stream;
}
public void setStream(String stream) {
this.stream = stream;
}
public String getRtspUrl() {
return rtspUrl;
}
public void setRtspUrl(String rtspUrl) {
this.rtspUrl = rtspUrl;
}
public String getAuto_close() {
return auto_close;
}
public void setAuto_close(String auto_close) {
this.auto_close = auto_close;
}
}
... ...
package com.zhonglai.luhui.api.controller.iot.dto.camera;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel("摄像头根据序列号启动指令")
public class StartStreamBySerial {
@ApiModelProperty("摄像头序列号")
private String deviceSerial;
@ApiModelProperty("无人观看自动关闭流")
private String auto_close;
public String getDeviceSerial() {
return deviceSerial;
}
public void setDeviceSerial(String deviceSerial) {
this.deviceSerial = deviceSerial;
}
public String getAuto_close() {
return auto_close;
}
public void setAuto_close(String auto_close) {
this.auto_close = auto_close;
}
}
... ...
package com.zhonglai.luhui.api.controller.iot.dto.camera;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ApiModel("摄像头关闭指令")
public class StopStream {
@ApiModelProperty("流id,推荐为摄像头的序列号")
private String stream;
public String getStream() {
return stream;
}
public void setStream(String stream) {
this.stream = stream;
}
}
... ...
package com.zhonglai.luhui.api.service;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.Message;
import com.ruoyi.common.utils.GsonConstructor;
import com.zhonglai.luhui.device.domain.IotDevice;
import com.zhonglai.luhui.device.dto.DeviceCommand;
import com.zhonglai.luhui.device.service.IIotDeviceService;
import com.zhonglai.luhui.rocketmq.service.RocketMqService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class RocketMqSendService {
@Autowired
private RocketMqService rocketMqService;
@Autowired
private IIotDeviceService iotDeviceService;
public AjaxResult deviceControl(DeviceCommand deviceCommand)
{
IotDevice iotDevice = iotDeviceService.selectIotDeviceByClient_id(deviceCommand.getDeviceId());
if(null == iotDevice || iotDevice.getStatus()!=3)
{
return AjaxResult.error("设备不存在或者不在线");
}
Message message = rocketMqService.send("deviceCommandListen", GsonConstructor.get().toJson(deviceCommand).getBytes(),iotDevice.getOperation_token());
if(message.getCode()==1)
{
return new AjaxResult(HttpStatus.SUCCESS, message.getMessage(), message.getData());
}
return new AjaxResult(HttpStatus.ERROR, message.getMessage(), message.getData());
}
public AjaxResult sysControl(DeviceCommand deviceCommand)
{
Message message = rocketMqService.send("deviceCommandListen",GsonConstructor.get().toJson(deviceCommand).getBytes(),"SysCommand");
if(message.getCode()==1)
{
return new AjaxResult(HttpStatus.SUCCESS, message.getMessage(), message.getData());
}
return new AjaxResult(HttpStatus.ERROR, message.getMessage(), message.getData());
}
}
... ...
## 地址类型与范围
| 类型 | 地址范围 (1-based) | 数据类型 | 说明 |
| ------------------------ | -------------- | --------------- | ---------------------- |
| Coil (线圈输出) | 00001 – 09999 | BIT | 可读可写的单个位,通常用于开关量输出 |
| Discrete Input (离散输入) | 10001 – 19999 | BIT | 只读的单个位,通常用于开关量输入 |
| Input Register (输入寄存器) | 30001 – 39999 | 16-bit 或 32-bit | 只读寄存器,通常用于传感器输入或模拟量 |
| Holding Register (保持寄存器) | 40001 – 49999 | 16-bit 或 32-bit | 可读可写寄存器,通常用于控制命令或模拟量输出 |
## 常用功能码(Function Code)
| 功能码 | 功能描述 | 适用地址类型 |
| --------- | ------------------------ | ------------------------------ |
| 01 | Read Coils | Coil (00001–09999) |
| 02 | Read Discrete Inputs | Discrete Input (10001–19999) |
| 03 | Read Holding Registers | Holding Register (40001–49999) |
| 04 | Read Input Registers | Input Register (30001–39999) |
| 05 | Write Single Coil | Coil (00001–09999) |
| 06 | Write Single Register | Holding Register (40001–49999) |
| 15 (0x0F) | Write Multiple Coils | Coil (00001–09999) |
| 16 (0x10) | Write Multiple Registers | Holding Register (40001–49999) |
## 扩展说明
1、BIT 类型地址
Coil、Discrete Input 都是 1 位 (0/1)
Input Register 和 Holding Register 内部也可按 bit 位访问(x.y 格式,例如 40001.0 表示寄存器 40001 的第 0 位)
2、寄存器数据类型映射
| Modbus 寄存器 | BaseLocator 数据类型示例 | 说明 |
| ----------------------- | ------------------------------------------------- | ------------------------ |
| Holding Register 16-bit | TWO\_BYTE\_INT\_SIGNED / TWO\_BYTE\_INT\_UNSIGNED | 标准 16 位整数 |
| Input Register 16-bit | 同上 | 只读 |
| Holding/Input 32-bit | FOUR\_BYTE\_INT\_SIGNED / FOUR\_BYTE\_FLOAT | 两个连续寄存器组成一个 32-bit 整数或浮点 |
| Holding/Input 64-bit | EIGHT\_BYTE\_INT\_SIGNED / EIGHT\_BYTE\_FLOAT | 四个连续寄存器组成 64-bit |
| Holding/Input bit | holdingRegisterBit / inputRegisterBit | 对寄存器内部的某个位操作 |
3、偏移量计算(0-based)
| 地址类型 | 1-based 地址 | 偏移量公式 |
| ---------------- | ----------- | ------------------------ |
| Coil | 00001–09999 | offset = address - 1 |
| Discrete Input | 10001–19999 | offset = address - 10001 |
| Input Register | 30001–39999 | offset = address - 30001 |
| Holding Register | 40001–49999 | offset = address - 40001 |
... ...
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>lh-modules</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>lh-device-modbus-terminal</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.infiniteautomation</groupId>
<artifactId>modbus4j</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
</dependency>
<!-- 阿里JSON解析器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!-- mqtt终端插件 -->
<dependency>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>lh-device-mqtt-terminal-jar</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.ini4j</groupId>
<artifactId>ini4j</artifactId>
</dependency>
</dependencies>
<build>
<finalName>lh-device-modbus-terminal</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<!--
生成的jar中,不要包含pom.xml和pom.properties这两个文件
-->
<addMavenDescriptor>false</addMavenDescriptor>
<manifest>
<!--
是否要把第三方jar放到manifest的classpath中
-->
<addClasspath>true</addClasspath>
<!--
生成的manifest中classpath的前缀,因为要把第三方jar放到lib目录下,所以classpath的前缀是lib/
-->
<classpathPrefix>lib/</classpathPrefix>
<mainClass>com.zhonglai.luhui.device.modbus.terminal.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<!-- The configuration of maven-assembly-plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4</version>
<configuration>
<descriptors>
<descriptor>${project.parent.parent.basedir}/configs/package.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
\ No newline at end of file
... ...
package com.zhonglai.luhui.device.modbus.terminal;
import com.zhonglai.luhui.device.modbus.terminal.config.CameraConfig;
import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig;
import com.zhonglai.luhui.device.modbus.terminal.data.ParseDataService;
import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jWrite;
import com.zhonglai.luhui.device.modbus.terminal.modbus.ModbusMasterMessage;
import com.zhonglai.luhui.device.modbus.terminal.task.CollectPlcDataTask;
import com.zhonglai.luhui.device.modbus.terminal.task.ScheduledThreadPool;
import com.zhonglai.luhui.device.mqtt.terminal.jar.config.MqttConfig;
import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class Main {
private static Log log = LogFactory.getLog(Modbus4jWrite.class);
public static void main(String[] args) throws Exception {
String configPath = null;
if (args.length!=0)
{
configPath = args[0];
}
//启动mqtt服务
MqttConfig.init(configPath); // 先加载配置
MqttService mqttService = MqttService.getInstance();
mqttService.start(new ParseDataService());
//加载modbus配置
String jsonPath = Main.class.getClassLoader().getResource("configs/plcs.json").getPath();;
if (null != configPath)
{
jsonPath = configPath+"/plcs.json";
}
InitPlcConfig.initPlcConfigFromFile(jsonPath);
String camerapath = Main.class.getClassLoader().getResource("configs/camera.properties").getPath();;
if (null != configPath)
{
camerapath = configPath+"/camera.properties";
}
CameraConfig.init(camerapath);
CollectPlcDataTask collectPlcDataTask = new CollectPlcDataTask();
collectPlcDataTask.collect(mqttService);
// 添加 JVM 关闭钩子,保证优雅退出
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
log.info("🛑 应用关闭中,正在释放资源...");
try {
MqttService.getInstance().stop();
ModbusMasterMessage.closeAll();
ScheduledThreadPool.stopScheduler();
log.info("✅ 资源释放完成");
} catch (Exception e) {
e.printStackTrace();
}
}));
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.camera;
import cn.hutool.core.net.NetUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zhonglai.luhui.device.modbus.terminal.config.CameraConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class WebRtcService {
private static Logger logger = LoggerFactory.getLogger(WebRtcService.class);
public static StringBuffer getZlmApi()
{
return new StringBuffer().append("http://").append(CameraConfig.webrtc_host).append("/index/api");
}
public static String getPlayUrl(String ip,String app,String stream)
{
return "https://"+ip+"/index/api/webrtc?app="+app+"&stream="+stream+"&type=play";
}
public static boolean isMediaOnline(String stream,String app,String secret) {
String url = getZlmApi() + "/isMediaOnline?secret="+secret+"&schema=rtsp&vhost=__defaultVhost__&app="+app+"&stream="+stream;
String str = HttpUtil.get(url);
System.out.println(str);
return JSON.parseObject(str).containsKey("online") && JSON.parseObject(str).getBoolean("online");
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.camera.opf;
import com.alibaba.fastjson.JSONObject;
public interface CameraCommdFunction {
public JSONObject execute();
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.camera.opf;
import com.alibaba.fastjson.JSONObject;
/**
* 摄像头操作指令
*/
public class CameraOperationInstructions {
private String commd;
private CameraCommdFunction param;
public String getCommd() {
return commd;
}
public void setCommd(String commd) {
this.commd = commd;
}
public CameraCommdFunction getParam() {
return param;
}
public void setParam(CameraCommdFunction param) {
this.param = param;
}
public static CameraOperationInstructions createCameraOperationInstructions(JSONObject parameter)
{
CameraOperationInstructions cameraOperationInstructions = new CameraOperationInstructions();
cameraOperationInstructions.setCommd(parameter.getString("commd"));
String jsonstr = parameter.getString("param");
switch (cameraOperationInstructions.getCommd())
{
case "startStream":
cameraOperationInstructions.setParam(JSONObject.parseObject(jsonstr, StartStream.class));
break;
case "stopStream":
cameraOperationInstructions.setParam(JSONObject.parseObject(jsonstr, StopStream.class));
break;
case "startStreamBySerial":
cameraOperationInstructions.setParam(JSONObject.parseObject(jsonstr, StartStreamBySerial.class));
break;
case "getLocalIpAddress":
break;
}
return cameraOperationInstructions;
}
public JSONObject execute()
{
return param.execute();
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.camera.opf;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zhonglai.luhui.device.modbus.terminal.camera.WebRtcService;
import com.zhonglai.luhui.device.modbus.terminal.config.CameraConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StartStream implements CameraCommdFunction {
private static Logger logger = LoggerFactory.getLogger(StartStream.class);
private String stream;
private String rtspUrl;
private String auto_close;
public String getStream() {
return stream;
}
public void setStream(String stream) {
this.stream = stream;
}
public String getRtspUrl() {
return rtspUrl;
}
public void setRtspUrl(String rtspUrl) {
this.rtspUrl = rtspUrl;
}
public String getAuto_close() {
return auto_close;
}
public void setAuto_close(String auto_close) {
this.auto_close = auto_close;
}
@Override
public JSONObject execute() {
String[] rtspurls = rtspUrl.split(",");
if(rtspurls.length!=0)
{
String playurls = "";
String localip = CameraConfig.localIp;
for(String rtspurl : rtspurls) {
String newStream = stream;
String playurl = WebRtcService.getPlayUrl(localip, CameraConfig.webrtc_app, newStream);
boolean oline = WebRtcService.isMediaOnline(newStream, CameraConfig.webrtc_app, CameraConfig.webrtcSecret);
if (!oline)
{
StringBuffer stringBuffer = WebRtcService.getZlmApi();
stringBuffer.append("/addStreamProxy?");
stringBuffer.append("secret=");
stringBuffer.append( CameraConfig.webrtcSecret);
stringBuffer.append("&vhost=__defaultVhost__");
stringBuffer.append("&app=");
stringBuffer.append( CameraConfig.webrtc_app);
stringBuffer.append("&stream=");
stringBuffer.append(newStream);
stringBuffer.append("&url=");
stringBuffer.append(rtspurl);
stringBuffer.append("&enable_auto_close=1");
stringBuffer.append("&retry_count=-1&rtp_type=0&timeout_sec=10&enable_hls=false&enable_hls_fmp4=false&enable_mp4=false&enable_rtsp=true&enable_rtmp=false&enable_ts=false&enable_fmp4=true&hls_demand=false&rtsp_demand=false&rtmp_demand=false&ts_demand=false&fmp4_demand=false&enable_audio=true&add_mute_audio=true&mp4_max_second=10&mp4_as_player=false&auto_close="+auto_close);
logger.info("添加流的接口请求:"+stringBuffer.toString());
String str = HttpUtil.get(stringBuffer.toString());
JSONObject jsonObject = JSON.parseObject(str);
if(jsonObject.containsKey("code") && jsonObject.getInteger("code")==0)
{
jsonObject.put("code",1);
if(!"".equals(playurls))
{
playurls += ",";
}
playurls+=playurl;
}else {
jsonObject.put("code",0);
System.out.println(str);
return jsonObject;
}
}else{
if(!"".equals(playurls))
{
playurls += ",";
}
playurls+=playurl;
}
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("code",1);
JSONObject data = new JSONObject();
data.put("playurl",playurls);
data.put("key", CameraConfig.webrtcSecret);
jsonObject.put("data",data);
return jsonObject;
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("code",0);
jsonObject.put("msg","rtspUrl参数错误");
return jsonObject;
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.camera.opf;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zhonglai.luhui.device.modbus.terminal.config.CameraConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class StartStreamBySerial implements CameraCommdFunction {
private static Logger logger = LoggerFactory.getLogger(StartStream.class);
private String deviceSerial;
private String auto_close;
public String getDeviceSerial() {
return deviceSerial;
}
public void setDeviceSerial(String deviceSerial) {
this.deviceSerial = deviceSerial;
}
public String getAuto_close() {
return auto_close;
}
public void setAuto_close(String auto_close) {
this.auto_close = auto_close;
}
@Override
public JSONObject execute() {
String str = HttpUtil.get(CameraConfig.yuerleApiUrl+"?deviceSerial="+deviceSerial);
JSONObject jsonObject = JSON.parseObject(str);
if(jsonObject.containsKey("code") && jsonObject.getInteger("code")==1 && null != jsonObject.get("data"))
{
String enable_rtsp = jsonObject.getString("data");
logger.info("令牌{}获取到设备{}的rtsp地址{}",CameraConfig.webrtcSecret,deviceSerial,enable_rtsp);
StartStream startStream = new StartStream();
startStream.setRtspUrl(enable_rtsp);
startStream.setAuto_close(auto_close);
startStream.setStream(deviceSerial);
return startStream.execute();
}
return jsonObject;
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.camera.opf;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.zhonglai.luhui.device.modbus.terminal.camera.WebRtcService;
import com.zhonglai.luhui.device.modbus.terminal.config.CameraConfig;
public class StopStream implements CameraCommdFunction {
private String stream;
public String getStream() {
return stream;
}
public void setStream(String stream) {
this.stream = stream;
}
@Override
public JSONObject execute() {
String url = WebRtcService.getZlmApi() + "/close_stream?schema=rtsp&vhost=__defaultVhost__&app=" + CameraConfig.webrtc_app + "&stream=" + stream+"&force=1&secret="+ CameraConfig.webrtcSecret;
String str = HttpUtil.get(url);
System.out.println(str);
return JSONObject.parseObject(str);
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.config;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.net.NetUtil;
import com.zhonglai.luhui.device.modbus.terminal.camera.WebRtcService;
import com.zhonglai.luhui.device.mqtt.terminal.jar.config.MqttConfig;
import org.ini4j.Ini;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Properties;
public class CameraConfig {
private static Logger logger = LoggerFactory.getLogger(CameraConfig.class);
public static String yuerleApiUrl;
public static String webrtc_host;
public static String webrtcSecret;
public static String webrtc_app;
public static String localIp;
public static void init(String configPath )
{
Properties properties = loadProperties(configPath);
yuerleApiUrl = properties.getProperty("yuerleApiUrl");
webrtc_app = properties.getProperty("webrtc_app");
webrtc_host = properties.getProperty("webrtc_host");
webrtcSecret = null==properties.getProperty("webrtcSecret")?getWebrtcSecret():properties.getProperty("webrtcSecret");
String ip = getLocalIp();
localIp = null == ip?getLocalIpAddress():ip;
}
public static String getLocalIp()
{
File file = new File("/app/zlmediakit_config/ip.txt");
if (file.exists())
{
String str = FileUtil.readString(file, "utf-8");
String result = str.trim().replaceAll("[\\r\\n]", "");
return result;
}
String localIpEnv = System.getenv("localIp");
if (localIpEnv != null) {
try {
String realIp = InetAddress.getByName(localIpEnv).getHostAddress();
return realIp;
} catch (Exception e) {
logger.error("宿主机 IP 获取失败",e);
}
}
return null;
}
public static String getZLMSecret() {
File file = new File("/app/zlmediakit_config/config.ini");
if (file.exists())
{
try {
// 读取 config.ini 文件
Ini ini = new Ini(file);
// 获取 [api] 段里的 secret 属性
String secret = ini.get("api", "secret");
logger.info("zlmediakit配置文件读取成功:"+secret);
return secret;
} catch (IOException e) {
logger.error("zlmediakit配置文件读取失败",e);
}
}
String[] command = {
"docker", "exec", "zlmediakit",
"grep", "^secret=", "/opt/media/conf/config.ini"
};
logger.info("正在通过指令{}获取操作令牌...",command);
ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.redirectErrorStream(true); // 合并错误流和标准输出流
try {
Process process = processBuilder.start();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
logger.info("执行指令返回的数据:{}",line);
if (line.startsWith("secret=")) {
return line.substring("secret=".length()).trim();
}
}
}
int exitCode = process.waitFor();
if (exitCode != 0) {
logger.info("命令执行失败,退出码:"+exitCode);
}
} catch (Exception e) {
logger.info("拿操作令牌异常,去配置里面拿:",e);
return "keXTvTDSHAxFDpBA0MDAHhxWeVXLQmUq";
}
return null; // 未找到 secret 或执行失败
}
/**
* 获取webrtc的密钥
* @return
*/
public static String getWebrtcSecret() {
if(null == webrtcSecret)
{
refreshWebrtcSecret();
}
return webrtcSecret;
}
/**
* 刷新webrtc的密钥
*/
public static void refreshWebrtcSecret()
{
webrtcSecret = getZLMSecret();
}
private static Properties loadProperties(String path)
{
Properties properties = new Properties();
try {
if(null != path && !"".equals(path))
{
properties.load(new FileReader(path));
}else{
properties.load(MqttConfig.class.getClassLoader().getResourceAsStream("configs/camera.properties"));
}
} catch (Exception e) {
throw new RuntimeException("加载camera.properties失败,未找到配置文件或内容为空");
}
return properties;
}
/**
* 获取本机IP
* @return
*/
public static String getLocalIpAddress() {
if(null != localIp)
{
return localIp;
}
try {
// 构造一个连接外部地址的 socket(这里用 Google 的公共 DNS IP)
try (DatagramSocket socket = new DatagramSocket()) {
socket.connect(InetAddress.getByName("8.8.8.8"), 10002);
InetAddress localAddress = socket.getLocalAddress();
String ip = localAddress.getHostAddress();
if("0.0.0.0".equals(ip))
{
return null;
}
return localIp=ip;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.config;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.CachPlcConfig;
import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcSystem;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* plc配置
*/
public class InitPlcConfig {
private static Map<Integer, CachPlcConfig> plcsConfigMap = new HashMap<>();
/**
* 从文件初始化plc点位配置
* @param jsonPath
* @throws IOException
*/
public static void initPlcConfigFromFile(String jsonPath) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Map<String, List<PlcSystem>> plcsMap = mapper.readValue(new File(jsonPath),
new TypeReference<Map<String, List<PlcSystem>>>() {});
List<PlcSystem> plcSystems = plcsMap.get("plcs");
for (PlcSystem plc : plcSystems)
{
CachPlcConfig cachPlcConfig = plcsConfigMap.get(plc.getId());
if (cachPlcConfig == null)
{
cachPlcConfig = new CachPlcConfig();
cachPlcConfig.setId(plc.getId());
cachPlcConfig.setSystemName(plc.getSystemName());
cachPlcConfig.setProtocolType(plc.getProtocolType());
cachPlcConfig.setConnectConfig(plc.getConnectConfig());
cachPlcConfig.setPlcMap(new HashMap());
plcsConfigMap.put(plc.getId(), cachPlcConfig);
}
Map<String, PlcPoint> plcMap = cachPlcConfig.getPlcMap();
if(plcMap.size()==0 && plc.getPoints().size()>0)
{
Map<String, PlcPoint> finalPlcMap = plcMap;
plc.getPoints().forEach(p -> finalPlcMap.put(p.system, p));
}
}
}
/**
* 获取指定plc的点位配置
* @param id 系统编号
* @return
*/
public static CachPlcConfig getPlcSystems(Integer id)
{
return plcsConfigMap.get(id);
}
/**
* 获取指定plc的指定点位配置
* @param system 指定点位系统名
* @param id 系统编号
* @return
*/
public static PlcPoint getPlcSystem(Integer id,String system)
{
if (plcsConfigMap.containsKey(id) && plcsConfigMap.get(id).getPlcMap().containsKey(system)) return plcsConfigMap.get(id).getPlcMap().get(system);
return null;
}
public static Map<Integer, CachPlcConfig> getPlcsConfigMap()
{
return plcsConfigMap;
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.data;
import com.zhonglai.luhui.device.mqtt.terminal.jar.ParseDataFactory;
import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
/**
* 解析数据服务
*/
public class ParseDataService implements ParseDataFactory {
@Override
public void parse(Topic topicDto, byte[] payload, MqttService mqttService) {
TopicFactoryAdapter topicFactory = TopicFactoryAdapter.createTopicFactory(topicDto, payload);
if(null != topicFactory)
{
topicFactory.parse(mqttService);
}
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.data;
import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
public interface TopicFactory {
void parse(MqttService mqttService);
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.data;
import com.zhonglai.luhui.device.modbus.terminal.data.topic.HostTopic;
import com.zhonglai.luhui.device.modbus.terminal.data.topic.PutTopic;
import com.zhonglai.luhui.device.modbus.terminal.data.topic.ReadTopic;
import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class TopicFactoryAdapter implements TopicFactory{
protected static final Logger logger = LoggerFactory.getLogger(TopicFactoryAdapter.class);
protected Topic topicDto;
protected byte[] payload;
public TopicFactoryAdapter(Topic topicDto, byte[] payload)
{
this.topicDto = topicDto;
this.payload = payload;
}
public static TopicFactoryAdapter createTopicFactory(Topic topicDto, byte[] payload)
{
switch (topicDto.getTopicType())
{
case "PUT": // 写入 , payload: {"1":{"name1":xxx,"name2":xxx}}
return new PutTopic(topicDto, payload);
case "READ": // 读取 , payload: {"1":{"name1","name2","name3"}}
return new ReadTopic(topicDto, payload);
case "HOST": // 主机操作 , payload: {""}
return new HostTopic(topicDto, payload);
default:
return null;
}
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.data.topic;
import com.zhonglai.luhui.device.modbus.terminal.camera.opf.CameraOperationInstructions;
import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
import com.zhonglai.luhui.device.modbus.terminal.data.TopicFactoryAdapter;
import com.alibaba.fastjson.JSONObject;
import org.eclipse.paho.client.mqttv3.MqttException;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.*;
import java.nio.file.attribute.FileTime;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
public class HostTopic extends TopicFactoryAdapter {
private static final AtomicReference<Path> currentDir =
new AtomicReference<>(Paths.get(System.getProperty("user.dir")));
public HostTopic(Topic topicDto, byte[] payload) {
super(topicDto, payload);
}
@Override
public void parse(MqttService mqttService) {
String str = new String(payload);
JSONObject jsonObject = JSONObject.parseObject(str);
String function = jsonObject.getString("function");
JSONObject data = jsonObject.getJSONObject("data");
JSONObject result = new JSONObject();
try {
switch (function) {
case "ls":
result.put("result", listFiles());
break;
case "cd":
result.put("result", changeDirectory(data.getString("path")));
break;
case "mkdir":
result.put("result", makeDir(data.getString("name")));
break;
case "rm":
result.put("result", removeFile(data.getString("name")));
break;
case "copy":
result.put("result", copyFile(data.getString("source"), data.getString("target")));
break;
case "mk":
result.put("result", makeFile(data.getString("name")));
break;
case "download":
result.put("result", downloadFile(data.getString("url"), data.getString("name")));
break;
case "upload":
result.put("result", uploadFile(data.getString("name"), data.getString("url")));
break;
case "camera":
result.put("result", camera(data));
break;
default:
result.put("error", "Unknown function: " + function);
}
} catch (Exception e) {
result.put("error", e.getMessage());
}
// 回传执行结果
try {
mqttService.publish("HOST_REQ/"+topicDto.getTime(), result.toJSONString());
} catch (MqttException e) {
logger.info("返回mqtt指令失败");
}
}
private JSONObject listFiles() throws IOException {
JSONObject result = new JSONObject();
Path dir = currentDir.get();
if (!Files.isDirectory(dir)) {
result.put("error", "Not a directory: " + dir.toString());
return result;
}
// 时间格式化器
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 文件数组
List<JSONObject> files = new ArrayList<>();
Files.list(dir).forEach(path -> {
JSONObject fileObj = new JSONObject();
fileObj.put("name", path.getFileName().toString());
try {
FileTime fileTime = Files.getLastModifiedTime(path);
fileObj.put("lastModified", sdf.format(new Date(fileTime.toMillis())));
} catch (IOException e) {
fileObj.put("lastModified", "unknown");
}
fileObj.put("isDirectory", Files.isDirectory(path));
try {
if (Files.isDirectory(path)) {
fileObj.put("size", "-");
} else {
fileObj.put("size", Files.size(path));
}
} catch (IOException e) {
fileObj.put("size", "unknown");
}
files.add(fileObj);
});
result.put("path", dir.toAbsolutePath().toString());
result.put("files", files);
return result;
}
private String changeDirectory(String path) {
Path newPath = currentDir.get().resolve(path).normalize();
if (Files.isDirectory(newPath)) {
currentDir.set(newPath);
return newPath.toAbsolutePath().toString(); // 返回绝对路径
}
return "";
}
private String makeDir(String name) throws IOException {
Path newPath = currentDir.get().resolve(name);
Files.createDirectories(newPath);
return newPath.toString();
}
private String removeFile(String name) throws IOException {
Path target = currentDir.get().resolve(name);
Files.deleteIfExists(target);
return target.toString();
}
private String copyFile(String source, String target) throws IOException {
Path src = currentDir.get().resolve(source);
Path tgt = currentDir.get().resolve(target);
Files.copy(src, tgt, StandardCopyOption.REPLACE_EXISTING);
return "Copied from " + src.toString() + " to " + tgt.toString();
}
private String makeFile(String name) throws IOException {
Path file = currentDir.get().resolve(name);
Files.createFile(file);
return "File created: " + file.toString();
}
private String downloadFile(String urlStr, String fileName) throws IOException {
Path target = currentDir.get().resolve(fileName);
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
try (InputStream in = conn.getInputStream();
OutputStream out = new FileOutputStream(target.toFile())) {
byte[] buffer = new byte[8192];
int len;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
}
return "Downloaded file to " + target.toString();
}
private String uploadFile(String fileName, String urlStr) throws IOException {
Path file = currentDir.get().resolve(fileName);
if (!Files.exists(file)) {
return "File not found: " + file.toString();
}
String boundary = "----WebKitFormBoundary" + System.currentTimeMillis();
String LINE_FEED = "\r\n";
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("POST");
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
try (OutputStream out = conn.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"), true);
FileInputStream inputStream = new FileInputStream(file.toFile())) {
// --- 表单文件头
writer.append("--").append(boundary).append(LINE_FEED);
writer.append("Content-Disposition: form-data; name=\"file\"; filename=\"")
.append(file.getFileName().toString()).append("\"").append(LINE_FEED);
writer.append("Content-Type: application/octet-stream").append(LINE_FEED);
writer.append(LINE_FEED);
writer.flush();
// --- 文件内容
byte[] buffer = new byte[8192];
int len;
while ((len = inputStream.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.flush();
// --- 文件结束
writer.append(LINE_FEED).flush();
writer.append("--").append(boundary).append("--").append(LINE_FEED);
writer.flush();
}
int responseCode = conn.getResponseCode();
String responseMsg = "";
InputStream respStream = null;
try {
respStream = conn.getInputStream();
} catch (IOException e) {
respStream = conn.getErrorStream(); // 出错时取 errorStream
}
if (respStream != null) {
StringBuilder sb = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(respStream, "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
responseMsg = sb.toString().trim();
reader.close();
}
return "Uploaded " + file.toString() + " to " + urlStr +
" (HTTP " + responseCode + ") response: " + responseMsg;
}
private String camera(JSONObject parameter) throws IOException {
CameraOperationInstructions cameraOperationInstructions = CameraOperationInstructions.createCameraOperationInstructions( parameter);
if (null != cameraOperationInstructions)
{
return cameraOperationInstructions.execute().toJSONString();
}
return "camera ok" ;
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.data.topic;
import com.alibaba.fastjson.JSONObject;
import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig;
import com.zhonglai.luhui.device.modbus.terminal.data.TopicFactoryAdapter;
import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jWrite;
import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
import org.eclipse.paho.client.mqttv3.MqttException;
import java.util.ArrayList;
import java.util.List;
/**
* 设备写数据
* payload: {"1":{"name1":xxx,"name2":xxx}}
*/
public class PutTopic extends TopicFactoryAdapter {
public PutTopic(Topic topicDto, byte[] payload) {
super(topicDto, payload);
}
@Override
public void parse(MqttService mqttService) {
String str = new String( payload);
JSONObject jsonObject = JSONObject.parseObject(str);
for (String key : jsonObject.keySet())
{
JSONObject plcCommand = jsonObject.getJSONObject(key);
Integer id = Integer.parseInt(key);
List<PlcPoint> plcPoints = getPlcPoints(id, plcCommand);
try {
new Modbus4jWrite(id).batchWrite(plcPoints,true);
} catch (Exception e) {
logger.error("写plc异常",e);
}
}
try {
JSONObject rJsonObject = new JSONObject();
rJsonObject.put("result",1);
mqttService.publish("PUT_REQ/"+topicDto.getTime(),rJsonObject.toJSONString());
} catch (MqttException e) {
logger.error("饭hi结果异常",e);
}
}
private List<PlcPoint> getPlcPoints(Integer id,JSONObject plcCommand)
{
List<PlcPoint> plcPoints = new ArrayList<>();
for (String pointName : plcCommand.keySet())
{
PlcPoint plcPoint = InitPlcConfig.getPlcSystem(id, pointName);
if ( null != plcPoint)
{
plcPoint.setValue(plcCommand.getString(pointName));
plcPoints.add(plcPoint);
}
}
return plcPoints;
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.data.topic;
import com.alibaba.fastjson.JSONObject;
import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig;
import com.zhonglai.luhui.device.modbus.terminal.data.TopicFactoryAdapter;
import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jRead;
import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jWrite;
import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
import com.zhonglai.luhui.device.mqtt.terminal.jar.dto.Topic;
import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
import org.eclipse.paho.client.mqttv3.MqttException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 设备读数据
* payload: {"1":"name1","name2","name3"}
*/
public class ReadTopic extends TopicFactoryAdapter {
public ReadTopic(Topic topicDto, byte[] payload) {
super(topicDto, payload);
}
@Override
public void parse(MqttService mqttService) {
JSONObject rJsonObject = new JSONObject();
String str = new String( payload);
JSONObject jsonObject = JSONObject.parseObject(str);
for (String key : jsonObject.keySet())
{
String plcCommand = jsonObject.getString(key);
Integer id = Integer.parseInt(key);
List<PlcPoint> plcPoints = getPlcPoints(id, plcCommand);
try {
Map<String, Object> map = new Modbus4jRead(id).batchRead(plcPoints,true);
rJsonObject.put(key, map);
} catch (Exception e) {
logger.error("读plc异常",e);
return;
}
}
try {
mqttService.publish("READ_REQ/"+topicDto.getTime(),rJsonObject.toJSONString());
} catch (MqttException e) {
logger.error("返回结果异常",e);
}
}
private List<PlcPoint> getPlcPoints(Integer id,String plcCommand)
{
List<PlcPoint> plcPoints = new ArrayList<>();
for (String pointName : plcCommand.split(","))
{
PlcPoint plcPoint = InitPlcConfig.getPlcSystem(id, pointName);
if ( null != plcPoint)
{
plcPoints.add(plcPoint);
}
}
return plcPoints;
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.modbus;
import com.serotonin.modbus4j.BatchRead;
import com.serotonin.modbus4j.BatchResults;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* modbus通讯工具类,采用modbus4j实现
*
* @author lxq
* @dependencies modbus4j-3.0.3.jar
* @website https://github.com/infiniteautomation/modbus4j
*/
public class Modbus4jRead {
private ModbusMaster master;
public Modbus4jRead(Integer id) throws Exception {
this.master = ModbusMasterMessage.createMaster(id);
}
/**
* 批量读取使用方法
*
* @throws ModbusTransportException
* @throws ErrorResponseException
* @throws ModbusInitException
*/
/**
* 批量读取点位
*/
public Map<String, Object> batchRead(List<PlcPoint> plcPoints, boolean zeroBasedAddress) throws ModbusTransportException, ErrorResponseException {
BatchRead<String> batch = new BatchRead<>();
for (PlcPoint plcPoint : plcPoints) {
String key = plcPoint.getSystem();
// 解析 Modbus 地址,生成 BaseLocator
BaseLocator<?> locator = ModbusAddressParser.parseLocator(
plcPoint.getAddress(),
plcPoint.getDataType(),
plcPoint.getSlaveId(),
zeroBasedAddress,
plcPoint.getOrder()
);
batch.addLocator(key, locator);
}
batch.setContiguousRequests(false);
// 发送批量请求
BatchResults<String> results = master.send(batch);
// 转换成 JSON 对象
Map<String, Object> jsonMap = new HashMap<>();
for (PlcPoint plcPoint : plcPoints) {
// 输出所有结果
String key = plcPoint.getSystem();
Object value = results.getValue(key);
switch (plcPoint.getDataType())
{
case FLOAT32:
if (value instanceof Integer) {
int raw = (int) value;
switch (plcPoint.getOrder()) {
case "ABCD":
value = ModbusAddressParser.intToFloatABCD(raw);
break;
case "BADC":
value = ModbusAddressParser.intToFloatBADC(raw);
break;
case "CDAB":
value = ModbusAddressParser.intToFloatCDAB(raw);
break;
case "DCBA":
value = ModbusAddressParser.intToFloatDCBA(raw);
break;
}
}
break;
case INT32:
if (value instanceof Integer) {
int raw = (int) value;
switch (plcPoint.getOrder()) {
case "ABCD":
value = ModbusAddressParser.int32ABCD(raw);
break;
case "BADC":
value = ModbusAddressParser.int32BADC(raw);
break;
case "CDAB":
value = ModbusAddressParser.int32CDAB(raw);
break;
case "DCBA":
value = ModbusAddressParser.int32DCBA(raw);
break;
}
}
break;
}
jsonMap.put(key, value);
}
return jsonMap;
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.modbus;
import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.msg.ModbusResponse;
import com.serotonin.modbus4j.msg.WriteCoilRequest;
import com.serotonin.modbus4j.msg.WriteCoilResponse;
import com.serotonin.modbus4j.msg.WriteCoilsRequest;
import com.serotonin.modbus4j.msg.WriteCoilsResponse;
import com.serotonin.modbus4j.msg.WriteRegisterRequest;
import com.serotonin.modbus4j.msg.WriteRegisterResponse;
import com.serotonin.modbus4j.msg.WriteRegistersRequest;
import java.util.List;
/**
* modbus4j写入数据
*
* @author xq
*
*/
public class Modbus4jWrite {
static Log log = LogFactory.getLog(Modbus4jWrite.class);
private ModbusMaster master;
public Modbus4jWrite(Integer id) throws Exception {
this.master = ModbusMasterMessage.createMaster(id);
}
/**
* 单点写
*/
public void writePoint(PlcPoint point, boolean zeroBasedAddress) throws ModbusTransportException, ErrorResponseException {
BaseLocator<?> locator = ModbusAddressParser.parseLocator(
point.getAddress(),
point.getDataType(),
point.getSlaveId(),
zeroBasedAddress,
point.getOrder()
);
Object value = point.getValue();
// 如果 value 是 String,则根据 dataType 转换
if (value instanceof String) {
String strVal = (String) value;
switch (point.getDataType()) {
case BIT:
value = Boolean.parseBoolean(strVal);
break;
case INT16:
value = Short.parseShort(strVal);
break;
case UINT16:
value = Integer.parseInt(strVal); // Modbus4j 内部支持 unsigned
break;
case INT32:
int raw = Integer.parseInt(strVal);
switch (point.getOrder()) {
case "ABCD":
value = ModbusAddressParser.int32ToABCD(raw);
break;
case "BADC":
value = ModbusAddressParser.int32ToBADC(raw);
break;
case "CDAB":
value = ModbusAddressParser.int32ToCDAB(raw);
break;
case "DCBA":
value = ModbusAddressParser.int32ToDCBA(raw);
break;
default:
value = raw;
break;
}
break;
case INT64:
value = Long.parseLong(strVal);
break;
case FLOAT32:
float fvalue = Float.parseFloat(strVal);
switch (point.getOrder()) {
case "ABCD":
value = ModbusAddressParser.floatToIntABCD(fvalue);
break;
case "BADC":
value = ModbusAddressParser.floatToIntBADC(fvalue);
break;
case "CDAB":
value = ModbusAddressParser.floatToIntCDAB(fvalue);
break;
case "DCBA":
value = ModbusAddressParser.floatToIntDCBA(fvalue);
break;
default:
value = fvalue;
break;
}
break;
case DOUBLE64:
value = Double.parseDouble(strVal);
break;
default:
throw new IllegalArgumentException("不支持的数据类型: " + point.getDataType());
}
}
// 设置值
master.setValue(locator, value);
log.info("写入成功: " + point.getName() + " = " + point.getValue());
}
/**
* 批量写(兼容 3.1.0,没有 BatchWrite)
*/
public void batchWrite(List<PlcPoint> points, boolean zeroBasedAddress) throws ModbusTransportException, ErrorResponseException {
for (PlcPoint plcPoint:points) {
writePoint(plcPoint, zeroBasedAddress);
}
}
public boolean isConnected() {
return master.isConnected();
}
public void close() {
master.destroy();
}
}
\ No newline at end of file
... ...
package com.zhonglai.luhui.device.modbus.terminal.modbus;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.code.DataType;
import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcDataType;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class ModbusAddressParser {
/**
* 解析 Modbus 地址,生成 BaseLocator
* @param rawAddress 原始地址 (40001, 40001.01, 40003-40004)
* @param type 数据类型 (BIT, INT16, UINT16, INT32, INT64, FLOAT32, DOUBLE64)
* @param slaveId 从站ID
* @return BaseLocator 对象
*/
public static BaseLocator<?> parseLocator(String rawAddress, PlcDataType type, int slaveId, boolean zeroBasedAddress,String order) {
int offset = parseOffset(rawAddress,zeroBasedAddress);
int bitIndex = parseBitIndex(rawAddress);
switch (type) {
case BIT:
if (rawAddress.startsWith("0")) {
// Coil (线圈输出)
return BaseLocator.coilStatus(slaveId, offset);
} else if (rawAddress.startsWith("1")) {
// Discrete Input (离散量输入)
return BaseLocator.inputStatus(slaveId, offset);
} else if (rawAddress.startsWith("3") && bitIndex >= 0) {
// Input Register 内部 bit 位
return BaseLocator.inputRegisterBit(slaveId, offset, bitIndex);
} else if (rawAddress.startsWith("4") && bitIndex >= 0) {
// Holding Register 内部 bit 位
return BaseLocator.holdingRegisterBit(slaveId, offset, bitIndex);
} else {
throw new IllegalArgumentException("地址不支持 BIT 类型: " + rawAddress);
}
case INT16:
if (rawAddress.startsWith("3")) {
return BaseLocator.inputRegister(slaveId, offset, DataType.TWO_BYTE_INT_SIGNED);
} else if (rawAddress.startsWith("4")) {
return BaseLocator.holdingRegister(slaveId, offset, DataType.TWO_BYTE_INT_SIGNED);
}
break;
case UINT16:
if (rawAddress.startsWith("3")) {
return BaseLocator.inputRegister(slaveId, offset, DataType.TWO_BYTE_INT_UNSIGNED);
} else if (rawAddress.startsWith("4")) {
return BaseLocator.holdingRegister(slaveId, offset, DataType.TWO_BYTE_INT_UNSIGNED);
}
break;
case INT32:
if (rawAddress.startsWith("3")) {
return BaseLocator.inputRegister(slaveId, offset, DataType.FOUR_BYTE_INT_SIGNED);
} else if (rawAddress.startsWith("4")) {
return BaseLocator.holdingRegister(slaveId, offset, DataType.FOUR_BYTE_INT_SIGNED);
}
break;
case INT64:
if (rawAddress.startsWith("3")) {
return BaseLocator.inputRegister(slaveId, offset, DataType.EIGHT_BYTE_INT_SIGNED);
} else if (rawAddress.startsWith("4")) {
return BaseLocator.holdingRegister(slaveId, offset, DataType.EIGHT_BYTE_INT_SIGNED);
}
break;
case FLOAT32:
if (order == null) {
order = "ABCD"; // 默认大端
}
int floatDataType = DataType.FOUR_BYTE_FLOAT;
switch (order) {
case "ABCD": // 大端 IEEE754
floatDataType = DataType.FOUR_BYTE_FLOAT;
break;
case "CDAB": // 寄存器交换
floatDataType = DataType.FOUR_BYTE_FLOAT_SWAPPED;
break;
case "BADC": // 字节交换
floatDataType = DataType.FOUR_BYTE_FLOAT_SWAPPED_INVERTED;
break;
case "DCBA": // 全反转
floatDataType = DataType.FOUR_BYTE_INT_SIGNED_SWAPPED_SWAPPED;
// ⚠ modbus4j 没有直接 DCBA 的 float,可以用 int SWAP-SWAP 再手动转 float
break;
}
if (rawAddress.startsWith("3")) {
return BaseLocator.inputRegister(slaveId, offset, floatDataType);
} else if (rawAddress.startsWith("4")) {
return BaseLocator.holdingRegister(slaveId, offset, floatDataType);
}
break;
case DOUBLE64:
if (rawAddress.startsWith("3")) {
return BaseLocator.inputRegister(slaveId, offset, DataType.EIGHT_BYTE_FLOAT);
} else if (rawAddress.startsWith("4")) {
return BaseLocator.holdingRegister(slaveId, offset, DataType.EIGHT_BYTE_FLOAT);
}
break;
default:
throw new IllegalArgumentException("不支持的数据类型: " + type);
}
throw new IllegalArgumentException("地址和数据类型不匹配: " + rawAddress + " -> " + type);
}
/** 解析寄存器偏移量 */
public static int parseOffset(String rawAddress, boolean zeroBasedAddress) {
if (rawAddress == null || rawAddress.isEmpty()) {
throw new IllegalArgumentException("地址不能为空");
}
// 处理区间地址:40003-40004
if (rawAddress.contains("-")) {
String[] parts = rawAddress.split("-");
rawAddress = parts[0]; // 取起始地址
}
// 去掉小数点后部分:40001.01 -> 40001
String basePart = rawAddress.split("\\.")[0];
int address = Integer.parseInt(basePart);
int offset;
if (address >= 1 && address <= 9999) { // Coil (0xxxx)
offset = address - 1;
} else if (address >= 10001 && address <= 19999) { // Discrete Input (1xxxx)
offset = address - 10001;
} else if (address >= 30001 && address <= 39999) { // Input Register (3xxxx)
offset = address - 30001;
} else if (address >= 40001 && address <= 49999) { // Holding Register (4xxxx)
offset = address - 40001;
} else {
throw new IllegalArgumentException("无效的Modbus地址: " + rawAddress);
}
// 根据 zeroBasedAddress 参数调整偏移
if (!zeroBasedAddress) {
offset += 1;
}
return offset;
}
/** 解析 bit 索引 */
public static int parseBitIndex(String rawAddress) {
if (rawAddress.contains(".")) {
String[] parts = rawAddress.split("\\.");
return Integer.parseInt(parts[1]) - 1; // 转成从0开始
}
return -1; // 没有 bit 位
}
public static float intToFloatDCBA(int value) {
byte[] bytes = new byte[4];
bytes[0] = (byte) (value & 0xFF);
bytes[1] = (byte) ((value >> 8) & 0xFF);
bytes[2] = (byte) ((value >> 16) & 0xFF);
bytes[3] = (byte) ((value >> 24) & 0xFF);
// 反转成 DCBA
byte tmp;
tmp = bytes[0]; bytes[0] = bytes[3]; bytes[3] = tmp;
tmp = bytes[1]; bytes[1] = bytes[2]; bytes[2] = tmp;
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
}
public static int floatToIntDCBA(float f) {
byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(f).array();
// 反转成 DCBA
byte tmp;
tmp = bytes[0]; bytes[0] = bytes[3]; bytes[3] = tmp;
tmp = bytes[1]; bytes[1] = bytes[2]; bytes[2] = tmp;
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
}
public static float intToFloatABCD(int value) {
byte[] bytes = intToBytes(value);
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
}
public static float intToFloatBADC(int value) {
byte[] bytes = intToBytes(value);
swap(bytes, 0, 1);
swap(bytes, 2, 3);
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
}
public static float intToFloatCDAB(int value) {
byte[] bytes = intToBytes(value);
swap(bytes, 0, 2);
swap(bytes, 1, 3);
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat();
}
// ========== 32位整型的不同字节序 ==========
public static int int32ABCD(int value) {
return value; // 默认就是ABCD
}
public static int int32BADC(int value) {
byte[] bytes = intToBytes(value);
swap(bytes, 0, 1);
swap(bytes, 2, 3);
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
}
public static int int32CDAB(int value) {
byte[] bytes = intToBytes(value);
swap(bytes, 0, 2);
swap(bytes, 1, 3);
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
}
public static int int32DCBA(int value) {
byte[] bytes = intToBytes(value);
reverse(bytes);
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
}
// ========== 通用工具 ==========
private static byte[] intToBytes(int value) {
return new byte[] {
(byte) ((value >> 24) & 0xFF),
(byte) ((value >> 16) & 0xFF),
(byte) ((value >> 8) & 0xFF),
(byte) (value & 0xFF)
};
}
private static void swap(byte[] arr, int i, int j) {
byte tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
private static void reverse(byte[] arr) {
for (int i = 0, j = arr.length - 1; i < j; i++, j--) {
swap(arr, i, j);
}
}
// ========== Float 写入 ==========
public static int floatToIntABCD(float f) {
byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(f).array();
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
}
public static int floatToIntBADC(float f) {
byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(f).array();
swap(bytes, 0, 1);
swap(bytes, 2, 3);
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
}
public static int floatToIntCDAB(float f) {
byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(f).array();
swap(bytes, 0, 2);
swap(bytes, 1, 3);
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
}
// ========== Int32 写入 ==========
public static int int32ToABCD(int value) {
return value; // 默认
}
public static int int32ToBADC(int value) {
byte[] bytes = intToBytes(value);
swap(bytes, 0, 1);
swap(bytes, 2, 3);
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
}
public static int int32ToCDAB(int value) {
byte[] bytes = intToBytes(value);
swap(bytes, 0, 2);
swap(bytes, 1, 3);
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
}
public static int int32ToDCBA(int value) {
byte[] bytes = intToBytes(value);
reverse(bytes);
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt();
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.modbus;
import com.alibaba.fastjson.JSONObject;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.msg.*;
import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig;
import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.CachPlcConfig;
import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.JSerialCommWrapper;
import java.io.File;
import java.util.concurrent.ConcurrentHashMap;
public class ModbusMasterMessage {
/**
* 工厂
*/
static ModbusFactory modbusFactory;
static {
if (modbusFactory == null) {
modbusFactory = new ModbusFactory();
}
}
// 存储每个系统的 ModbusMaster
private static final ConcurrentHashMap<Integer, ModbusMaster> masterCache = new ConcurrentHashMap<>();
// 每个系统 ID 对应的锁对象
private static final ConcurrentHashMap<Integer, Object> lockMap = new ConcurrentHashMap<>();
/**
* 创建或获取 ModbusMaster (线程安全)
*/
public static ModbusMaster createMaster(Integer id) throws Exception {
ModbusMaster master = masterCache.get(id);
if (master != null && master.isConnected()) {
return master;
}
// 获取该 ID 的专用锁
Object lock = lockMap.computeIfAbsent(id, k -> new Object());
synchronized (lock) {
// 双重检查,避免重复创建
master = masterCache.get(id);
if (master != null && master.isConnected()) {
return master;
}
// 如果旧的存在但断开了,销毁
if (master != null) {
master.destroy();
}
// 创建新 master
ModbusMaster newMaster = selectMaster(id);
// newMaster.setIoLog(new MyIOLog(new File("logs/modbus-" + id + ".log")));
newMaster.init();
masterCache.put(id, newMaster);
return newMaster;
}
}
private static ModbusMaster selectMaster(Integer id) throws Exception {
CachPlcConfig cachPlcConfig = InitPlcConfig.getPlcSystems(id);
if (null == cachPlcConfig)
{
throw new Exception("系统: " + id+" 未配置");
}
JSONObject connectConfig = cachPlcConfig.getConnectConfig();
switch (cachPlcConfig.getProtocolType())
{
case RTU:
return modbusFactory.createRtuMaster(JSONObject.parseObject(connectConfig.toJSONString(),JSerialCommWrapper.class));
case TCP:
return modbusFactory.createTcpMaster(JSONObject.parseObject(connectConfig.toJSONString(),IpParameters.class), true); //这里传的是 false → 表示短连接模式,每次用完连接就会关闭。
case ASCII:
return modbusFactory.createAsciiMaster(JSONObject.parseObject(connectConfig.toJSONString(),JSerialCommWrapper.class));
case UDP:
return modbusFactory.createUdpMaster(JSONObject.parseObject(connectConfig.toJSONString(),IpParameters.class));
default:
throw new Exception("系统: " + id+" 不支持的协议");
}
}
/**
* 关闭指定系统的 Master
*/
public static void closeMaster(Integer id) {
Object lock = lockMap.computeIfAbsent(id, k -> new Object());
synchronized (lock) {
ModbusMaster master = masterCache.remove(id);
if (master != null) {
master.destroy();
}
lockMap.remove(id);
}
}
/**
* 关闭所有 Master
*/
public static void closeAll() {
masterCache.values().forEach(ModbusMaster::destroy);
masterCache.clear();
lockMap.clear();
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.modbus;
import com.serotonin.modbus4j.sero.log.BaseIOLog;
import java.io.File;
public class MyIOLog extends BaseIOLog {
/**
* <p>Constructor for BaseIOLog.</p>
*
* @param logFile a {@link File} object.
*/
public MyIOLog(File logFile) {
super(logFile);
}
@Override
protected void sizeCheck() {
// 简单示例:不限制大小
// 可以实现文件超过一定大小时备份、清空等逻辑
}
@Override
public void log(String message) {
super.log(message);
// 同时在控制台打印
System.out.println(message);
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.modbus.dto;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.util.JSONPObject;
import java.util.Map;
public class CachPlcConfig {
private Integer id;
private String systemName;
private ProtocolType protocolType;
private JSONObject connectConfig;
private Map<String, PlcPoint> plcMap;
public String getSystemName() {
return systemName;
}
public void setSystemName(String systemName) {
this.systemName = systemName;
}
public Map<String, PlcPoint> getPlcMap() {
return plcMap;
}
public void setPlcMap(Map<String, PlcPoint> plcMap) {
this.plcMap = plcMap;
}
public JSONObject getConnectConfig() {
return connectConfig;
}
public void setConnectConfig(JSONObject connectConfig) {
this.connectConfig = connectConfig;
}
public ProtocolType getProtocolType() {
return protocolType;
}
public void setProtocolType(ProtocolType protocolType) {
this.protocolType = protocolType;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.modbus.dto;
import com.serotonin.modbus4j.serial.SerialPortWrapper;
import com.fazecast.jSerialComm.SerialPort; // 需要 jSerialComm 库
import java.io.InputStream;
import java.io.OutputStream;
public class JSerialCommWrapper implements SerialPortWrapper {
private final String commPortId; // 串口号,例如 "COM3" 或 "/dev/ttyUSB0"
private final int baudRate; // 波特率
private final int dataBits;
private final int stopBits;
private final int parity; // 0 = NONE, 1 = ODD, 2 = EVEN
private SerialPort serialPort;
public JSerialCommWrapper(String commPortId, int baudRate, int dataBits, int stopBits, int parity) {
this.commPortId = commPortId;
this.baudRate = baudRate;
this.dataBits = dataBits;
this.stopBits = stopBits;
this.parity = parity;
}
@Override
public void close() throws Exception {
if (serialPort != null) {
serialPort.closePort();
}
}
@Override
public void open() throws Exception {
serialPort = SerialPort.getCommPort(commPortId);
serialPort.setComPortParameters(baudRate, dataBits, stopBits, parity);
serialPort.openPort();
}
@Override
public InputStream getInputStream() {
return serialPort.getInputStream();
}
@Override
public OutputStream getOutputStream() {
return serialPort.getOutputStream();
}
@Override
public int getBaudRate() {
return baudRate;
}
@Override
public int getDataBits() {
return dataBits;
}
@Override
public int getStopBits() {
return stopBits;
}
@Override
public int getParity() {
return parity;
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.modbus.dto;
import com.fasterxml.jackson.annotation.JsonCreator;
/**
* PLC读写权限
*/
public enum PlcAccess {
R,
W,
RW;
@JsonCreator
public static PlcAccess fromString(String value) {
if (value == null) return null;
switch (value.toUpperCase()) {
case "R":
return R;
case "W":
return W;
case "RW":
return RW;
default:
throw new IllegalArgumentException("未知的枚举值: " + value);
}
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.modbus.dto;
import com.fasterxml.jackson.annotation.JsonCreator;
public enum PlcDataType {
BIT, // 线圈/开关量
INPUT_BIT, // 输入离散量
INT16,
UINT16,
INT32,
INT64,
FLOAT32,
DOUBLE64;
//忽略大小写
@JsonCreator
public static PlcDataType fromString(String value) {
if (value == null) return null;
switch (value.toUpperCase()) {
case "BIT":
return BIT;
case "INT":
case "INT16":
return INT16;
case "UINT":
case "UINT16":
return UINT16;
case "LONG": // 兼容 long
return INT32;
case "INT32":
return INT32;
case "INT64":
return INT64;
case "FLOAT":
case "FLOAT32":
return FLOAT32;
case "DOUBLE":
case "DOUBLE64":
return DOUBLE64;
case "INPUT_BIT":
case "INPUT":
return INPUT_BIT;
default:
throw new IllegalArgumentException("未知的枚举值: " + value);
}
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.modbus.dto;
public class PlcPoint {
public String name; // 点位名
public String system; // 点位系统名
public String address; // Modbus 地址,如 40001, 40001.01, 40003-40004
public PlcDataType dataType; // bit, int16, uint16, int32, float32, double64
public Integer slaveId = 1;
public Object value;
private String order = "DCBA"; // 新增,默认值
private PlcAccess access;
public PlcAccess getAccess() {
return access;
}
public void setAccess(PlcAccess access) {
this.access = access;
}
public String getOrder() {
return order;
}
public void setOrder(String order) {
this.order = order;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSystem() {
return system;
}
public void setSystem(String system) {
this.system = system;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public PlcDataType getDataType() {
return dataType;
}
public void setDataType(PlcDataType dataType) {
this.dataType = dataType;
}
public Integer getSlaveId() {
return slaveId;
}
public void setSlaveId(Integer slaveId) {
this.slaveId = slaveId;
}
}
\ No newline at end of file
... ...
package com.zhonglai.luhui.device.modbus.terminal.modbus.dto;
import com.alibaba.fastjson.JSONObject;
import java.util.List;
public class PlcSystem {
private Integer id;
private String systemName;
private ProtocolType protocolType;
private JSONObject connectConfig;
private List<PlcPoint> points;
public String getSystemName() {
return systemName;
}
public void setSystemName(String systemName) {
this.systemName = systemName;
}
public ProtocolType getProtocolType() {
return protocolType;
}
public void setProtocolType(ProtocolType protocolType) {
this.protocolType = protocolType;
}
public JSONObject getConnectConfig() {
return connectConfig;
}
public void setConnectConfig(JSONObject connectConfig) {
this.connectConfig = connectConfig;
}
public List<PlcPoint> getPoints() {
return points;
}
public void setPoints(List<PlcPoint> points) {
this.points = points;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
\ No newline at end of file
... ...
package com.zhonglai.luhui.device.modbus.terminal.modbus.dto;
/**
* 协议类型
*/
public enum ProtocolType {
RTU,
UDP,
ASCII,
TCP,
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.task;
import com.alibaba.fastjson.JSONObject;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig;
import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jRead;
import com.zhonglai.luhui.device.modbus.terminal.modbus.ModbusMasterMessage;
import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.CachPlcConfig;
import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
import com.zhonglai.luhui.device.mqtt.terminal.jar.mqtt.MqttService;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 定时采集上报plc数据
*/
public class CollectPlcDataTask {
protected static final Logger logger = LoggerFactory.getLogger(CollectPlcDataTask.class);
private static final int maxDataLenth = 30;
public void collect(MqttService mqttService) {
ScheduledThreadPool.scheduler.scheduleAtFixedRate(() -> {
try {
pubMqttData(mqttService);
}catch (Exception e)
{
logger.info("plc通讯异常:{}",e.getMessage());
}
},0, 60, TimeUnit.SECONDS);
}
private void pubMqttData(MqttService mqttService)
{
//查看可以访问的plc
Map<Integer, CachPlcConfig> plcConfigMap = InitPlcConfig.getPlcsConfigMap();
for (Integer plcId : plcConfigMap.keySet())
{
CachPlcConfig cachPlcConfig = plcConfigMap.get(plcId);
try {
pubMqttData(mqttService, plcId, cachPlcConfig);
} catch (Exception e) {
logger.info("plc {} 通讯异常:{}",plcId,e.getMessage());
}
}
}
private void pubMqttData(MqttService mqttService, Integer plcId,CachPlcConfig cachPlcConfig) throws Exception
{
Map<String, PlcPoint> map = cachPlcConfig.getPlcMap();
List<PlcPoint> plcPoints = new ArrayList<>();
int dataLenth = 1 ;
for (String system : map.keySet())
{
PlcPoint plcPoint = map.get(system);
plcPoints.add(plcPoint);
dataLenth++;
if (dataLenth == maxDataLenth)
{
if(!subMqttData(mqttService, plcId, plcPoints))
{
plcPoints.clear();
break;
}
System.out.println("plc "+plcId+" 发送数据 "+plcPoints.size()+" 条,等待1s接着发送");
dataLenth = 1;
plcPoints.clear();
plcPoints = new ArrayList<>();
try {
Thread.sleep(1000l);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
if (plcPoints.size() > 0)
{
subMqttData(mqttService, plcId, plcPoints);
System.out.println("plc "+plcId+" 发送数据 "+plcPoints.size()+" 条");
}
}
private boolean subMqttData(MqttService mqttService, Integer plcId, List<PlcPoint> plcPoints)
{
//通知
try {
Map<String, Object> datMap = new Modbus4jRead(plcId).batchRead(plcPoints,true);
JSONObject mqttSendData = new JSONObject();
mqttSendData.put(plcId+"", datMap);
String rdata = JSONObject.toJSONString(mqttSendData);
mqttService.publish("ADD_POST",rdata);
} catch (Exception e) {
if (e instanceof ModbusTransportException )
{
logger.error("plc通讯超时:"+plcId);
ModbusMasterMessage.closeMaster(plcId); // 销毁旧连接
return false;
}else
if(e instanceof MqttException)
{
logger.error("mqtt连接异常",e);
}else
if (e instanceof ErrorResponseException)
{
logger.error("plc返回指令异常",e);
ModbusMasterMessage.closeMaster(plcId); // 销毁旧连接
return false;
}else{
throw new RuntimeException(e);
}
}
return true;
}
}
... ...
package com.zhonglai.luhui.device.modbus.terminal.task;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
public class ScheduledThreadPool {
public static final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
public static void stopScheduler() {
if (null != scheduler)
{
scheduler.shutdown();
}
}
}
... ...
yuerleApiUrl=https://ly.api.yu2le.com/bigScreen/getYsLocalIp
webrtc_app=yuerle
webrtc_host=zlmediakit
\ No newline at end of file
... ...
mqtt.broker=tcp://iot.yu2le.com:1883
mqtt.username=lh_tc_kt
mqtt.password=lh_tc_kt
mqtt.clientId=LHZDY974113706494905
mqtt.subTopic=PUT/+,READ/+,HOST/+
\ No newline at end of file
... ...
{
"plcs": [
{
"id": 6,
"systemName": "测试",
"protocolType": "TCP",
"connectConfig": { "host": "192.168.1.82", "port": 2000},
"points": [
{"name": "水泵1故障", "system": "sb1gz", "address": "40001.01", "dataType": "bit","access":"r"},
{"name": "水泵2故障", "system": "sb2gz", "address": "40001.02", "dataType": "bit","access":"r"},
{"name": "氧锥泵1故障", "system": "yzb1gz", "address": "40001.03", "dataType": "bit","access":"r"},
{"name": "氧锥泵2故障", "system": "yzb2gz", "address": "40001.04", "dataType": "bit","access":"r"},
{"name": "氧锥泵3故障", "system": "yzb3gz", "address": "40001.05", "dataType": "bit","access":"r"},
{"name": "氧锥泵4故障", "system": "yzb4gz", "address": "40001.06", "dataType": "bit","access":"r"},
{"name": "排污泵故障", "system": "pwb_gz", "address": "40001.07", "dataType": "bit","access":"r"},
{"name": "排污阀1开不到位", "system": "pwf1kbdw", "address": "40001.09", "dataType": "bit","access":"r"},
{"name": "溶氧上限报警设定值", "system": "ry_sxsz", "address": "40053-40054", "dataType": "float32","order": "CDAB","access":"rw"},
{"name": "溶氧下限报警设定值", "system": "ry_xxsz", "address": "40055-40056", "dataType": "float32","order": "CDAB","access":"rw"}
]
},
{
"id": 1,
"systemName": "成鱼系统1",
"protocolType": "TCP",
"connectConfig": { "host": "192.168.1.19", "port": 2000},
"points": [
{"name": "自动", "system": "zd", "address": "10001", "dataType": "bit"},
{"name": "远程", "system": "yc", "address": "10002", "dataType": "bit"},
{"name": "补水泵启动", "system": "bsbqd", "address": "10003", "dataType": "bit"},
{"name": "水泵1运行", "system": "sb1yx", "address": "10004", "dataType": "bit"},
{"name": "水泵2运行", "system": "sb2yx", "address": "10005", "dataType": "bit"},
{"name": "氧锥泵1运行", "system": "yzb1yx", "address": "10006", "dataType": "bit"},
{"name": "氧锥泵2运行", "system": "yzb2yx", "address": "10007", "dataType": "bit"},
{"name": "氧锥泵3运行", "system": "yzb3yx", "address": "10008", "dataType": "bit"},
{"name": "氧锥泵4运行", "system": "yzb4yx", "address": "10009", "dataType": "bit"},
{"name": "排污泵运行", "system": "pwb", "address": "10010", "dataType": "bit"},
{"name": "微滤机电源合闸", "system": "wljdyhz", "address": "10011", "dataType": "bit"},
{"name": "紫外灯电源合闸", "system": "zwd", "address": "10012", "dataType": "bit"},
{"name": "微滤池液位高", "system": "wlcyw_g", "address": "10013", "dataType": "bit"},
{"name": "微滤池液位低", "system": "wlcyw_d", "address": "10014", "dataType": "bit"},
{"name": "蝶阀1开到位", "system": "df1kdw", "address": "10015", "dataType": "bit"},
{"name": "蝶阀1关到位", "system": "df1gdw", "address": "10016", "dataType": "bit"},
{"name": "蝶阀2开到位", "system": "df2kdw", "address": "10017", "dataType": "bit"},
{"name": "蝶阀2关到位", "system": "df2gdw", "address": "10018", "dataType": "bit"},
{"name": "蝶阀3开到位", "system": "df3kdw", "address": "10019", "dataType": "bit"},
{"name": "蝶阀3关到位", "system": "df3gdw", "address": "10020", "dataType": "bit"},
{"name": "蝶阀4开到位", "system": "df4kdw", "address": "10021", "dataType": "bit"},
{"name": "蝶阀4关到位", "system": "df4gdw", "address": "10022", "dataType": "bit"},
{"name": "蝶阀5开到位", "system": "df5kdw", "address": "10023", "dataType": "bit"},
{"name": "蝶阀5关到位", "system": "df5gdw", "address": "10024", "dataType": "bit"},
{"name": "蝶阀6开到位", "system": "df6kdw", "address": "10025", "dataType": "bit"},
{"name": "蝶阀6关到位", "system": "df6gdw", "address": "10026", "dataType": "bit"},
{"name": "蝶阀7开到位", "system": "df7kdw", "address": "10027", "dataType": "bit"},
{"name": "蝶阀7关到位", "system": "df7gdw", "address": "10028", "dataType": "bit"},
{"name": "蝶阀8开到位", "system": "df8kdw", "address": "10029", "dataType": "bit"},
{"name": "蝶阀8关到位", "system": "df8gdw", "address": "10030", "dataType": "bit"},
{"name": "循环水泵运行", "system": "xhsbyx", "address": "10031", "dataType": "bit"},
{"name": "系统报警", "system": "xtbj", "address": "00001", "dataType": "bit"},
{"name": "水泵1故障", "system": "sb1gz", "address": "40001.01", "dataType": "bit"},
{"name": "水泵2故障", "system": "sb2gz", "address": "40001.02", "dataType": "bit"},
{"name": "氧锥泵1故障", "system": "yzb1gz", "address": "40001.03", "dataType": "bit"},
{"name": "氧锥泵2故障", "system": "yzb2gz", "address": "40001.04", "dataType": "bit"},
{"name": "氧锥泵3故障", "system": "yzb3gz", "address": "40001.05", "dataType": "bit"},
{"name": "氧锥泵4故障", "system": "yzb4gz", "address": "40001.06", "dataType": "bit"},
{"name": "排污泵故障", "system": "pwb_gz", "address": "40001.07", "dataType": "bit"},
{"name": "排污阀1开不到位", "system": "pwf1kbdw", "address": "40001.09", "dataType": "bit"},
{"name": "排污阀1关不到位", "system": "pwf1gbdw", "address": "40001.10", "dataType": "bit"},
{"name": "排污阀2开不到位", "system": "pwf2kbdw", "address": "40001.11", "dataType": "bit"},
{"name": "排污阀2关不到位", "system": "pwf2gbdw", "address": "40001.12", "dataType": "bit"},
{"name": "排污阀3开不到位", "system": "pwf3kbdw", "address": "40001.13", "dataType": "bit"},
{"name": "排污阀3关不到位", "system": "pwf3gbdw", "address": "40001.14", "dataType": "bit"},
{"name": "排污阀4开不到位", "system": "pwf4kbdw", "address": "40001.15", "dataType": "bit"},
{"name": "排污阀4关不到位", "system": "pwf4gbdw", "address": "40001.16", "dataType": "bit"},
{"name": "排污阀5开不到位", "system": "pwf5kbdw", "address": "40002.01", "dataType": "bit"},
{"name": "排污阀5关不到位", "system": "pwf5gbdw", "address": "40002.02", "dataType": "bit"},
{"name": "排污阀6开不到位", "system": "pwf6kbdw", "address": "40002.03", "dataType": "bit"},
{"name": "排污阀6关不到位", "system": "pwf6gbdw", "address": "40002.04", "dataType": "bit"},
{"name": "排污阀7开不到位", "system": "pwf7kbdw", "address": "40002.05", "dataType": "bit"},
{"name": "排污阀7关不到位", "system": "pwf7gbdw", "address": "40002.06", "dataType": "bit"},
{"name": "排污阀8开不到位", "system": "pwf8kbdw", "address": "40002.07", "dataType": "bit"},
{"name": "排污阀8关不到位", "system": "pwf8gbdw", "address": "40002.08", "dataType": "bit"},
{"name": "补水高液位超时", "system": "bsgywdcs", "address": "40002.11", "dataType": "bit"},
{"name": "微滤池高液位超时", "system": "wlcgywdcs", "address": "40002.12", "dataType": "bit"},
{"name": "微滤机电源跳闸", "system": "wljdytz", "address": "40002.13", "dataType": "bit"},
{"name": "紫外杀菌灯跳闸", "system": "zwsjd_tz", "address": "40002.14", "dataType": "bit"},
{"name": "溶氧超限报警", "system": "rycxbj", "address": "40002.15", "dataType": "bit"},
{"name": "微滤池低液位长时间不消失报警", "system": "wlcdywbcsbj", "address": "40002.16", "dataType": "bit"},
{"name": "溶氧值", "system": "ry", "address": "40003-40004", "dataType": "float32"},
{"name": "温度值", "system": "wd", "address": "40005-40006", "dataType": "float32"},
{"name": "电能值", "system": "dn", "address": "40007-40008", "dataType": "float32"},
{"name": "当前氧锥泵运行台数", "system": "dqyzb", "address": "40009", "dataType": "int16"},
{"name": "氧锥泵1运行时间", "system": "yzb1_sj", "address": "40011-40012", "dataType": "int32"},
{"name": "氧锥泵2运行时间", "system": "yzb2_sj", "address": "40013-40014", "dataType": "int32"},
{"name": "氧锥泵3运行时间", "system": "yzb3_sj", "address": "40015-40016", "dataType": "int32"},
{"name": "氧锥泵4运行时间", "system": "yzb4_sj", "address": "40017-40018", "dataType": "int32"},
{"name": "生化池水温", "system": "shcsw", "address": "40019-40020", "dataType": "float32"},
{"name": "循环水泵故障", "system": "xhsb_gz", "address": "40021.01", "dataType": "bit"},
{"name": "生化池水温低限报警", "system": "shcsw_dx_bj", "address": "40021.02", "dataType": "bit"},
{"name": "生化池水温高限报警", "system": "shcsw_gx_bj", "address": "40021.03", "dataType": "bit"},
{"name": "排污阀1开OR关", "system": "pwf1_or", "address": "40051.01", "dataType": "bit"},
{"name": "排污阀2开OR关", "system": "pwf2_or", "address": "40051.02", "dataType": "bit"},
{"name": "排污阀3开OR关", "system": "pwf3_or", "address": "40051.03", "dataType": "bit"},
{"name": "排污阀4开OR关", "system": "pwf4_or", "address": "40051.04", "dataType": "bit"},
{"name": "排污阀5开OR关", "system": "pwf5_or", "address": "40051.05", "dataType": "bit"},
{"name": "排污阀6开OR关", "system": "pwf6_or", "address": "40051.06", "dataType": "bit"},
{"name": "排污阀7开OR关", "system": "pwf7_or", "address": "40051.07", "dataType": "bit"},
{"name": "排污阀8开OR关", "system": "pwf8_or", "address": "40051.08", "dataType": "bit"},
{"name": "水泵1启动", "system": "sb1_qd", "address": "40051.09", "dataType": "bit"},
{"name": "水泵2启动", "system": "sb2_qd", "address": "40051.10", "dataType": "bit"},
{"name": "氧锥泵1启动", "system": "yzb1_qd", "address": "40051.11", "dataType": "bit"},
{"name": "氧锥泵2启动", "system": "yzb2_qd", "address": "40051.12", "dataType": "bit"},
{"name": "氧锥泵3启动", "system": "yzb3_qd", "address": "40051.13", "dataType": "bit"},
{"name": "氧锥泵4启动", "system": "yzb4_qd", "address": "40051.14", "dataType": "bit"},
{"name": "排污泵启动", "system": "pwb_qd", "address": "40051.15", "dataType": "bit"},
{"name": "水泵1停止", "system": "sb1_tz", "address": "40052.01", "dataType": "bit"},
{"name": "水泵2停止", "system": "sb2_tz", "address": "40052.02", "dataType": "bit"},
{"name": "氧锥泵1停止", "system": "yzb1_tz", "address": "40052.03", "dataType": "bit"},
{"name": "氧锥泵2停止", "system": "yzb2_tz", "address": "40052.04", "dataType": "bit"},
{"name": "氧锥泵3停止", "system": "yzb3_tz", "address": "40052.05", "dataType": "bit"},
{"name": "氧锥泵4停止", "system": "yzb4_tz", "address": "40052.06", "dataType": "bit"},
{"name": "排污泵停止", "system": "pwb_tz", "address": "40052.07", "dataType": "bit"},
{"name": "清报警", "system": "qbj", "address": "40052.09", "dataType": "bit"},
{"name": "累计时间清零", "system": "lj_sjql", "address": "40052.10", "dataType": "bit"},
{"name": "溶氧上限报警设定值", "system": "ry_sxsz", "address": "40053-40054", "dataType": "float32"},
{"name": "溶氧下限报警设定值", "system": "ry_xxsz", "address": "40055-40056", "dataType": "float32"}
]
},
{
"id": 2,
"systemName": "成鱼系统2",
"protocolType": "TCP",
"connectConfig": { "host": "192.168.2.2", "port": 2001},
"points": [
{"name": "自动", "system": "zd", "address": "10001", "dataType": "bit"},
{"name": "远程", "system": "yc", "address": "10002", "dataType": "bit"},
{"name": "补水泵启动", "system": "bsbqd", "address": "10003", "dataType": "bit"},
{"name": "水泵1运行", "system": "sb1yx", "address": "10004", "dataType": "bit"},
{"name": "水泵2运行", "system": "sb2yx", "address": "10005", "dataType": "bit"},
{"name": "氧锥泵1运行", "system": "yzb1yx", "address": "10006", "dataType": "bit"},
{"name": "氧锥泵2运行", "system": "yzb2yx", "address": "10007", "dataType": "bit"},
{"name": "氧锥泵3运行", "system": "yzb3yx", "address": "10008", "dataType": "bit"},
{"name": "氧锥泵4运行", "system": "yzb4yx", "address": "10009", "dataType": "bit"},
{"name": "排污泵运行", "system": "pwb", "address": "10010", "dataType": "bit"},
{"name": "微滤机电源合闸", "system": "wljdyhz", "address": "10011", "dataType": "bit"},
{"name": "紫外灯电源合闸", "system": "zwd", "address": "10012", "dataType": "bit"},
{"name": "微滤池液位高", "system": "wlcyw_g", "address": "10013", "dataType": "bit"},
{"name": "微滤池液位低", "system": "wlcyw_d", "address": "10014", "dataType": "bit"},
{"name": "蝶阀1开到位", "system": "df1kdw", "address": "10015", "dataType": "bit"},
{"name": "蝶阀1关到位", "system": "df1gdw", "address": "10016", "dataType": "bit"},
{"name": "蝶阀2开到位", "system": "df2kdw", "address": "10017", "dataType": "bit"},
{"name": "蝶阀2关到位", "system": "df2gdw", "address": "10018", "dataType": "bit"},
{"name": "蝶阀3开到位", "system": "df3kdw", "address": "10019", "dataType": "bit"},
{"name": "蝶阀3关到位", "system": "df3gdw", "address": "10020", "dataType": "bit"},
{"name": "蝶阀4开到位", "system": "df4kdw", "address": "10021", "dataType": "bit"},
{"name": "蝶阀4关到位", "system": "df4gdw", "address": "10022", "dataType": "bit"},
{"name": "蝶阀5开到位", "system": "df5kdw", "address": "10023", "dataType": "bit"},
{"name": "蝶阀5关到位", "system": "df5gdw", "address": "10024", "dataType": "bit"},
{"name": "蝶阀6开到位", "system": "df6kdw", "address": "10025", "dataType": "bit"},
{"name": "蝶阀6关到位", "system": "df6gdw", "address": "10026", "dataType": "bit"},
{"name": "蝶阀7开到位", "system": "df7kdw", "address": "10027", "dataType": "bit"},
{"name": "蝶阀7关到位", "system": "df7gdw", "address": "10028", "dataType": "bit"},
{"name": "蝶阀8开到位", "system": "df8kdw", "address": "10029", "dataType": "bit"},
{"name": "蝶阀8关到位", "system": "df8gdw", "address": "10030", "dataType": "bit"},
{"name": "循环水泵运行", "system": "xhsbyx", "address": "10031", "dataType": "bit"},
{"name": "系统报警", "system": "xtbj", "address": "00001", "dataType": "bit"},
{"name": "水泵1故障", "system": "sb1gz", "address": "40001.01", "dataType": "bit"},
{"name": "水泵2故障", "system": "sb2gz", "address": "40001.02", "dataType": "bit"},
{"name": "氧锥泵1故障", "system": "yzb1gz", "address": "40001.03", "dataType": "bit"},
{"name": "氧锥泵2故障", "system": "yzb2gz", "address": "40001.04", "dataType": "bit"},
{"name": "氧锥泵3故障", "system": "yzb3gz", "address": "40001.05", "dataType": "bit"},
{"name": "氧锥泵4故障", "system": "yzb4gz", "address": "40001.06", "dataType": "bit"},
{"name": "排污泵故障", "system": "pwb_gz", "address": "40001.07", "dataType": "bit"},
{"name": "排污阀1开不到位", "system": "pwf1kbdw", "address": "40001.09", "dataType": "bit"},
{"name": "排污阀1关不到位", "system": "pwf1gbdw", "address": "40001.10", "dataType": "bit"},
{"name": "排污阀2开不到位", "system": "pwf2kbdw", "address": "40001.11", "dataType": "bit"},
{"name": "排污阀2关不到位", "system": "pwf2gbdw", "address": "40001.12", "dataType": "bit"},
{"name": "排污阀3开不到位", "system": "pwf3kbdw", "address": "40001.13", "dataType": "bit"},
{"name": "排污阀3关不到位", "system": "pwf3gbdw", "address": "40001.14", "dataType": "bit"},
{"name": "排污阀4开不到位", "system": "pwf4kbdw", "address": "40001.15", "dataType": "bit"},
{"name": "排污阀4关不到位", "system": "pwf4gbdw", "address": "40001.16", "dataType": "bit"},
{"name": "排污阀5开不到位", "system": "pwf5kbdw", "address": "40002.01", "dataType": "bit"},
{"name": "排污阀5关不到位", "system": "pwf5gbdw", "address": "40002.02", "dataType": "bit"},
{"name": "排污阀6开不到位", "system": "pwf6kbdw", "address": "40002.03", "dataType": "bit"},
{"name": "排污阀6关不到位", "system": "pwf6gbdw", "address": "40002.04", "dataType": "bit"},
{"name": "排污阀7开不到位", "system": "pwf7kbdw", "address": "40002.05", "dataType": "bit"},
{"name": "排污阀7关不到位", "system": "pwf7gbdw", "address": "40002.06", "dataType": "bit"},
{"name": "排污阀8开不到位", "system": "pwf8kbdw", "address": "40002.07", "dataType": "bit"},
{"name": "排污阀8关不到位", "system": "pwf8gbdw", "address": "40002.08", "dataType": "bit"},
{"name": "补水高液位超时", "system": "bsgywdcs", "address": "40002.11", "dataType": "bit"},
{"name": "微滤池高液位超时", "system": "wlcgywdcs", "address": "40002.12", "dataType": "bit"},
{"name": "微滤机电源跳闸", "system": "wljdytz", "address": "40002.13", "dataType": "bit"},
{"name": "紫外杀菌灯跳闸", "system": "zwsjd_tz", "address": "40002.14", "dataType": "bit"},
{"name": "溶氧超限报警", "system": "rycxbj", "address": "40002.15", "dataType": "bit"},
{"name": "微滤池低液位长时间不消失报警", "system": "wlcdywbcsbj", "address": "40002.16", "dataType": "bit"},
{"name": "溶氧值", "system": "ry", "address": "40003-40004", "dataType": "float32"},
{"name": "温度值", "system": "wd", "address": "40005-40006", "dataType": "float32"},
{"name": "电能值", "system": "dn", "address": "40007-40008", "dataType": "float32"},
{"name": "当前氧锥泵运行台数", "system": "dqyzb", "address": "40009", "dataType": "int16"},
{"name": "氧锥泵1运行时间", "system": "yzb1_sj", "address": "40011-40012", "dataType": "int32"},
{"name": "氧锥泵2运行时间", "system": "yzb2_sj", "address": "40013-40014", "dataType": "int32"},
{"name": "氧锥泵3运行时间", "system": "yzb3_sj", "address": "40015-40016", "dataType": "int32"},
{"name": "氧锥泵4运行时间", "system": "yzb4_sj", "address": "40017-40018", "dataType": "int32"},
{"name": "生化池水温", "system": "shcsw", "address": "40019-40020", "dataType": "float32"},
{"name": "循环水泵故障", "system": "xhsb_gz", "address": "40021.01", "dataType": "bit"},
{"name": "生化池水温低限报警", "system": "shcsw_dx_bj", "address": "40021.02", "dataType": "bit"},
{"name": "生化池水温高限报警", "system": "shcsw_gx_bj", "address": "40021.03", "dataType": "bit"},
{"name": "排污阀1开OR关", "system": "pwf1_or", "address": "40051.01", "dataType": "bit"},
{"name": "排污阀2开OR关", "system": "pwf2_or", "address": "40051.02", "dataType": "bit"},
{"name": "排污阀3开OR关", "system": "pwf3_or", "address": "40051.03", "dataType": "bit"},
{"name": "排污阀4开OR关", "system": "pwf4_or", "address": "40051.04", "dataType": "bit"},
{"name": "排污阀5开OR关", "system": "pwf5_or", "address": "40051.05", "dataType": "bit"},
{"name": "排污阀6开OR关", "system": "pwf6_or", "address": "40051.06", "dataType": "bit"},
{"name": "排污阀7开OR关", "system": "pwf7_or", "address": "40051.07", "dataType": "bit"},
{"name": "排污阀8开OR关", "system": "pwf8_or", "address": "40051.08", "dataType": "bit"},
{"name": "水泵1启动", "system": "sb1_qd", "address": "40051.09", "dataType": "bit"},
{"name": "水泵2启动", "system": "sb2_qd", "address": "40051.10", "dataType": "bit"},
{"name": "氧锥泵1启动", "system": "yzb1_qd", "address": "40051.11", "dataType": "bit"},
{"name": "氧锥泵2启动", "system": "yzb2_qd", "address": "40051.12", "dataType": "bit"},
{"name": "氧锥泵3启动", "system": "yzb3_qd", "address": "40051.13", "dataType": "bit"},
{"name": "氧锥泵4启动", "system": "yzb4_qd", "address": "40051.14", "dataType": "bit"},
{"name": "排污泵启动", "system": "pwb_qd", "address": "40051.15", "dataType": "bit"},
{"name": "水泵1停止", "system": "sb1_tz", "address": "40052.01", "dataType": "bit"},
{"name": "水泵2停止", "system": "sb2_tz", "address": "40052.02", "dataType": "bit"},
{"name": "氧锥泵1停止", "system": "yzb1_tz", "address": "40052.03", "dataType": "bit"},
{"name": "氧锥泵2停止", "system": "yzb2_tz", "address": "40052.04", "dataType": "bit"},
{"name": "氧锥泵3停止", "system": "yzb3_tz", "address": "40052.05", "dataType": "bit"},
{"name": "氧锥泵4停止", "system": "yzb4_tz", "address": "40052.06", "dataType": "bit"},
{"name": "排污泵停止", "system": "pwb_tz", "address": "40052.07", "dataType": "bit"},
{"name": "清报警", "system": "qbj", "address": "40052.09", "dataType": "bit"},
{"name": "累计时间清零", "system": "lj_sjql", "address": "40052.10", "dataType": "bit"},
{"name": "溶氧上限报警设定值", "system": "ry_sxsz", "address": "40053-40054", "dataType": "float32"},
{"name": "溶氧下限报警设定值", "system": "ry_xxsz", "address": "40055-40056", "dataType": "float32"}
]
},
{
"id": 3,
"systemName": "源水处理区",
"protocolType": "TCP",
"connectConfig": { "host": "192.168.2.5", "port": 2004},
"points": [
{"name": "自动", "system": "zd", "address": "10001", "dataType": "bit"},
{"name": "远程", "system": "yc", "address": "10002", "dataType": "bit"},
{"name": "水源泵1启动", "system": "syp1", "address": "10003", "dataType": "bit"},
{"name": "水源泵2启动", "system": "syp2", "address": "10004", "dataType": "bit"},
{"name": "水源泵3启动", "system": "syp3", "address": "10005", "dataType": "bit"},
{"name": "风机1启动", "system": "fj1", "address": "10006", "dataType": "bit"},
{"name": "风机2启动", "system": "fj2", "address": "10007", "dataType": "bit"},
{"name": "紫外灯电源合闸", "system": "zw", "address": "10008", "dataType": "bit"},
{"name": "生化池高液位", "system": "shg", "address": "10009", "dataType": "bit"},
{"name": "生化池低液位", "system": "shd", "address": "10010", "dataType": "bit"},
{"name": "水源泵1故障", "system": "syp1g", "address": "40001.01", "dataType": "bit"},
{"name": "水源泵2故障", "system": "syp2g", "address": "40001.02", "dataType": "bit"},
{"name": "水源泵3故障", "system": "syp3g", "address": "40001.03", "dataType": "bit"},
{"name": "风机1故障", "system": "fj1g", "address": "40001.04", "dataType": "bit"},
{"name": "风机2故障", "system": "fj2g", "address": "40001.05", "dataType": "bit"},
{"name": "紫外杀菌等跳闸故障", "system": "zwg", "address": "40001.06", "dataType": "bit"},
{"name": "电能值", "system": "dn", "address": "40007-40008", "dataType": "float"},
{"name": "当前水源泵启动台数", "system": "dqsy", "address": "40009", "dataType": "int"},
{"name": "当前风机运行台数", "system": "dqfj", "address": "40010", "dataType": "int"},
{"name": "水源泵1启动时间", "system": "syp1sj", "address": "40011-40012", "dataType": "long"},
{"name": "水源泵2启动时间", "system": "syp2sj", "address": "40013-40014", "dataType": "long"},
{"name": "水源泵3启动时间", "system": "syp3sj", "address": "40015-40016", "dataType": "long"},
{"name": "风机1启动时间", "system": "fj1sj", "address": "40017-40018", "dataType": "long"},
{"name": "风机2启动时间", "system": "fj2sj", "address": "40019-40020", "dataType": "long"},
{"name": "补水阀1开OR关", "system": "bsf1", "address": "40051.01", "dataType": "bit"},
{"name": "补水阀2开OR关", "system": "bsf2", "address": "40051.02", "dataType": "bit"},
{"name": "水源泵1启动", "system": "syp1s", "address": "40051.01", "dataType": "bit"},
{"name": "水源泵2启动", "system": "syp2s", "address": "40051.02", "dataType": "bit"},
{"name": "水源泵3启动", "system": "syp3s", "address": "40051.03", "dataType": "bit"},
{"name": "风机1启动", "system": "fj1s", "address": "40051.04", "dataType": "bit"},
{"name": "风机2启动", "system": "fj2s", "address": "40051.05", "dataType": "bit"},
{"name": "水源泵1停止", "system": "syp1t", "address": "40051.09", "dataType": "bit"},
{"name": "水源泵2停止", "system": "syp2t", "address": "40051.10", "dataType": "bit"},
{"name": "水源泵3停止", "system": "syp3t", "address": "40051.11", "dataType": "bit"},
{"name": "风机1停止", "system": "fj1t", "address": "40051.12", "dataType": "bit"},
{"name": "风机2停止", "system": "fj2t", "address": "40051.13", "dataType": "bit"},
{"name": "清报警", "system": "qbj", "address": "40052.01", "dataType": "bit"},
{"name": "累计时间清零", "system": "ljsj", "address": "40052.02", "dataType": "bit"}
]
},
{
"id": 4,
"systemName": "育苗系统",
"protocolType": "TCP",
"connectConfig": { "host": "192.168.2.4", "port": 2002},
"points": [
{"name": "手动/自动", "system": "sdz", "address": "10001", "dataType": "bit"},
{"name": "本地/远程", "system": "bdyy", "address": "10002", "dataType": "bit"},
{"name": "水泵1运行", "system": "sb1", "address": "10004", "dataType": "bit"},
{"name": "水泵2运行", "system": "sb2", "address": "10005", "dataType": "bit"},
{"name": "风机1运行", "system": "fj1", "address": "10006", "dataType": "bit"},
{"name": "风机2运行", "system": "fj2", "address": "10007", "dataType": "bit"},
{"name": "热源泵1电源合闸", "system": "ryb1", "address": "10008", "dataType": "bit"},
{"name": "热源泵2电源合闸", "system": "ryb2", "address": "10009", "dataType": "bit"},
{"name": "微滤机电源合闸", "system": "wlj", "address": "10010", "dataType": "bit"},
{"name": "紫外灯电源合闸", "system": "zwd", "address": "10011", "dataType": "bit"},
{"name": "补水池高液位", "system": "bsc", "address": "10012", "dataType": "bit"},
{"name": "微滤池高液位", "system": "wlq", "address": "10013", "dataType": "bit"},
{"name": "溶氧超限报警", "system": "rycj", "address": "10015", "dataType": "bit"},
{"name": "微滤池低液位", "system": "wld", "address": "10015", "dataType": "bit"},
{"name": "微滤池低液位长时间不消失报警", "system": "wldc", "address": "10016", "dataType": "bit"},
{"name": "系统报警", "system": "xtbj", "address": "00001", "dataType": "bit"},
{"name": "溶氧值", "system": "ryz", "address": "40003-40004", "dataType": "float32"},
{"name": "温度值", "system": "wdz", "address": "40005-40006", "dataType": "float32"},
{"name": "电能值", "system": "dnz", "address": "40007-40008", "dataType": "float32"},
{"name": "当前风机运行台数", "system": "dqfj", "address": "40009", "dataType": "int32"},
{"name": "风机1运行时间", "system": "fj1sj", "address": "40011-40012", "dataType": "int64"},
{"name": "风机2运行时间", "system": "fj2sj", "address": "40013-40014", "dataType": "int64"},
{"name": "水泵1启动", "system": "sb1start", "address": "40051.01", "dataType": "bit"},
{"name": "水泵2启动", "system": "sb2start", "address": "40051.02", "dataType": "bit"},
{"name": "风机1启动", "system": "fj1start", "address": "40051.03", "dataType": "bit"},
{"name": "风机2启动", "system": "fj2start", "address": "40051.04", "dataType": "bit"},
{"name": "补水泵3启动", "system": "bsp3start", "address": "40051.05", "dataType": "bit"},
{"name": "水泵1停止", "system": "sb1stop", "address": "40051.09", "dataType": "bit"},
{"name": "水泵2停止", "system": "sb2stop", "address": "40051.10", "dataType": "bit"},
{"name": "风机1停止", "system": "fj1stop", "address": "40051.11", "dataType": "bit"},
{"name": "风机2停止", "system": "fj2stop", "address": "40051.12", "dataType": "bit"},
{"name": "补水泵3停止", "system": "bsp3stop", "address": "40051.13", "dataType": "bit"},
{"name": "清报警", "system": "qbj", "address": "40052.01", "dataType": "bit"},
{"name": "累计时间清零", "system": "ljsjql", "address": "40052.02", "dataType": "bit"},
{"name": "溶氧上限报警设定值", "system": "rysjup", "address": "40053-40054", "dataType": "float32"},
{"name": "溶氧下限报警设定值", "system": "rysjdown", "address": "40055-40056", "dataType": "float32"}
]
},
{
"id": 5,
"systemName": "设备房系统",
"protocolType": "TCP",
"connectConfig": { "host": "192.168.2.3", "port": 2003},
"points": [
{"name": "自动", "system": "zd", "address": "10001", "dataType": "bit"},
{"name": "远程", "system": "yc", "address": "10002", "dataType": "bit"},
{"name": "风机1运行", "system": "fj1", "address": "10003", "dataType": "bit"},
{"name": "风机2运行", "system": "fj2", "address": "10004", "dataType": "bit"},
{"name": "风机3运行", "system": "fj3", "address": "10005", "dataType": "bit"},
{"name": "风机4运行", "system": "fj4", "address": "10006", "dataType": "bit"},
{"name": "补水泵1运行", "system": "bsb1", "address": "10007", "dataType": "bit"},
{"name": "补水泵2运行", "system": "bsb2", "address": "10008", "dataType": "bit"},
{"name": "热泵1电源合闸", "system": "rb1", "address": "10010", "dataType": "bit"},
{"name": "热泵2电源合闸", "system": "rb2", "address": "10011", "dataType": "bit"},
{"name": "空压机电源合闸", "system": "kyj", "address": "10012", "dataType": "bit"},
{"name": "补水阀1开到位", "system": "bsf1", "address": "10013", "dataType": "bit"},
{"name": "补水阀2开到位", "system": "bsf2", "address": "10014", "dataType": "bit"},
{"name": "补水阀1关到位", "system": "bsf1g", "address": "10016", "dataType": "bit"},
{"name": "补水阀2关到位", "system": "bsf2g", "address": "10017", "dataType": "bit"},
{"name": "补水1高液位", "system": "bsg1", "address": "10019", "dataType": "bit"},
{"name": "补水2高液位", "system": "bsg2", "address": "10020", "dataType": "bit"},
{"name": "系统报警", "system": "bj", "address": "00001", "dataType": "bit"},
{"name": "风机1故障", "system": "fj1g", "address": "40001.01", "dataType": "bit"},
{"name": "风机2故障", "system": "fj2g", "address": "40001.02", "dataType": "bit"},
{"name": "风机3故障", "system": "fj3g", "address": "40001.03", "dataType": "bit"},
{"name": "风机4故障", "system": "fj4g", "address": "40001.04", "dataType": "bit"},
{"name": "补水泵1故障", "system": "bsb1g", "address": "40001.05", "dataType": "bit"},
{"name": "补水泵2故障", "system": "bsb2g", "address": "40001.06", "dataType": "bit"},
{"name": "热泵1跳闸故障", "system": "rb1g", "address": "40001.08", "dataType": "bit"},
{"name": "热泵2跳闸故障", "system": "rb2g", "address": "40001.09", "dataType": "bit"},
{"name": "补水阀1开不到位", "system": "bsf1b", "address": "40001.10", "dataType": "bit"},
{"name": "补水阀1关不到位", "system": "bsf1bg", "address": "40001.11", "dataType": "bit"},
{"name": "补水阀2开不到位", "system": "bsf2b", "address": "40001.12", "dataType": "bit"},
{"name": "补水阀2关不到位", "system": "bsf2bg", "address": "40001.13", "dataType": "bit"},
{"name": "空压机跳闸故障", "system": "kyjg", "address": "40001.16", "dataType": "bit"},
{"name": "电能值", "system": "dnz", "address": "40007-40008", "dataType": "float"},
{"name": "当前风机运行台数", "system": "dqfj", "address": "40009", "dataType": "int"},
{"name": "风机1运行时间", "system": "fj1t", "address": "40011-40012", "dataType": "long"},
{"name": "风机2运行时间", "system": "fj2t", "address": "40013-40014", "dataType": "long"},
{"name": "风机3运行时间", "system": "fj3t", "address": "40015-40016", "dataType": "long"},
{"name": "风机4运行时间", "system": "fj4t", "address": "40017-40018", "dataType": "long"},
{"name": "补水阀1开OR关", "system": "bsf1c", "address": "40051.01", "dataType": "bit"},
{"name": "补水阀2开OR关", "system": "bsf2c", "address": "40051.02", "dataType": "bit"},
{"name": "风机1启动", "system": "fj1s", "address": "40051.09", "dataType": "bit"},
{"name": "风机2启动", "system": "fj2s", "address": "40051.10", "dataType": "bit"},
{"name": "风机3启动", "system": "fj3s", "address": "40051.11", "dataType": "bit"},
{"name": "风机4启动", "system": "fj4s", "address": "40051.12", "dataType": "bit"},
{"name": "补水泵1启动", "system": "bsb1s", "address": "40051.13", "dataType": "bit"},
{"name": "补水泵2启动", "system": "bsb2s", "address": "40051.14", "dataType": "bit"},
{"name": "风机1停止", "system": "fj1p", "address": "40052.01", "dataType": "bit"},
{"name": "风机2停止", "system": "fj2p", "address": "40052.02", "dataType": "bit"},
{"name": "风机3停止", "system": "fj3p", "address": "40052.03", "dataType": "bit"},
{"name": "风机4停止", "system": "fj4p", "address": "40052.04", "dataType": "bit"},
{"name": "补水泵1停止", "system": "bsb1p", "address": "40052.05", "dataType": "bit"},
{"name": "补水泵2停止", "system": "bsb2p", "address": "40052.06", "dataType": "bit"},
{"name": "清报警", "system": "qbj", "address": "40052.09", "dataType": "bit"},
{"name": "累计时间清零", "system": "ljtq", "address": "40052.10", "dataType": "bit"}
]
}
]
}
... ...
<?xml version="1.0" encoding="UTF-8"?>
<assembly>
<id>bin</id>
<!-- 最终打包成一个用于发布的zip文件 -->
<formats>
<format>zip</format>
</formats>
<!-- Adds dependencies to zip package under lib directory -->
<dependencySets>
<dependencySet>
<!--
不使用项目的artifact,第三方jar不要解压,打包进zip文件的lib目录
-->
<useProjectArtifact>false</useProjectArtifact>
<outputDirectory>lib</outputDirectory>
<unpack>false</unpack>
</dependencySet>
</dependencySets>
<fileSets>
<!-- 把项目相关的说明文件,打包进zip文件的根目录 -->
<fileSet>
<directory>${project.basedir}</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>README*</include>
<include>LICENSE*</include>
<include>NOTICE*</include>
</includes>
</fileSet>
<!-- 把项目的配置文件,打包进zip文件的config目录 -->
<fileSet>
<directory>${project.basedir}\src\main\resources\configs</directory>
<outputDirectory>../configs</outputDirectory>
<includes>
<include>*.properties</include>
</includes>
</fileSet>
<!-- 把项目的配置文件,提出来 -->
<fileSet>
<directory>${project.basedir}\src\main\resources</directory>
<outputDirectory>/</outputDirectory>
<includes>
<include>*.properties</include>
<include>*.yml</include>
</includes>
</fileSet>
<!-- 把项目的脚本文件目录( src/main/scripts )中的启动脚本文件,打包进zip文件的跟目录 -->
<fileSet>
<directory>${project.basedir}\bin</directory>
<outputDirectory></outputDirectory>
<includes>
<include>start.*</include>
<include>stop.*</include>
</includes>
</fileSet>
<!-- 把项目自己编译出来的jar文件,打包进zip文件的根目录 -->
<fileSet>
<directory>${project.build.directory}</directory>
<outputDirectory></outputDirectory>
<includes>
<include>*.jar</include>
</includes>
</fileSet>
</fileSets>
</assembly>
\ No newline at end of file
... ...
import com.alibaba.fastjson.JSONObject;
import com.zhonglai.luhui.device.modbus.terminal.config.InitPlcConfig;
import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jRead;
import com.zhonglai.luhui.device.modbus.terminal.modbus.Modbus4jWrite;
import com.zhonglai.luhui.device.modbus.terminal.modbus.dto.PlcPoint;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class TestModbus {
public static void main(String[] args) throws Exception {
args = new String[]{"E:\\work\\idea\\Luhui\\lh-modules\\lh-device-modbus-terminal\\src\\main\\resources\\configs\\plcs.json","false","测试", "ry_sxsz=3.16","ry_xxsz=2.1"};
if (args.length < 2) {
System.out.println("用法: java -jar modbus-app.jar <plcs.json路径> <点位名1> <点位名2> ...");
return;
}
// testRead(args);
testWrite(args);
}
private static void testRead(String[] args) throws Exception {
String jsonPath = args[0];
boolean zeroBasedAddress = new Boolean(args[1]);
Integer id = Integer.parseInt(args[2]);
List<String> pointNames = Arrays.asList(Arrays.copyOfRange(args, 3, args.length));
InitPlcConfig.initPlcConfigFromFile(jsonPath);
List<PlcPoint> plcPoints = new ArrayList<>();
for (String pointName :pointNames)
{
PlcPoint plcPoint = InitPlcConfig.getPlcSystem(id, pointName);
if ( null != plcPoint)
{
plcPoints.add(plcPoint);
}
}
Map<String, Object> map = new Modbus4jRead(id).batchRead(plcPoints,zeroBasedAddress);
System.out.println(JSONObject.toJSONString(map));
}
private static void testWrite(String[] args) throws Exception
{
String jsonPath = args[0];
boolean zeroBasedAddress = new Boolean(args[1]);
Integer id = Integer.parseInt(args[2]);
List<String> pointNames = Arrays.asList(Arrays.copyOfRange(args, 3, args.length));
InitPlcConfig.initPlcConfigFromFile(jsonPath);
List<PlcPoint> plcPoints = new ArrayList<>();
for (String pointName :pointNames)
{
String[] pointNameArr = pointName.split("=");
PlcPoint plcPoint = InitPlcConfig.getPlcSystem(id, pointNameArr[0]);
if ( null != plcPoint)
{
plcPoint.setValue(pointNameArr[1]);
plcPoints.add(plcPoint);
}
}
new Modbus4jWrite(id).batchWrite(plcPoints,zeroBasedAddress);
}
}
... ...
... ... @@ -62,6 +62,27 @@ public class DeviceCommandListenServiceImpl implements DeviceCommandServiceFacto
return noticeMessageDomain;
}
@Override
public NoticeMessageDto host(String deviceId, JsonObject jsonObject) {
ParserDeviceHostDto parserDeviceHostDto = DeviceCach.getDeviceHost(deviceId);
Topic topic = new Topic();
topic.setTopicType("HOST");
topic.setClientid(deviceId);
topic.setRoleid(parserDeviceHostDto.getIotProduct().getRole_id()+"");
topic.setUsername(parserDeviceHostDto.getIotProduct().getMqtt_username());
topic.setPayloadtype("Json");
topic.setMessageid(com.ruoyi.common.utils.DateUtils.getNowTimeMilly()+"");
NoticeMessageDto noticeMessageDto = new NoticeMessageDto();
noticeMessageDto.setTopic(topic);
noticeMessageDto.setTopicconfig(topicconfig);
noticeMessageDto.setCommd(jsonObject.toString().getBytes());
return noticeMessageDto;
}
private Topic getWriteTopic(String deviceId, IotProduct iotProduct)
{
... ...
... ... @@ -49,6 +49,27 @@ public class DeviceCommandListenServiceImpl implements DeviceCommandServiceFacto
return null;
}
@Override
public NoticeMessageDto host(String deviceId, JsonObject jsonObject) {
ParserDeviceHostDto parserDeviceHostDto = DeviceCach.getDeviceHost(deviceId);
Topic topic = new Topic();
topic.setTopicType("HOST");
topic.setClientid(deviceId);
topic.setRoleid(parserDeviceHostDto.getIotProduct().getRole_id()+"");
topic.setUsername(parserDeviceHostDto.getIotProduct().getMqtt_username());
topic.setPayloadtype("Json");
topic.setMessageid(DateUtils.getNowTimeMilly()+"");
NoticeMessageDto noticeMessageDto = new NoticeMessageDto();
noticeMessageDto.setTopic(topic);
noticeMessageDto.setTopicconfig(topicModel);
noticeMessageDto.setCommd(jsonObject.toString().getBytes());
return noticeMessageDto;
}
private Topic getWriteTopic(String deviceId, IotProduct iotProduct)
{
Topic topic = new Topic();
... ...
... ... @@ -55,6 +55,27 @@ public class DeviceCommandListenServiceImpl implements DeviceCommandServiceFacto
return null;
}
@Override
public NoticeMessageDto host(String deviceId, JsonObject jsonObject) {
ParserDeviceHostDto parserDeviceHostDto = DeviceCach.getDeviceHost(deviceId);
Topic topic = new Topic();
topic.setTopicType("HOST");
topic.setClientid(deviceId);
topic.setRoleid(parserDeviceHostDto.getIotProduct().getRole_id()+"");
topic.setUsername(parserDeviceHostDto.getIotProduct().getMqtt_username());
topic.setPayloadtype("Json");
topic.setMessageid(DateUtils.getNowTimeMilly()+"");
NoticeMessageDto noticeMessageDto = new NoticeMessageDto();
noticeMessageDto.setTopic(topic);
noticeMessageDto.setTopicconfig(topicModel);
noticeMessageDto.setCommd(jsonObject.toString().getBytes());
return noticeMessageDto;
}
private Topic getWriteTopic(String deviceId, IotProduct iotProduct)
{
Topic topic = new Topic();
... ...
... ... @@ -22,5 +22,9 @@
<groupId>com.zhonglai.luhui</groupId>
<artifactId>lh-device-protocol-factory</artifactId>
</dependency>
<dependency>
<groupId>com.infiniteautomation</groupId>
<artifactId>modbus4j</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
... ...
package com.zhonglai.luhui.device.protocol.modbus.modbus;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.serotonin.modbus4j.msg.*;
import java.io.File;
import java.util.*;
/**
* 通用点位解析类
*/
public class ModbusPointReader {
private ModbusMaster master;
private List<PointConfig> points;
public ModbusPointReader(String ip, int port, String configPath) throws Exception {
// 初始化 Modbus Master
IpParameters params = new IpParameters();
params.setHost(ip);
params.setPort(port);
ModbusFactory factory = new ModbusFactory();
master = factory.createTcpMaster(params, false);
master.init();
// 加载配置文件(JSON)
ObjectMapper mapper = new ObjectMapper();
points = Arrays.asList(mapper.readValue(new File(configPath), PointConfig[].class));
}
public void readAll() throws Exception {
for (PointConfig p : points) {
Object value = readPoint(p);
System.out.printf("设备: %s | 点位: %s | 值: %s%n",
p.device, p.name, value);
}
}
private Object readPoint(PointConfig p) throws Exception {
int slaveId = 1; // 您可以根据需要修改,这里使用固定的1
switch (p.type) {
case "coil":
ReadCoilsRequest coilsRequest = new ReadCoilsRequest(slaveId, p.address, p.length);
ReadCoilsResponse coilsResponse = (ReadCoilsResponse) master.send(coilsRequest);
return coilsResponse.getBooleanData()[0];
case "discreteInput":
ReadDiscreteInputsRequest discreteRequest = new ReadDiscreteInputsRequest(slaveId, p.address, p.length);
ReadDiscreteInputsResponse discreteResponse = (ReadDiscreteInputsResponse) master.send(discreteRequest);
return discreteResponse.getBooleanData()[0];
case "holdingRegister":
ReadHoldingRegistersRequest holdingRequest = new ReadHoldingRegistersRequest(slaveId, p.address, p.length);
ReadHoldingRegistersResponse holdingResponse = (ReadHoldingRegistersResponse) master.send(holdingRequest);
return parseRegister(holdingResponse.getShortData(), p);
case "inputRegister":
ReadInputRegistersRequest inputRequest = new ReadInputRegistersRequest(slaveId, p.address, p.length);
ReadInputRegistersResponse inputResponse = (ReadInputRegistersResponse) master.send(inputRequest);
return parseRegister(inputResponse.getShortData(), p);
default:
throw new IllegalArgumentException("未知点位类型: " + p.type);
}
}
private Object parseRegister(short[] regs, PointConfig p) {
switch (p.datatype) {
case "bool":
return ((regs[0] >> p.bit) & 1) == 1;
case "int16":
return regs[0];
case "int32":
return (regs[0] << 16) | (regs[1] & 0xFFFF);
case "float":
int raw = (regs[0] << 16) | (regs[1] & 0xFFFF);
return Float.intBitsToFloat(raw);
case "double":
long raw64 = ((long) regs[0] << 48) | ((long) regs[1] << 32) | ((long) regs[2] << 16) | (regs[3] & 0xFFFF);
return Double.longBitsToDouble(raw64);
default:
return regs[0];
}
}
public void close() {
if (master != null) master.destroy();
}
// 使用示例
public static void main(String[] args) throws Exception {
ModbusPointReader reader = new ModbusPointReader("192.168.2.11", 2000, "points.json");
reader.readAll();
reader.close();
}
}
... ...
package com.zhonglai.luhui.device.protocol.modbus.modbus;
/**
*点位配置类
*/
public class PointConfig {
public String device;
public String name;
public String type;
public int address;
public int length;
public String datatype;
public int bit = 0;
}
\ No newline at end of file
... ...
... ... @@ -3,6 +3,7 @@ package com.zhonglai.luhui.device.protocol.plc004.control;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.GsonConstructor;
import com.zhonglai.luhui.device.analysis.comm.factory.Topic;
import com.zhonglai.luhui.device.domain.IotProduct;
... ... @@ -50,6 +51,27 @@ public class DeviceCommandListenServiceImpl implements DeviceCommandServiceFacto
return null;
}
@Override
public NoticeMessageDto host(String deviceId, JsonObject jsonObject) {
ParserDeviceHostDto parserDeviceHostDto = DeviceCach.getDeviceHost(deviceId);
Topic topic = new Topic();
topic.setTopicType("HOST");
topic.setClientid(deviceId);
topic.setRoleid(parserDeviceHostDto.getIotProduct().getRole_id()+"");
topic.setUsername(parserDeviceHostDto.getIotProduct().getMqtt_username());
topic.setPayloadtype("Json");
topic.setMessageid(DateUtils.getNowTimeMilly()+"");
NoticeMessageDto noticeMessageDto = new NoticeMessageDto();
noticeMessageDto.setTopic(topic);
noticeMessageDto.setTopicconfig(topicModel);
noticeMessageDto.setCommd(jsonObject.toString().getBytes());
return noticeMessageDto;
}
/**
* 将嵌套的JsonObject扁平化。
*
... ...
... ... @@ -52,6 +52,27 @@ public class DeviceCommandListenServiceImpl implements DeviceCommandServiceFacto
return null;
}
@Override
public NoticeMessageDto host(String deviceId, JsonObject jsonObject) {
ParserDeviceHostDto parserDeviceHostDto = DeviceCach.getDeviceHost(deviceId);
Topic topic = new Topic();
topic.setTopicType("HOST");
topic.setClientid(deviceId);
topic.setRoleid(parserDeviceHostDto.getIotProduct().getRole_id()+"");
topic.setUsername(parserDeviceHostDto.getIotProduct().getMqtt_username());
topic.setPayloadtype("Json");
topic.setMessageid(com.ruoyi.common.utils.DateUtils.getNowTimeMilly()+"");
NoticeMessageDto noticeMessageDto = new NoticeMessageDto();
noticeMessageDto.setTopic(topic);
noticeMessageDto.setTopicconfig(topicconfig);
noticeMessageDto.setCommd(jsonObject.toString().getBytes());
return noticeMessageDto;
}
private Topic getWriteTopic(String deviceId, IotProduct iotProduct)
{
... ...
... ... @@ -146,6 +146,20 @@ public class DeviceCommandListenService implements RocketMQReplyListener<Message
}else {
return new Message(MessageCode.DEFAULT_FAIL_CODE,"指令发送失败");
}
case host:
noticeMessageDomain = deviceCommandServiceFactory.host(deviceCommand.getDeviceId(), jsonObject);
if(null == noticeMessageDomain)
{
return new Message(MessageCode.DEFAULT_FAIL_CODE,"该设备不支持主机操作功能");
}
if(clienNoticeServiceFactory.sendMessage(noticeMessageDomain))
{
return new Message(MessageCode.DEFAULT_SUCCESS_CODE,"指令发送成功");
}else {
return new Message(MessageCode.DEFAULT_FAIL_CODE,"指令发送失败");
}
default:
return new Message(MessageCode.DEFAULT_FAIL_CODE,"指令类型不存在,请联系管理员");
}
... ...
... ... @@ -11,4 +11,6 @@ public interface DeviceCommandServiceFactory {
NoticeMessageDto write(String deviceId, JsonObject jsonObject);
NoticeMessageDto notice(String deviceId, JsonObject jsonObject);
NoticeMessageDto host(String deviceId, JsonObject jsonObject);
}
... ...
... ... @@ -41,5 +41,10 @@ public enum CommandType {
/**
* 删除订阅
*/
delSubscribe
delSubscribe,
/**
* 主机操作
*/
host
}
... ...
... ... @@ -11,9 +11,12 @@ import com.zhonglai.luhui.device.analysis.comm.dto.thingsmodels.ThingsModelItemB
import com.zhonglai.luhui.device.analysis.comm.factory.Topic;
import com.zhonglai.luhui.device.analysis.comm.util.DateUtils;
import com.zhonglai.luhui.device.domain.IotThingsModel;
import com.zhonglai.luhui.device.protocol.factory.control.DeviceCommandListenService;
import com.zhonglai.luhui.device.protocol.factory.dto.*;
import com.zhonglai.luhui.device.protocol.factory.service.IotThingsModelService;
import org.apache.commons.lang3.EnumUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
... ... @@ -129,7 +132,7 @@ public class DefaultProtocolPurificationFactoryImpl implements ProtocolPurificat
if(null != jsonElement && !jsonElement.isJsonNull() )
{
ThingsModelItemBase thingsModelItemBase = getThingsModelItemBase(thingsModel,jsonElement);
if(!thingsModelItemBase.checkValue())
if(null == thingsModelItemBase || !thingsModelItemBase.checkValue())
{
continue;
}
... ... @@ -170,16 +173,31 @@ public class DefaultProtocolPurificationFactoryImpl implements ProtocolPurificat
return null;
}
private ThingsModelItemBase getThingsModelItemBase(IotThingsModel thingsModel,JsonElement jsonElement)
{
String data_type = thingsModel.getData_type().toUpperCase();
if(!EnumUtils.isValidEnum(ThingsModelDataTypeEnum.class,data_type))
{
data_type = ThingsModelDataTypeEnum.STRING.name();
private ThingsModelItemBase getThingsModelItemBase(IotThingsModel thingsModel, JsonElement jsonElement) {
String dataType = thingsModel.getData_type();
ThingsModelDataTypeEnum thingsModelDataTypeEnum;
try {
if (dataType == null) {
thingsModelDataTypeEnum = ThingsModelDataTypeEnum.STRING;
} else {
// 统一转大写再校验
String upperType = dataType.toUpperCase();
if (EnumUtils.isValidEnum(ThingsModelDataTypeEnum.class, upperType)) {
thingsModelDataTypeEnum = Enum.valueOf(ThingsModelDataTypeEnum.class, upperType);
} else {
thingsModelDataTypeEnum = ThingsModelDataTypeEnum.STRING;
}
}
} catch (IllegalArgumentException | NullPointerException e) {
// 容错处理,回退到 STRING
thingsModelDataTypeEnum = ThingsModelDataTypeEnum.STRING;
}
return ThingsModelItemBase.newhingsModel(Enum.valueOf(ThingsModelDataTypeEnum.class,data_type),thingsModel, jsonElement);
return ThingsModelItemBase.newhingsModel(thingsModelDataTypeEnum, thingsModel, jsonElement);
}
private DeviceSensorData getDeviceSensorData(Integer time,String sensorNumber,Topic topic,IotThingsModel thingsModel,ThingsModelItemBase thingsModelItemBase)
{
DeviceSensorData sensorData = new DeviceSensorData();
... ...
... ... @@ -38,6 +38,7 @@
<module>lh-ssh-service-lesten</module>
<module>lh-deviceInfo-sync</module>
<module>lh-camera</module>
<module>lh-device-modbus-terminal</module>
</modules>
<properties>
... ...
... ... @@ -390,6 +390,12 @@
<artifactId>lh-jar-ssh-proxy</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<!-- mqtt终端插件 -->
<dependency>
<groupId>com.zhonglai.luhui</groupId>
<artifactId>lh-device-mqtt-terminal-jar</artifactId>
<version>${ruoyi.version}</version>
</dependency>
<!-- 支持data -->
<dependency>
<groupId>org.projectlombok</groupId>
... ... @@ -627,6 +633,24 @@
<artifactId>tess4j</artifactId>
<version>5.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.infiniteautomation/modbus4j -->
<dependency>
<groupId>com.infiniteautomation</groupId>
<artifactId>modbus4j</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>2.11.2</version>
</dependency>
<dependency>
<groupId>org.ini4j</groupId>
<artifactId>ini4j</artifactId>
<version>0.5.4</version>
</dependency>
</dependencies>
... ...
# 项目相关配置 jhlt: # 名称 name: zhonglai # 版本 version: 3.8.2 # 版权年份 copyrightYear: 2024 # 实例演示开关 demoEnabled: true # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) profile: uploadPath # 开发环境配置 server: # 服务器的HTTP端口,默认为8080 port: 8086 servlet: # 应用的访问路径 context-path: / tomcat: # tomcat的URI编码 uri-encoding: UTF-8 # 连接数满后的排队数,默认为100 accept-count: 1000 threads: # tomcat最大线程数,默认为200 max: 800 # Tomcat启动初始化的线程数,默认值10 min-spare: 100 # 日志配置 logging: level: com.ruoyi: debug org.springframework: warn # Spring配置 spring: # 资源信息 messages: # 国际化资源文件路径 basename: i18n/messages profiles: active: druid # 文件上传 servlet: multipart: # 单个文件大小 max-file-size: 10MB # 设置总上传的文件大小 max-request-size: 20MB # 服务模块 devtools: restart: # 热部署开关 enabled: true # web: # resources: # static-locations: classpath:/static/, classpath:/templates/ # token配置 token: # 令牌自定义标识 header: Authorization # 令牌有效期(默认30分钟) expireTime: 31536000 # MyBatis配置 mybatis: # 搜索指定包别名 typeAliasesPackage: com.ruoyi.**.domain # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapperLocations: classpath*:mapper/**/*Mapper.xml # 加载全局的配置文件 configLocation: classpath:mybatis/mybatis-config.xml # PageHelper分页插件 pagehelper: helperDialect: mysql supportMethodsArguments: true params: count=countSql # Swagger配置 swagger: # 是否开启swagger enabled: true # 请求前缀 pathMapping: /api sys: ## // 对于登录login 注册register 验证码captchaImage 允许匿名访问 antMatchers: - /doc.html - /webjars/** - /swagger-ui.html - /v2/api-docs/** - /swagger/api-docs - /token/code - /swagger-resources/** - /login/** - /profile/** chatgpt: token: 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 # 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 timeout: 5000 apiHost: https://api.openai.com/ proxy: isProxy: true host: 127.0.0.1 port: 7890
\ No newline at end of file
# 项目相关配置 jhlt: # 名称 name: zhonglai # 版本 version: 3.8.2 # 版权年份 copyrightYear: 2024 # 实例演示开关 demoEnabled: true # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath) profile: uploadPath # 开发环境配置 server: # 服务器的HTTP端口,默认为8080 port: 8086 servlet: # 应用的访问路径 context-path: / tomcat: # tomcat的URI编码 uri-encoding: UTF-8 # 连接数满后的排队数,默认为100 accept-count: 1000 threads: # tomcat最大线程数,默认为200 max: 800 # Tomcat启动初始化的线程数,默认值10 min-spare: 100 # 日志配置 logging: level: com.ruoyi: debug org.springframework: warn # Spring配置 spring: # 资源信息 messages: # 国际化资源文件路径 basename: i18n/messages profiles: active: druid # 文件上传 servlet: multipart: # 单个文件大小 max-file-size: 10MB # 设置总上传的文件大小 max-request-size: 20MB # 服务模块 devtools: restart: # 热部署开关 enabled: true # web: # resources: # static-locations: classpath:/static/, classpath:/templates/ # token配置 token: # 令牌自定义标识 header: Authorization # 令牌有效期(默认30分钟) expireTime: 31536000 # MyBatis配置 mybatis: # 搜索指定包别名 typeAliasesPackage: com.ruoyi.**.domain # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapperLocations: classpath*:mapper/**/*Mapper.xml # 加载全局的配置文件 configLocation: classpath:mybatis/mybatis-config.xml # PageHelper分页插件 pagehelper: helperDialect: mysql supportMethodsArguments: true params: count=countSql # Swagger配置 swagger: # 是否开启swagger enabled: true # 请求前缀 pathMapping: /api sys: # 对于登录login 注册register 验证码captchaImage 允许匿名访问 antMatchers: - /doc.html - /webjars/** - /swagger-ui.html - /v2/api-docs/** - /swagger/api-docs - /token/code - /swagger-resources/** - /login/** - /profile/** chatgpt: token: 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 # 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 timeout: 5000 apiHost: https://api.openai.com/ proxy: isProxy: true host: 127.0.0.1 port: 7890
\ No newline at end of file
... ...