20260116:设备下沉第一版,理解错误

This commit is contained in:
George 2026-01-16 21:48:09 +08:00
parent 268fd71b22
commit 382025e049
32 changed files with 2515 additions and 101 deletions

View File

@ -129,7 +129,7 @@ emqx:
ApiSecret: emqx.api.123 ApiSecret: emqx.api.123
# sip 配置 # sip 配置
sip: sip:
enabled: true # 是否启用视频监控SIP,true为启用 enabled: false # 是否启用视频监控SIP,true为启用
## 本地调试时,绑定网卡局域网IP,设备在同一局域网,设备接入IP填写绑定IP ## 本地调试时,绑定网卡局域网IP,设备在同一局域网,设备接入IP填写绑定IP
## 部署服务端时,默认绑定容器IP,设备接入IP填写服务器公网IP ## 部署服务端时,默认绑定容器IP,设备接入IP填写服务器公网IP
ip: 192.168.110.160 # 本服务启动的 IP ip: 192.168.110.160 # 本服务启动的 IP

View File

@ -244,6 +244,10 @@ public interface FastBeeConstant {
String TSL_PRE_KEY = "TSL:"; String TSL_PRE_KEY = "TSL:";
String MODBUS_PRE_KEY = "MODBUS:"; String MODBUS_PRE_KEY = "MODBUS:";
/**
* 设备级 modbus 缓存
*/
String MODBUS_DEVICE_PRE_KEY = "MODBUS:DEV:";
/** /**
* modbus缓存指令 * modbus缓存指令

View File

@ -65,6 +65,13 @@ public class RedisKeyBuilder {
return FastBeeConstant.REDIS.MODBUS_PRE_KEY + productId; return FastBeeConstant.REDIS.MODBUS_PRE_KEY + productId;
} }
/**
* 设备级 modbus 配置缓存 key
*/
public static String buildModbusDeviceKey(Long deviceId) {
return FastBeeConstant.REDIS.MODBUS_DEVICE_PRE_KEY + deviceId;
}
/** /**
* 录像缓存key * 录像缓存key
*/ */

View File

@ -24,6 +24,7 @@ import com.fastbee.iot.enums.DeviceType;
import com.fastbee.iot.model.DeviceStatusVO; import com.fastbee.iot.model.DeviceStatusVO;
import com.fastbee.iot.model.modbus.ModbusPollJob; import com.fastbee.iot.model.modbus.ModbusPollJob;
import com.fastbee.iot.service.IDeviceService; import com.fastbee.iot.service.IDeviceService;
import com.fastbee.iot.service.IDeviceModbusParamsService;
import com.fastbee.iot.service.IModbusJobService; import com.fastbee.iot.service.IModbusJobService;
import com.fastbee.iot.service.IModbusParamsService; import com.fastbee.iot.service.IModbusParamsService;
import com.fastbee.mq.producer.MessageProducer; import com.fastbee.mq.producer.MessageProducer;
@ -73,6 +74,8 @@ public class DevicePropFetchConsumer {
private ISessionStore sessionStore; private ISessionStore sessionStore;
@Resource @Resource
private IModbusParamsService modbusParamsService; private IModbusParamsService modbusParamsService;
@Resource
private IDeviceModbusParamsService deviceModbusParamsService;
@Value("${server.broker.enabled}") @Value("${server.broker.enabled}")
private Boolean enabled; private Boolean enabled;
@ -197,8 +200,9 @@ public class DevicePropFetchConsumer {
//如果是网关子设备,则检测子设备是否在特定时间内有数据上报 //如果是网关子设备,则检测子设备是否在特定时间内有数据上报
if (modbusJob.getDeviceType() == DeviceType.SUB_GATEWAY.getCode()) { if (modbusJob.getDeviceType() == DeviceType.SUB_GATEWAY.getCode()) {
long deterTime = 300L; //这里默认使用5分钟,如果轮询时间大于5分钟,请在配置参数设置更长时间 long deterTime = 300L; //这里默认使用5分钟,如果轮询时间大于5分钟,请在配置参数设置更长时间
ModbusParams params = modbusParamsService.getModbusParamsByDeviceId(modbusJob.getSubDeviceId()); // 优先使用设备级配置,无则回退到产品级配置
if (!Objects.isNull(params)) { ModbusParams params = deviceModbusParamsService.getEffectiveParams(modbusJob.getSubDeviceId());
if (!Objects.isNull(params) && !Objects.isNull(params.getDeterTimer())) {
deterTime = Long.parseLong(params.getDeterTimer()); deterTime = Long.parseLong(params.getDeterTimer());
} }

View File

@ -0,0 +1,76 @@
package com.fastbee.controller.modbus;
import com.fastbee.common.annotation.Log;
import com.fastbee.common.core.controller.BaseController;
import com.fastbee.common.core.domain.AjaxResult;
import com.fastbee.common.core.page.TableDataInfo;
import com.fastbee.common.enums.BusinessType;
import com.fastbee.iot.cache.IModbusConfigCache;
import com.fastbee.iot.domain.DeviceModbusConfig;
import com.fastbee.iot.model.modbus.DeviceModbusConfigVO;
import com.fastbee.iot.service.IDeviceModbusConfigService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 设备级 modbus 配置
*
* @author gpt
*/
@RestController
@RequestMapping("/deviceModbus/config")
@Api(tags = "设备modbus配置")
public class DeviceModbusConfigController extends BaseController {
@Autowired
private IDeviceModbusConfigService deviceModbusConfigService;
@Autowired
private IModbusConfigCache modbusConfigCache;
/**
* 查询设备modbus配置列表
*/
@PreAuthorize("@ss.hasPermi('modbus:config:list')")
@GetMapping("/list")
public TableDataInfo list(DeviceModbusConfig config) {
startPage();
List<DeviceModbusConfig> list = deviceModbusConfigService.selectDeviceModbusConfigList(config);
return getDataTable(list);
}
/**
* 批量新增或修改设备modbus配置
*/
@PreAuthorize("@ss.hasPermi('modbus:config:add')")
@Log(title = "设备modbus配置", businessType = BusinessType.INSERT)
@PostMapping("/addBatch")
@ApiOperation("批量新增或修改设备modbus配置")
public AjaxResult addBatch(@RequestBody DeviceModbusConfigVO vo) {
deviceModbusConfigService.addOrUpDeviceModbusConfigBatch(vo.getConfigList(), vo.getProductId(), vo.getDeviceId(), vo.getDelIds());
// 刷新设备缓存
modbusConfigCache.setModbusConfigCacheByDeviceId(vo.getProductId(), vo.getDeviceId());
return AjaxResult.success();
}
/**
* 删除设备modbus配置
*/
@PreAuthorize("@ss.hasPermi('modbus:config:remove')")
@Log(title = "设备modbus配置", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids,
@RequestParam(required = false) Long productId,
@RequestParam(required = false) Long deviceId) {
int rows = deviceModbusConfigService.deleteDeviceModbusConfigByIds(ids);
if (rows > 0 && deviceId != null) {
// 删除后刷新对应设备的寄存器缓存,避免解析仍命中旧配置
modbusConfigCache.setModbusConfigCacheByDeviceId(productId, deviceId);
}
return toAjax(rows);
}
}

View File

@ -0,0 +1,82 @@
package com.fastbee.controller.modbus;
import com.fastbee.common.core.controller.BaseController;
import com.fastbee.common.core.domain.AjaxResult;
import com.fastbee.iot.domain.DeviceModbusParams;
import com.fastbee.iot.service.IDeviceModbusParamsService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Arrays;
/**
* 设备modbus配置参数Controller
*
* @author Sumuxi
* @date 2026/1/14
*/
@RestController
@RequestMapping("/modbus/device/params")
@Api(tags = "设备modbus配置参数")
public class DeviceModbusParamsController extends BaseController {
@Resource
private IDeviceModbusParamsService deviceModbusParamsService;
/**
* 根据设备ID获取设备modbus配置参数
*/
@PreAuthorize("@ss.hasPermi('modbus:params:query')")
@GetMapping(value = "/getByDeviceId")
@ApiOperation("根据设备ID获取设备modbus配置参数")
public AjaxResult getByDeviceId(@ApiParam("设备ID") @RequestParam Long deviceId) {
return success(deviceModbusParamsService.getByDeviceId(deviceId));
}
/**
* 获取设备的有效modbus配置参数(设备级 > 产品级)
*/
@PreAuthorize("@ss.hasPermi('modbus:params:query')")
@GetMapping(value = "/getEffective")
@ApiOperation("获取设备的有效modbus配置参数(设备级优先,回退到产品级)")
public AjaxResult getEffective(
@ApiParam("设备ID") @RequestParam Long deviceId,
@ApiParam("产品ID") @RequestParam Long productId) {
return success(deviceModbusParamsService.getEffectiveParams(deviceId, productId));
}
/**
* 新增或更新设备modbus配置参数
*/
@PreAuthorize("@ss.hasPermi('modbus:params:edit')")
@PostMapping("/addOrUpdate")
@ApiOperation("新增或更新设备modbus配置参数")
public AjaxResult addOrUpdate(@RequestBody DeviceModbusParams params) {
return toAjax(deviceModbusParamsService.addOrUpdate(params));
}
/**
* 根据设备ID删除设备modbus配置参数
*/
@PreAuthorize("@ss.hasPermi('modbus:params:remove')")
@DeleteMapping("/deleteByDeviceId/{deviceId}")
@ApiOperation("根据设备ID删除设备modbus配置参数")
public AjaxResult deleteByDeviceId(@PathVariable Long deviceId) {
return toAjax(deviceModbusParamsService.deleteByDeviceId(deviceId));
}
/**
* 批量删除设备modbus配置参数
*/
@PreAuthorize("@ss.hasPermi('modbus:params:remove')")
@DeleteMapping("/{ids}")
@ApiOperation("批量删除设备modbus配置参数")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(deviceModbusParamsService.removeByIds(Arrays.asList(ids)));
}
}

View File

@ -97,22 +97,23 @@ public class ModbusProtocol implements IProtocol {
matchSubDev(report, deviceData.getSerialNumber(), slaveId); matchSubDev(report, deviceData.getSerialNumber(), slaveId);
String serialNumber = report.getSerialNumber(); String serialNumber = report.getSerialNumber();
Long productId = report.getProductId(); Long productId = report.getProductId();
Long deviceId = getDeviceIdBySerial(serialNumber);
ModbusCode modbusCode = ModbusCode.getInstance(code); ModbusCode modbusCode = ModbusCode.getInstance(code);
List<ThingsModelSimpleItem> values = new ArrayList<>(); List<ThingsModelSimpleItem> values = new ArrayList<>();
switch (modbusCode) { switch (modbusCode) {
case Read01: case Read01:
case Read02: case Read02:
//起始地址 //起始地址(从设备级Redis缓存获取)
String address0102 = getCacheModbusAddress(deviceData.getSerialNumber(), slaveId, code); String address0102 = getCacheModbusAddress(productId, deviceId);
//读线圈或读离散型寄存器处理 //读线圈或读离散型寄存器处理
handleRead0102(productId, message, address0102, values); handleRead0102(productId, deviceId, message, address0102, values);
break; break;
case Read03: case Read03:
case Read04: case Read04:
//起始地址 //起始地址(从设备级Redis缓存获取)
String address0304 = getCacheModbusAddress(deviceData.getSerialNumber(), slaveId, code); String address0304 = getCacheModbusAddress(productId, deviceId);
//读输入、保持寄存器 //读输入、保持寄存器
handleRead0304(productId, message, address0304, values, hexDump); handleRead0304(productId, deviceId, message, address0304, values, hexDump);
break; break;
case Write06: case Write06:
case Write05: case Write05:
@ -144,18 +145,19 @@ public class ModbusProtocol implements IProtocol {
* 读线圈或读离散型寄存器处理 * 读线圈或读离散型寄存器处理
* *
* @param productId 产品id * @param productId 产品id
* @param deviceId 设备id(覆盖)
* @param message 数据集合 * @param message 数据集合
* @param address 起始地址 * @param address 起始地址
* @param simpleItemList 返回的物模型值 * @param simpleItemList 返回的物模型值
*/ */
private void handleRead0102(Long productId, ModbusRtu message, String address, List<ThingsModelSimpleItem> simpleItemList) { private void handleRead0102(Long productId, Long deviceId, ModbusRtu message, String address, List<ThingsModelSimpleItem> simpleItemList) {
//字节数 //字节数
int bitCount = message.getBitCount(); int bitCount = message.getBitCount();
//字节数据集合 //字节数据集合
byte[] byteDatas = message.getBitData(); byte[] byteDatas = message.getBitData();
String hexDump = ByteBufUtil.hexDump(byteDatas); String hexDump = ByteBufUtil.hexDump(byteDatas);
//处理多个的情况 //处理多个的情况
Map<Integer, List<ModbusConfig>> modbusConfigMap = modbusConfigCache.getModbusConfigCacheByProductId(productId); Map<Integer, List<ModbusConfig>> modbusConfigMap = modbusConfigCache.getModbusConfigCacheByDeviceId(productId, deviceId);
if (CollectionUtils.isEmpty(modbusConfigMap)) { if (CollectionUtils.isEmpty(modbusConfigMap)) {
log.warn("寄存器地址:{},不存在", address); log.warn("寄存器地址:{},不存在", address);
return; return;
@ -192,17 +194,18 @@ public class ModbusProtocol implements IProtocol {
* 处理读保持寄存器/输入寄存器 * 处理读保持寄存器/输入寄存器
* *
* @param productId 产品id * @param productId 产品id
* @param deviceId 设备id(覆盖)
* @param message 数据 * @param message 数据
* @param address 起始地址 * @param address 起始地址
* @param simpleItemList 返回的物模型值 * @param simpleItemList 返回的物模型值
* @param sourceStr 原始报文字符串 * @param sourceStr 原始报文字符串
*/ */
private void handleRead0304(Long productId, ModbusRtu message, String address, List<ThingsModelSimpleItem> simpleItemList, String sourceStr) { private void handleRead0304(Long productId, Long deviceId, ModbusRtu message, String address, List<ThingsModelSimpleItem> simpleItemList, String sourceStr) {
short[] data = message.getData(); short[] data = message.getData();
int length = data.length; int length = data.length;
if (length == 1) { if (length == 1) {
//单个寄存器上报情况处理 //单个寄存器上报情况处理
List<ModbusConfig> modbusConfig = modbusConfigCache.getSingleModbusConfig(productId, address); List<ModbusConfig> modbusConfig = getConfigByAddress(productId, deviceId, address);
if (CollectionUtils.isEmpty(modbusConfig)) { if (CollectionUtils.isEmpty(modbusConfig)) {
log.warn("寄存器地址:{},不存在", address); log.warn("寄存器地址:{},不存在", address);
return; return;
@ -239,7 +242,7 @@ public class ModbusProtocol implements IProtocol {
// 原数据字符串(保持完整,不截取) // 原数据字符串(保持完整,不截取)
String dataHex = sourceStr.substring(6, sourceStr.length() - 4); String dataHex = sourceStr.substring(6, sourceStr.length() - 4);
//处理多个情况 //处理多个情况
Map<Integer, List<ModbusConfig>> modbusConfigMap = modbusConfigCache.getModbusConfigCacheByProductId(productId); Map<Integer, List<ModbusConfig>> modbusConfigMap = modbusConfigCache.getModbusConfigCacheByDeviceId(productId, deviceId);
if (CollectionUtils.isEmpty(modbusConfigMap)) { if (CollectionUtils.isEmpty(modbusConfigMap)) {
log.warn("寄存器数据不存在"); log.warn("寄存器数据不存在");
return; return;
@ -373,7 +376,8 @@ public class ModbusProtocol implements IProtocol {
private void handleMsgReply(ModbusRtu message, DeviceReport report, ModbusCode code) { private void handleMsgReply(ModbusRtu message, DeviceReport report, ModbusCode code) {
//单个寄存器上报情况处理 //单个寄存器上报情况处理
String address = message.getAddress() + ""; String address = message.getAddress() + "";
List<ModbusConfig> modbusConfig = modbusConfigCache.getSingleModbusConfig(report.getProductId(), address); Long deviceId = getDeviceIdBySerial(report.getSerialNumber());
List<ModbusConfig> modbusConfig = getConfigByAddress(report.getProductId(), deviceId, address);
if (CollectionUtils.isEmpty(modbusConfig)) { if (CollectionUtils.isEmpty(modbusConfig)) {
log.warn("寄存器地址:{},不存在", address); log.warn("寄存器地址:{},不存在", address);
return; return;
@ -416,6 +420,30 @@ public class ModbusProtocol implements IProtocol {
} }
} }
/**
* 根据设备编号获取设备ID
*/
private Long getDeviceIdBySerial(String serialNumber) {
Device device = deviceMapper.selectDeviceBySerialNumber(serialNumber);
return device == null ? null : device.getDeviceId();
}
/**
* 根据地址获取设备优先的配置列表
*/
private List<ModbusConfig> getConfigByAddress(Long productId, Long deviceId, String address) {
Map<Integer, List<ModbusConfig>> modbusConfigMap = modbusConfigCache.getModbusConfigCacheByDeviceId(productId, deviceId);
if (CollectionUtils.isEmpty(modbusConfigMap)) {
return java.util.Collections.emptyList();
}
try {
int ad = Integer.parseInt(address);
return modbusConfigMap.getOrDefault(ad, java.util.Collections.emptyList());
} catch (NumberFormatException e) {
return java.util.Collections.emptyList();
}
}
@Override @Override
public FunctionCallBackBo encode(MQSendMessageBo message) { public FunctionCallBackBo encode(MQSendMessageBo message) {
FunctionCallBackBo callBack = new FunctionCallBackBo(); FunctionCallBackBo callBack = new FunctionCallBackBo();
@ -484,39 +512,26 @@ public class ModbusProtocol implements IProtocol {
} }
/** /**
* 获取modbus地址 * 获取modbus起始地址
* 从设备级Redis缓存获取配置,返回最小寄存器地址
*
* @param productId 产品ID
* @param deviceId 设备ID
* @return 起始地址
*/ */
private String getCacheModbusAddress(String serialNumber, int salveId, int code) { private String getCacheModbusAddress(Long productId, Long deviceId) {
// String key = RedisKeyBuilder.buildModbusRuntimeCacheKey(serialNumber);
// Set<String> commandSet = redisCache.zRange(key, 0, -1);
//
// // 1. 遍历匹配指令
// for (String command : commandSet) {
// try {
// Mparams params = ModbusUtils.getModbusParams(command);
// redisCache.zRem(key, command); // 删除缓存
// if (params.getSlaveId() == salveId && params.getCode() == code) {
// return String.valueOf(params.getAddress());
// }
// } catch (Exception e) {
// log.warn("解析指令异常: {}", e.getMessage());
// }
// }
// 2. 无匹配时查询数据库
try { try {
Device device = deviceMapper.selectDeviceBySerialNumber(serialNumber); // 从设备级Redis缓存获取Modbus配置(key: buildModbusDeviceKey(deviceId))
if (device == null) return "0"; Map<Integer, List<ModbusConfig>> modbusConfigMap = modbusConfigCache.getModbusConfigCacheByDeviceId(productId, deviceId);
if (CollectionUtils.isEmpty(modbusConfigMap)) {
List<ModbusConfig> configs = modbusConfigMapper.selectModbusConfigListByProductId(device.getProductId()); return "0";
if (CollectionUtils.isEmpty(configs)) return "0"; }
return modbusConfigMap.keySet().stream()
return configs.stream() .mapToInt(Integer::intValue)
.mapToInt(ModbusConfig::getAddress)
.min() .min()
.orElse(0) + ""; .orElse(0) + "";
} catch (Exception e) { } catch (Exception e) {
log.error("数据库查询异常: {}", e.getMessage()); log.error("获取Modbus配置缓存异常: {}", e.getMessage());
return "0"; return "0";
} }
} }

View File

@ -20,7 +20,9 @@ import com.fastbee.common.utils.StringUtils;
import com.fastbee.common.utils.modbus.BitUtils; import com.fastbee.common.utils.modbus.BitUtils;
import com.fastbee.helper.ModbusProtocolHelper; import com.fastbee.helper.ModbusProtocolHelper;
import com.fastbee.iot.cache.IModbusConfigCache; import com.fastbee.iot.cache.IModbusConfigCache;
import com.fastbee.iot.domain.Device;
import com.fastbee.iot.domain.ModbusConfig; import com.fastbee.iot.domain.ModbusConfig;
import com.fastbee.iot.mapper.DeviceMapper;
import com.fastbee.iot.model.ThingsModels.ThingsModelValueItem; import com.fastbee.iot.model.ThingsModels.ThingsModelValueItem;
import com.fastbee.iot.service.ISubGatewayService; import com.fastbee.iot.service.ISubGatewayService;
import com.fastbee.modbustcp.model.ModbusTcp; import com.fastbee.modbustcp.model.ModbusTcp;
@ -57,6 +59,8 @@ public class ModbusTcpProtocol implements IProtocol {
private IModbusConfigCache modbusConfigCache; private IModbusConfigCache modbusConfigCache;
@Resource @Resource
private ISubGatewayService subGatewayService; private ISubGatewayService subGatewayService;
@Resource
private DeviceMapper deviceMapper;
@Override @Override
public DeviceReport decode(DeviceData deviceData, String clientId) { public DeviceReport decode(DeviceData deviceData, String clientId) {
@ -79,6 +83,7 @@ public class ModbusTcpProtocol implements IProtocol {
matchSubDev(report, deviceData.getSerialNumber(), slaveId); matchSubDev(report, deviceData.getSerialNumber(), slaveId);
String serialNumber = report.getSerialNumber(); String serialNumber = report.getSerialNumber();
Long productId = report.getProductId(); Long productId = report.getProductId();
Long deviceId = getDeviceIdBySerial(serialNumber);
ModbusCode modbusCode = ModbusCode.getInstance(code); ModbusCode modbusCode = ModbusCode.getInstance(code);
List<ThingsModelSimpleItem> values = new ArrayList<>(); List<ThingsModelSimpleItem> values = new ArrayList<>();
switch (modbusCode) { switch (modbusCode) {
@ -87,7 +92,7 @@ public class ModbusTcpProtocol implements IProtocol {
//起始地址 //起始地址
ModbusTcp rawModbusTcpRtu = getCacheModbusAddress(deviceData.getSerialNumber(), message.getTransactionIdentifier()); ModbusTcp rawModbusTcpRtu = getCacheModbusAddress(deviceData.getSerialNumber(), message.getTransactionIdentifier());
//读线圈或读离散型寄存器处理 //读线圈或读离散型寄存器处理
handleRead0102(productId, message, rawModbusTcpRtu, values); handleRead0102(productId, deviceId, message, rawModbusTcpRtu, values);
break; break;
case Read03: case Read03:
case Read04: case Read04:
@ -98,7 +103,7 @@ public class ModbusTcpProtocol implements IProtocol {
String addressStr = sourceData.substring(17, 20); String addressStr = sourceData.substring(17, 20);
int address = Integer.parseInt(addressStr, 16); int address = Integer.parseInt(addressStr, 16);
//读输入、保持寄存器 //读输入、保持寄存器
handleRead0304(productId, message, String.valueOf(address), values, hexDump); handleRead0304(productId, deviceId, message, String.valueOf(address), values, hexDump);
break; break;
case Write06: case Write06:
case Write05: case Write05:
@ -130,18 +135,19 @@ public class ModbusTcpProtocol implements IProtocol {
* 读线圈或读离散型寄存器处理 * 读线圈或读离散型寄存器处理
* *
* @param productId 产品id * @param productId 产品id
* @param deviceId 设备id(覆盖)
* @param message 数据集合 * @param message 数据集合
* @param modbusTcpRtu 下发报文 * @param modbusTcpRtu 下发报文
* @param simpleItemList 返回的物模型值 * @param simpleItemList 返回的物模型值
*/ */
private void handleRead0102(Long productId, ModbusTcp message, ModbusTcp modbusTcpRtu, List<ThingsModelSimpleItem> simpleItemList) { private void handleRead0102(Long productId, Long deviceId, ModbusTcp message, ModbusTcp modbusTcpRtu, List<ThingsModelSimpleItem> simpleItemList) {
//字节数 //字节数
int bitCount = message.getBitCount(); int bitCount = message.getBitCount();
//字节数据集合 //字节数据集合
byte[] byteDatas = message.getBitData(); byte[] byteDatas = message.getBitData();
String hexDump = ByteBufUtil.hexDump(byteDatas); String hexDump = ByteBufUtil.hexDump(byteDatas);
//处理多个的情况 //处理多个的情况
Map<Integer, List<ModbusConfig>> modbusConfigMap = modbusConfigCache.getModbusConfigCacheByProductId(productId); Map<Integer, List<ModbusConfig>> modbusConfigMap = modbusConfigCache.getModbusConfigCacheByDeviceId(productId, deviceId);
if (CollectionUtils.isEmpty(modbusConfigMap)) { if (CollectionUtils.isEmpty(modbusConfigMap)) {
log.warn("寄存器地址:{},不存在", modbusTcpRtu.getAddress()); log.warn("寄存器地址:{},不存在", modbusTcpRtu.getAddress());
return; return;
@ -221,17 +227,18 @@ public class ModbusTcpProtocol implements IProtocol {
* 处理读保持寄存器/输入寄存器 * 处理读保持寄存器/输入寄存器
* *
* @param productId 产品id * @param productId 产品id
* @param deviceId 设备id(覆盖)
* @param message 数据 * @param message 数据
* @param address 起始地址 * @param address 起始地址
* @param simpleItemList 返回的物模型值 * @param simpleItemList 返回的物模型值
* @param sourceStr 原始报文字符串 * @param sourceStr 原始报文字符串
*/ */
private void handleRead0304(Long productId, ModbusTcp message, String address, List<ThingsModelSimpleItem> simpleItemList, String sourceStr) { private void handleRead0304(Long productId, Long deviceId, ModbusTcp message, String address, List<ThingsModelSimpleItem> simpleItemList, String sourceStr) {
short[] data = message.getData(); short[] data = message.getData();
int length = data.length; int length = data.length;
if (length == 1) { if (length == 1) {
//单个寄存器上报情况处理 //单个寄存器上报情况处理
List<ModbusConfig> modbusConfig = modbusConfigCache.getSingleModbusConfig(productId, address); List<ModbusConfig> modbusConfig = getConfigByAddress(productId, deviceId, address);
if (CollectionUtils.isEmpty(modbusConfig)) { if (CollectionUtils.isEmpty(modbusConfig)) {
log.warn("寄存器地址:{},不存在", address); log.warn("寄存器地址:{},不存在", address);
return; return;
@ -268,7 +275,7 @@ public class ModbusTcpProtocol implements IProtocol {
//原数据字符串 //原数据字符串
String dataHex = sourceStr.substring(6, sourceStr.length() - 4); String dataHex = sourceStr.substring(6, sourceStr.length() - 4);
//处理多个情况 //处理多个情况
Map<Integer, List<ModbusConfig>> modbusConfigMap = modbusConfigCache.getModbusConfigCacheByProductId(productId); Map<Integer, List<ModbusConfig>> modbusConfigMap = modbusConfigCache.getModbusConfigCacheByDeviceId(productId, deviceId);
if (CollectionUtils.isEmpty(modbusConfigMap)) { if (CollectionUtils.isEmpty(modbusConfigMap)) {
log.warn("寄存器数据不存在"); log.warn("寄存器数据不存在");
return; return;
@ -329,7 +336,8 @@ public class ModbusTcpProtocol implements IProtocol {
private void handleMsgReply(ModbusTcp message, DeviceReport report, ModbusCode code) { private void handleMsgReply(ModbusTcp message, DeviceReport report, ModbusCode code) {
//单个寄存器上报情况处理 //单个寄存器上报情况处理
String address = message.getAddress() + ""; String address = message.getAddress() + "";
List<ModbusConfig> modbusConfig = modbusConfigCache.getSingleModbusConfig(report.getProductId(), address); Long deviceId = getDeviceIdBySerial(report.getSerialNumber());
List<ModbusConfig> modbusConfig = getConfigByAddress(report.getProductId(), deviceId, address);
if (CollectionUtils.isEmpty(modbusConfig)) { if (CollectionUtils.isEmpty(modbusConfig)) {
log.warn("寄存器地址:{},不存在", address); log.warn("寄存器地址:{},不存在", address);
return; return;
@ -372,6 +380,24 @@ public class ModbusTcpProtocol implements IProtocol {
} }
} }
private Long getDeviceIdBySerial(String serialNumber) {
Device device = deviceMapper.selectDeviceBySerialNumber(serialNumber);
return device == null ? null : device.getDeviceId();
}
private List<ModbusConfig> getConfigByAddress(Long productId, Long deviceId, String address) {
Map<Integer, List<ModbusConfig>> modbusConfigMap = modbusConfigCache.getModbusConfigCacheByDeviceId(productId, deviceId);
if (CollectionUtils.isEmpty(modbusConfigMap)) {
return java.util.Collections.emptyList();
}
try {
int ad = Integer.parseInt(address);
return modbusConfigMap.getOrDefault(ad, java.util.Collections.emptyList());
} catch (NumberFormatException e) {
return java.util.Collections.emptyList();
}
}
@Override @Override
public FunctionCallBackBo encode(MQSendMessageBo message) { public FunctionCallBackBo encode(MQSendMessageBo message) {
FunctionCallBackBo callBack = new FunctionCallBackBo(); FunctionCallBackBo callBack = new FunctionCallBackBo();

View File

@ -11,7 +11,9 @@ import com.fastbee.common.core.thingsModel.ThingsModelSimpleItem;
import com.fastbee.common.utils.DateUtils; import com.fastbee.common.utils.DateUtils;
import com.fastbee.common.utils.modbus.BitUtils; import com.fastbee.common.utils.modbus.BitUtils;
import com.fastbee.iot.cache.IModbusConfigCache; import com.fastbee.iot.cache.IModbusConfigCache;
import com.fastbee.iot.domain.Device;
import com.fastbee.iot.domain.ModbusConfig; import com.fastbee.iot.domain.ModbusConfig;
import com.fastbee.iot.mapper.DeviceMapper;
import com.fastbee.iot.model.ThingsModels.ThingsModelValueItem; import com.fastbee.iot.model.ThingsModels.ThingsModelValueItem;
import com.fastbee.pakModbus.model.PakModbusRtu; import com.fastbee.pakModbus.model.PakModbusRtu;
import com.fastbee.protocol.base.protocol.IProtocol; import com.fastbee.protocol.base.protocol.IProtocol;
@ -80,6 +82,8 @@ public class ModbusRtuPakProtocol implements IProtocol {
private ModbusRtuPakEncoder rtuPakEncoder; private ModbusRtuPakEncoder rtuPakEncoder;
@Resource @Resource
private IModbusConfigCache modbusConfigCache; private IModbusConfigCache modbusConfigCache;
@Resource
private DeviceMapper deviceMapper;
@Override @Override
public DeviceReport decode(DeviceData deviceData, String clientId) { public DeviceReport decode(DeviceData deviceData, String clientId) {
@ -94,11 +98,14 @@ public class ModbusRtuPakProtocol implements IProtocol {
return report; return report;
} }
PakModbusRtu message = rtuPakDecoder.decode(buf); PakModbusRtu message = rtuPakDecoder.decode(buf);
Long deviceId = getDeviceIdBySerial(deviceData.getSerialNumber());
report.setSerialNumber(deviceData.getSerialNumber());
report.setProductId(deviceData.getProductId());
List<ThingsModelSimpleItem> values = new ArrayList<>(); List<ThingsModelSimpleItem> values = new ArrayList<>();
short[] data = message.getData(); short[] data = message.getData();
for (int i = 0; i < data.length; i++) { for (int i = 0; i < data.length; i++) {
int address = message.getAddress() + i; int address = message.getAddress() + i;
handleModbusDataType(deviceData.getProductId(), address + "", data[i] + "", values); handleModbusDataType(deviceData.getProductId(), deviceId, address + "", data[i] + "", values);
} }
report.setClientId(clientId); report.setClientId(clientId);
report.setThingsModelSimpleItem(values); report.setThingsModelSimpleItem(values);
@ -113,10 +120,10 @@ public class ModbusRtuPakProtocol implements IProtocol {
* @param productId * @param productId
* @param values * @param values
*/ */
private void handleModbusDataType(Long productId, String address, String value, List<ThingsModelSimpleItem> values) { private void handleModbusDataType(Long productId, Long deviceId, String address, String value, List<ThingsModelSimpleItem> values) {
int nValue = Integer.parseInt(value); int nValue = Integer.parseInt(value);
List<ModbusConfig> modbusConfig = modbusConfigCache.getSingleModbusConfig(productId, address); List<ModbusConfig> modbusConfig = getConfigByAddress(productId, deviceId, address);
if (CollectionUtils.isEmpty(modbusConfig)) { if (CollectionUtils.isEmpty(modbusConfig)) {
log.warn("寄存器地址:{},不存在", address); log.warn("寄存器地址:{},不存在", address);
return; return;
@ -158,6 +165,24 @@ public class ModbusRtuPakProtocol implements IProtocol {
} }
} }
private Long getDeviceIdBySerial(String serialNumber) {
Device device = deviceMapper.selectDeviceBySerialNumber(serialNumber);
return device == null ? null : device.getDeviceId();
}
private List<ModbusConfig> getConfigByAddress(Long productId, Long deviceId, String address) {
Map<Integer, List<ModbusConfig>> modbusConfigMap = modbusConfigCache.getModbusConfigCacheByDeviceId(productId, deviceId);
if (CollectionUtils.isEmpty(modbusConfigMap)) {
return java.util.Collections.emptyList();
}
try {
int ad = Integer.parseInt(address);
return modbusConfigMap.getOrDefault(ad, java.util.Collections.emptyList());
} catch (NumberFormatException e) {
return java.util.Collections.emptyList();
}
}
@Override @Override
public FunctionCallBackBo encode(MQSendMessageBo bo) { public FunctionCallBackBo encode(MQSendMessageBo bo) {

View File

@ -13,7 +13,6 @@ import java.util.Map;
*/ */
public interface IModbusConfigCache { public interface IModbusConfigCache {
/** /**
* 获取modbus参数 --返回 Map<String, ModbusConfig> * 获取modbus参数 --返回 Map<String, ModbusConfig>
* *
@ -46,4 +45,18 @@ public interface IModbusConfigCache {
* @return ModbusConfig * @return ModbusConfig
*/ */
public List<ModbusConfig> getSingleModbusConfig(Long productId, String identify); public List<ModbusConfig> getSingleModbusConfig(Long productId, String identify);
/**
* 获取设备维度的modbus参数(设备优先,产品兜底)
*
* @param productId 产品id(兜底用)
* @param deviceId 设备id(优先)
* @return address -> configs
*/
Map<Integer, List<ModbusConfig>> getModbusConfigCacheByDeviceId(Long productId, Long deviceId);
/**
* 刷新设备维度缓存
*/
Map<Integer, List<ModbusConfig>> setModbusConfigCacheByDeviceId(Long productId, Long deviceId);
} }

View File

@ -7,6 +7,7 @@ import com.fastbee.common.core.redis.RedisKeyBuilder;
import com.fastbee.common.utils.StringUtils; import com.fastbee.common.utils.StringUtils;
import com.fastbee.iot.cache.IModbusConfigCache; import com.fastbee.iot.cache.IModbusConfigCache;
import com.fastbee.iot.domain.ModbusConfig; import com.fastbee.iot.domain.ModbusConfig;
import com.fastbee.iot.service.IDeviceModbusConfigService;
import com.fastbee.iot.service.IModbusConfigService; import com.fastbee.iot.service.IModbusConfigService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
@ -27,6 +28,8 @@ public class ModbusConfigCacheImpl implements IModbusConfigCache {
@Resource @Resource
private IModbusConfigService modbusConfigService; private IModbusConfigService modbusConfigService;
@Resource @Resource
private IDeviceModbusConfigService deviceModbusConfigService;
@Resource
private RedisCache redisCache; private RedisCache redisCache;
/** /**
@ -104,4 +107,37 @@ public class ModbusConfigCacheImpl implements IModbusConfigCache {
return modbusConfigList; return modbusConfigList;
} }
@Override
public Map<Integer, List<ModbusConfig>> getModbusConfigCacheByDeviceId(Long productId, Long deviceId) {
// 现在仅按设备配置解析,不再回落到产品模板
if (deviceId == null) {
return new HashMap<>();
}
String modbusKey = RedisKeyBuilder.buildModbusDeviceKey(deviceId);
Map<String, String> map = redisCache.hashEntity(modbusKey);
if (!CollectionUtils.isEmpty(map)) {
Map<Integer, List<ModbusConfig>> resultMap = new HashMap<>();
for (Map.Entry<String, String> entry : map.entrySet()) {
List<ModbusConfig> modbusConfigList = JSON.parseArray(entry.getValue(), ModbusConfig.class);
resultMap.put(Integer.parseInt(entry.getKey()), modbusConfigList);
}
return resultMap;
}
return setModbusConfigCacheByDeviceId(productId, deviceId);
}
@Override
public Map<Integer, List<ModbusConfig>> setModbusConfigCacheByDeviceId(Long productId, Long deviceId) {
String modbusKey = RedisKeyBuilder.buildModbusDeviceKey(deviceId);
Map<String, String> resultMap = new HashMap<>();
// 仅使用设备维度的寄存器配置,不再合并产品模板
List<ModbusConfig> deviceConfigs = deviceModbusConfigService.selectDeviceConfigs(productId, deviceId);
Map<Integer, List<ModbusConfig>> listMap = deviceConfigs.stream().collect(Collectors.groupingBy(ModbusConfig::getAddress));
for (Map.Entry<Integer, List<ModbusConfig>> entry : listMap.entrySet()) {
String valueStr = JSONObject.toJSONString(entry.getValue());
resultMap.put(entry.getKey() + "", valueStr);
}
redisCache.hashPutAll(modbusKey, resultMap);
return listMap;
}
} }

View File

@ -0,0 +1,27 @@
package com.fastbee.iot.domain;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @description: 设备级 modbus 配置对象 iot_device_modbus_config,
* 继承产品级 modbus 配置对象 iot_modbus_config,允许设备对产品模板做覆盖
*
* @author Sumuxi
* @date 2026/1/14
*/
@ApiModel(value = "DeviceModbusConfig", description = "设备modbus配置 iot_device_modbus_config")
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("iot_device_modbus_config")
public class DeviceModbusConfig extends ModbusConfig {
private static final long serialVersionUID = 1L;
/** 所属设备id */
@ApiModelProperty(value = "所属设备id")
private Long deviceId;
}

View File

@ -0,0 +1,29 @@
package com.fastbee.iot.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @description: 设备modbus配置参数对象 iot_device_modbus_params,
* 继承产品modbus配置参数对象 iot_modbus_params
*
* @author Sumuxi
* @date 2026/1/14
*/
@ApiModel(value = "DeviceModbusParams", description = "设备modbus配置参数 iot_device_modbus_params")
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("iot_device_modbus_params")
public class DeviceModbusParams extends ModbusParams {
private static final long serialVersionUID = 1L;
/** 所属设备id */
@ApiModelProperty(value = "所属设备id")
@TableField("device_id")
private Long deviceId;
}

View File

@ -0,0 +1,44 @@
package com.fastbee.iot.mapper;
import com.fastbee.iot.domain.DeviceModbusConfig;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 设备级 modbus 配置 Mapper
*/
public interface DeviceModbusConfigMapper {
DeviceModbusConfig selectDeviceModbusConfigById(Long id);
List<DeviceModbusConfig> selectDeviceModbusConfigList(DeviceModbusConfig config);
int insertDeviceModbusConfig(DeviceModbusConfig config);
int updateDeviceModbusConfig(DeviceModbusConfig config);
int deleteDeviceModbusConfigById(Long id);
int deleteDeviceModbusConfigByIds(Long[] ids);
List<DeviceModbusConfig> selectShortListByDeviceId(DeviceModbusConfig config);
DeviceModbusConfig selectByIdentify(@Param("deviceId") Long deviceId, @Param("identity") String identity);
/**
* 批量插入设备modbus配置
*
* @param list 配置列表
* @return 插入行数
*/
int insertModbusConfigBatch(@Param("list") List<DeviceModbusConfig> list);
/**
* 批量更新设备modbus配置
*
* @param list 配置列表
* @return 更新行数
*/
int updateModbusConfigBatch(@Param("list") List<DeviceModbusConfig> list);
}

View File

@ -0,0 +1,31 @@
package com.fastbee.iot.mapper;
import com.fastbee.framework.mybatis.mapper.BaseMapperX;
import com.fastbee.iot.domain.DeviceModbusParams;
import org.apache.ibatis.annotations.Param;
/**
* 设备modbus配置参数Mapper接口
*
* @author Sumuxi
* @date 2026/1/14
*/
public interface DeviceModbusParamsMapper extends BaseMapperX<DeviceModbusParams> {
/**
* 根据设备id获取modbus配置参数
*
* @param deviceId 设备id
* @return DeviceModbusParams
*/
DeviceModbusParams getByDeviceId(@Param("deviceId") Long deviceId);
/**
* 根据设备id删除配置
*
* @param deviceId 设备id
* @return 影响行数
*/
int deleteByDeviceId(@Param("deviceId") Long deviceId);
}

View File

@ -0,0 +1,25 @@
package com.fastbee.iot.model.modbus;
import com.fastbee.iot.domain.DeviceModbusConfig;
import lombok.Data;
import java.util.List;
/**
* 设备级 modbus 配置批量提交 VO
*/
@Data
public class DeviceModbusConfigVO {
/** 产品id */
private Long productId;
/** 设备id */
private Long deviceId;
/** modbus配置列表 */
private List<DeviceModbusConfig> configList;
/** 删除的id集合 */
private Long[] delIds;
}

View File

@ -0,0 +1,50 @@
package com.fastbee.iot.service;
import com.fastbee.iot.domain.DeviceModbusConfig;
import com.fastbee.iot.domain.ModbusConfig;
import java.util.List;
/**
* 设备级 modbus 配置 Service
*/
public interface IDeviceModbusConfigService {
DeviceModbusConfig selectDeviceModbusConfigById(Long id);
List<DeviceModbusConfig> selectDeviceModbusConfigList(DeviceModbusConfig config);
int insertDeviceModbusConfig(DeviceModbusConfig config);
int updateDeviceModbusConfig(DeviceModbusConfig config);
int deleteDeviceModbusConfigByIds(Long[] ids);
int deleteDeviceModbusConfigById(Long id);
/**
* 批量新增或更新设备级配置
*/
void addOrUpDeviceModbusConfigBatch(List<DeviceModbusConfig> configList, Long productId, Long deviceId, Long[] delIds);
/**
* 获取设备级简要字段
*/
List<DeviceModbusConfig> selectShortListByDeviceId(DeviceModbusConfig config);
/**
* 根据设备和标识符获取配置
*/
DeviceModbusConfig selectByIdentify(Long deviceId, String identity);
/**
* 合并设备覆盖后的最终配置列表(转为 ModbusConfig 供解析和缓存)
*/
List<ModbusConfig> selectMergedConfig(Long productId, Long deviceId);
/**
* 仅设备维度的配置列表(转为 ModbusConfig 供解析和缓存)
*/
List<ModbusConfig> selectDeviceConfigs(Long productId, Long deviceId);
}

View File

@ -0,0 +1,102 @@
package com.fastbee.iot.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.fastbee.iot.domain.DeviceModbusParams;
import com.fastbee.iot.domain.ModbusParams;
import java.util.List;
/**
* 设备modbus配置参数Service接口
*
* @author Sumuxi
* @date 2026/1/14
*/
public interface IDeviceModbusParamsService extends IService<DeviceModbusParams> {
/**
* 根据设备id获取modbus配置参数
*
* @param deviceId 设备id
* @return DeviceModbusParams
*/
DeviceModbusParams getByDeviceId(Long deviceId);
/**
* 根据设备id获取modbus配置参数(带缓存)
*
* @param deviceId 设备id
* @return DeviceModbusParams
*/
DeviceModbusParams getByDeviceIdWithCache(Long deviceId);
/**
* 获取设备的有效modbus配置参数(自动获取productId)
* 如果设备级配置存在则返回设备级,否则返回产品级默认配置
* 推荐使用此方法,便于平滑切换
*
* @param deviceId 设备id
* @return ModbusParams
*/
ModbusParams getEffectiveParams(Long deviceId);
/**
* 获取设备的有效modbus配置参数
* 如果设备级配置存在则返回设备级,否则返回产品级默认配置
*
* @param deviceId 设备id
* @param productId 产品id
* @return ModbusParams
*/
ModbusParams getEffectiveParams(Long deviceId, Long productId);
/**
* 新增设备modbus配置参数
*
* @param params 设备modbus配置参数
* @return 是否成功
*/
Boolean insertParams(DeviceModbusParams params);
/**
* 新增或更新设备modbus配置参数
*
* @param params 设备modbus配置参数
* @return 是否成功
*/
boolean addOrUpdate(DeviceModbusParams params);
/**
* 修改设备modbus配置参数
*
* @param params 设备modbus配置参数
* @return 是否成功
*/
Boolean updateParams(DeviceModbusParams params);
/**
* 根据设备id删除配置
*
* @param deviceId 设备id
* @return 是否成功
*/
Boolean deleteByDeviceId(Long deviceId);
/**
* 批量删除设备modbus配置参数
*
* @param ids 待删除的主键集合
* @param isValid 是否进行有效性校验
* @return 是否成功
*/
Boolean deleteByIds(Long[] ids, Boolean isValid);
/**
* 根据设备id列表查询配置列表
*
* @param deviceIds 设备id列表
* @return 配置列表
*/
List<DeviceModbusParams> selectByDeviceIds(List<Long> deviceIds);
}

View File

@ -0,0 +1,164 @@
package com.fastbee.iot.service.impl;
import com.fastbee.common.exception.ServiceException;
import com.fastbee.common.utils.DateUtils;
import com.fastbee.common.utils.SecurityUtils;
import com.fastbee.iot.domain.DeviceModbusConfig;
import com.fastbee.iot.domain.ModbusConfig;
import com.fastbee.iot.mapper.DeviceModbusConfigMapper;
import com.fastbee.iot.mapper.ModbusConfigMapper;
import com.fastbee.iot.service.IDeviceModbusConfigService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;
/**
* 设备级 modbus 配置 Service 实现
*/
@Service
public class DeviceModbusConfigServiceImpl implements IDeviceModbusConfigService {
@Resource
private DeviceModbusConfigMapper deviceModbusConfigMapper;
@Resource
private ModbusConfigMapper modbusConfigMapper;
@Override
public DeviceModbusConfig selectDeviceModbusConfigById(Long id) {
return deviceModbusConfigMapper.selectDeviceModbusConfigById(id);
}
@Override
public List<DeviceModbusConfig> selectDeviceModbusConfigList(DeviceModbusConfig config) {
return deviceModbusConfigMapper.selectDeviceModbusConfigList(config);
}
@Override
public int insertDeviceModbusConfig(DeviceModbusConfig config) {
config.setCreateTime(DateUtils.getNowDate());
return deviceModbusConfigMapper.insertDeviceModbusConfig(config);
}
@Override
public int updateDeviceModbusConfig(DeviceModbusConfig config) {
config.setUpdateTime(DateUtils.getNowDate());
return deviceModbusConfigMapper.updateDeviceModbusConfig(config);
}
@Override
public int deleteDeviceModbusConfigByIds(Long[] ids) {
return deviceModbusConfigMapper.deleteDeviceModbusConfigByIds(ids);
}
@Override
public int deleteDeviceModbusConfigById(Long id) {
return deviceModbusConfigMapper.deleteDeviceModbusConfigById(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void addOrUpDeviceModbusConfigBatch(List<DeviceModbusConfig> configList, Long productId, Long deviceId, Long[] delIds) {
if (Objects.isNull(productId)) {
throw new ServiceException("产品id不能为空");
}
if (Objects.isNull(deviceId)) {
throw new ServiceException("设备id不能为空");
}
if (CollectionUtils.isEmpty(configList)) {
throw new ServiceException("modbus配置数据不能为空");
}
if (delIds != null && delIds.length > 0) {
this.deleteDeviceModbusConfigByIds(delIds);
}
List<DeviceModbusConfig> insertModbusConfigs = new ArrayList<>();
List<DeviceModbusConfig> updateModbusConfigs = new ArrayList<>();
for (DeviceModbusConfig config : configList) {
config.setProductId(productId);
config.setDeviceId(deviceId);
if (Objects.isNull(config.getId())) {
config.setCreateBy(SecurityUtils.getUsername());
config.setCreateTime(DateUtils.getNowDate());
//this.insertDeviceModbusConfig(config);
insertModbusConfigs.add(config);
} else {
config.setUpdateBy(SecurityUtils.getUsername());
config.setUpdateTime(DateUtils.getNowDate());
//this.updateDeviceModbusConfig(config);
updateModbusConfigs.add(config);
}
}
if (!CollectionUtils.isEmpty(insertModbusConfigs)) {
deviceModbusConfigMapper.insertModbusConfigBatch(insertModbusConfigs);
}
if (!CollectionUtils.isEmpty(updateModbusConfigs)) {
deviceModbusConfigMapper.updateModbusConfigBatch(updateModbusConfigs);
}
}
@Override
public List<DeviceModbusConfig> selectShortListByDeviceId(DeviceModbusConfig config) {
return deviceModbusConfigMapper.selectShortListByDeviceId(config);
}
@Override
public DeviceModbusConfig selectByIdentify(Long deviceId, String identity) {
return deviceModbusConfigMapper.selectByIdentify(deviceId, identity);
}
@Override
public List<ModbusConfig> selectMergedConfig(Long productId, Long deviceId) {
List<DeviceModbusConfig> deviceList = selectShortListByDeviceId(buildShortConfig(deviceId, productId, null));
List<ModbusConfig> productList = Collections.emptyList();
if (productId != null) {
ModbusConfig p = new ModbusConfig();
p.setProductId(productId);
productList = new ArrayList<>(Optional.ofNullable(modbusConfigMapper.selectShortListByProductId(p)).orElse(Collections.emptyList()));
}
Map<String, ModbusConfig> merged = new LinkedHashMap<>();
// 产品模板
for (ModbusConfig config : productList) {
merged.put(config.getIdentifier(), config);
}
// 设备覆盖(同 identifier 覆盖产品)
for (DeviceModbusConfig deviceConfig : deviceList) {
merged.put(deviceConfig.getIdentifier(), convert(deviceConfig));
}
return new ArrayList<>(merged.values());
}
private DeviceModbusConfig buildShortConfig(Long deviceId, Long productId, Integer type) {
DeviceModbusConfig config = new DeviceModbusConfig();
config.setDeviceId(deviceId);
config.setProductId(productId);
config.setType(type);
return config;
}
/**
* 用于兼容 cache 逻辑的转换
*/
private ModbusConfig convert(DeviceModbusConfig config) {
ModbusConfig target = new ModbusConfig();
BeanUtils.copyProperties(config, target);
return target;
}
@Override
public List<ModbusConfig> selectDeviceConfigs(Long productId, Long deviceId) {
List<DeviceModbusConfig> deviceList = selectShortListByDeviceId(buildShortConfig(deviceId, productId, null));
return deviceList.stream().map(this::convert).collect(Collectors.toList());
}
/**
* 供单元测试或特殊场景调用,避免直接依赖 service 造成循环
*/
protected List<ModbusConfig> selectProductShortList(ModbusConfig config) {
return modbusConfigMapper.selectShortListByProductId(config);
}
}

View File

@ -0,0 +1,131 @@
package com.fastbee.iot.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fastbee.common.utils.DateUtils;
import com.fastbee.common.utils.SecurityUtils;
import com.fastbee.iot.domain.Device;
import com.fastbee.iot.domain.DeviceModbusParams;
import com.fastbee.iot.domain.ModbusParams;
import com.fastbee.iot.mapper.DeviceMapper;
import com.fastbee.iot.mapper.DeviceModbusParamsMapper;
import com.fastbee.iot.service.IDeviceModbusParamsService;
import com.fastbee.iot.service.IModbusParamsService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* 设备modbus配置参数Service业务层处理
*
* @author Sumuxi
* @date 2026/1/14
*/
@Service
public class DeviceModbusParamsServiceImpl extends ServiceImpl<DeviceModbusParamsMapper, DeviceModbusParams>
implements IDeviceModbusParamsService {
private static final String CACHE_NAME = "DeviceModbusParams";
@Resource
private IModbusParamsService modbusParamsService;
@Resource
private DeviceMapper deviceMapper;
@Override
public DeviceModbusParams getByDeviceId(Long deviceId) {
return baseMapper.getByDeviceId(deviceId);
}
@Override
@Cacheable(cacheNames = CACHE_NAME, key = "#deviceId")
public DeviceModbusParams getByDeviceIdWithCache(Long deviceId) {
return baseMapper.getByDeviceId(deviceId);
}
@Override
public ModbusParams getEffectiveParams(Long deviceId) {
// 优先返回设备级配置
DeviceModbusParams deviceParams = this.getByDeviceId(deviceId);
if (Objects.nonNull(deviceParams)) {
return deviceParams;
}
// 查询设备获取productId,回退到产品级默认配置
Device device = deviceMapper.selectDeviceByDeviceId(deviceId);
if (Objects.nonNull(device) && Objects.nonNull(device.getProductId())) {
return modbusParamsService.getModbusParamsByProductId(device.getProductId());
}
return null;
}
@Override
public ModbusParams getEffectiveParams(Long deviceId, Long productId) {
// 优先返回设备级配置
DeviceModbusParams deviceParams = this.getByDeviceId(deviceId);
if (Objects.nonNull(deviceParams)) {
return deviceParams;
}
// 返回产品级默认配置
return modbusParamsService.getModbusParamsByProductId(productId);
}
@Override
public Boolean insertParams(DeviceModbusParams params) {
params.setCreateBy(SecurityUtils.getUsername());
params.setCreateTime(DateUtils.getNowDate());
return this.save(params);
}
@Override
@CacheEvict(cacheNames = CACHE_NAME, key = "#params.deviceId")
public boolean addOrUpdate(DeviceModbusParams params) {
Long deviceId = params.getDeviceId();
DeviceModbusParams existing = this.getByDeviceId(deviceId);
if (Objects.nonNull(existing)) {
params.setId(existing.getId());
params.setUpdateBy(SecurityUtils.getUsername());
params.setUpdateTime(DateUtils.getNowDate());
} else {
params.setCreateBy(SecurityUtils.getUsername());
params.setCreateTime(DateUtils.getNowDate());
}
return this.saveOrUpdate(params);
}
@Override
@CacheEvict(cacheNames = CACHE_NAME, key = "#params.deviceId")
public Boolean updateParams(DeviceModbusParams params) {
params.setUpdateBy(SecurityUtils.getUsername());
params.setUpdateTime(DateUtils.getNowDate());
return this.updateById(params);
}
@Override
@CacheEvict(cacheNames = CACHE_NAME, key = "#deviceId")
public Boolean deleteByDeviceId(Long deviceId) {
return baseMapper.deleteByDeviceId(deviceId) > 0;
}
@Override
@CacheEvict(cacheNames = CACHE_NAME, allEntries = true)
public Boolean deleteByIds(Long[] ids, Boolean isValid) {
if (isValid) {
// 可以在此添加业务校验逻辑
}
return this.removeByIds(Arrays.asList(ids));
}
@Override
public List<DeviceModbusParams> selectByDeviceIds(List<Long> deviceIds) {
LambdaQueryWrapper<DeviceModbusParams> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.in(DeviceModbusParams::getDeviceId, deviceIds);
return baseMapper.selectList(queryWrapper);
}
}

View File

@ -109,7 +109,7 @@ public class ModbusConfigServiceImpl implements IModbusConfigService {
assert !Objects.isNull(productId) : "产品id不能为空"; assert !Objects.isNull(productId) : "产品id不能为空";
assert !CollectionUtils.isEmpty(modbusConfigList) : "modbus配置数据不能为空"; assert !CollectionUtils.isEmpty(modbusConfigList) : "modbus配置数据不能为空";
if (!CollectionUtils.isEmpty(Arrays.asList(ids))) { if (!CollectionUtils.isEmpty(Arrays.asList(ids))) {
//要删除的id集合不为空,则先进行删除操作 // 要删除的id集合不为空,则先进行删除操作
this.deleteModbusConfigByIds(ids); this.deleteModbusConfigByIds(ids);
} }
for (ModbusConfig config : modbusConfigList) { for (ModbusConfig config : modbusConfigList) {

View File

@ -10,6 +10,7 @@ import com.fastbee.iot.mapper.SubGatewayMapper;
import com.fastbee.iot.model.gateWay.GateSubDeviceVO; import com.fastbee.iot.model.gateWay.GateSubDeviceVO;
import com.fastbee.iot.model.gateWay.SubDeviceAddVO; import com.fastbee.iot.model.gateWay.SubDeviceAddVO;
import com.fastbee.iot.model.gateWay.SubDeviceListVO; import com.fastbee.iot.model.gateWay.SubDeviceListVO;
import com.fastbee.iot.service.IDeviceModbusParamsService;
import com.fastbee.iot.service.IDeviceService; import com.fastbee.iot.service.IDeviceService;
import com.fastbee.iot.service.IModbusParamsService; import com.fastbee.iot.service.IModbusParamsService;
import com.fastbee.iot.service.ISubGatewayService; import com.fastbee.iot.service.ISubGatewayService;
@ -33,6 +34,8 @@ public class SubGatewayServiceImpl implements ISubGatewayService {
private SubGatewayMapper gatewayMapper; private SubGatewayMapper gatewayMapper;
@Resource @Resource
private IModbusParamsService modbusParamsService; private IModbusParamsService modbusParamsService;
@Resource
private IDeviceModbusParamsService deviceModbusParamsService;
/** /**
* 查询网关与子设备关联 * 查询网关与子设备关联
@ -125,7 +128,8 @@ public class SubGatewayServiceImpl implements ISubGatewayService {
List<SubGateway> list = new ArrayList<>(); List<SubGateway> list = new ArrayList<>();
if (subDeviceIds.length > 0) { if (subDeviceIds.length > 0) {
for (Long subDeviceId : subDeviceIds) { for (Long subDeviceId : subDeviceIds) {
ModbusParams modbusParams = modbusParamsService.getModbusParamsByDeviceId(subDeviceId); // 优先使用设备级配置,无则回退到产品级配置
ModbusParams modbusParams = deviceModbusParamsService.getEffectiveParams(subDeviceId);
SubGateway subGateway = new SubGateway(); SubGateway subGateway = new SubGateway();
subGateway.setSubDeviceId(subDeviceId); subGateway.setSubDeviceId(subDeviceId);
subGateway.setGwDeviceId(gwDeviceId); subGateway.setGwDeviceId(gwDeviceId);

View File

@ -0,0 +1,406 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fastbee.iot.mapper.DeviceModbusConfigMapper">
<resultMap id="DeviceModbusConfigResult" type="com.fastbee.iot.domain.DeviceModbusConfig">
<result property="id" column="id"/>
<result property="deviceId" column="device_id"/>
<result property="productId" column="product_id"/>
<result property="identifier" column="identifier"/>
<result property="slave" column="slave"/>
<result property="address" column="address"/>
<result property="isReadonly" column="is_readonly"/>
<result property="dataType" column="data_type"/>
<result property="quantity" column="quantity"/>
<result property="type" column="type"/>
<result property="bitOrder" column="bit_order"/>
<result property="sort" column="sort"/>
<result property="delFlag" column="del_flag"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="selectDeviceModbusConfigVo">
select id,
device_id,
product_id,
identifier,
slave,
address,
is_readonly,
data_type,
quantity,
type,
bit_order,
sort,
del_flag,
create_by,
create_time,
update_by,
update_time,
remark
from iot_device_modbus_config
</sql>
<select id="selectDeviceModbusConfigList" parameterType="com.fastbee.iot.domain.DeviceModbusConfig"
resultMap="DeviceModbusConfigResult">
<include refid="selectDeviceModbusConfigVo"/>
<where>
<if test="identifier != null and identifier != ''">
and identifier = #{identifier}
</if>
<if test="address != null ">
and address = #{address}
</if>
<if test="isReadonly != null ">
and is_readonly = #{isReadonly}
</if>
<if test="dataType != null and dataType != ''">
and data_type = #{dataType}
</if>
<if test="quantity != null ">
and quantity = #{quantity}
</if>
<if test="type != null ">
and type = #{type}
</if>
<if test="productId != null ">
and product_id = #{productId}
</if>
<if test="deviceId != null ">
and device_id = #{deviceId}
</if>
</where>
order by sort,id
</select>
<select id="selectDeviceModbusConfigById" parameterType="Long" resultMap="DeviceModbusConfigResult">
<include refid="selectDeviceModbusConfigVo"/>
where id = #{id}
</select>
<insert id="insertDeviceModbusConfig" parameterType="com.fastbee.iot.domain.DeviceModbusConfig">
insert into iot_device_modbus_config
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">
id,
</if>
<if test="deviceId != null">
device_id,
</if>
<if test="productId != null">
product_id,
</if>
<if test="identifier != null and identifier != ''">
identifier,
</if>
<if test="slave != null">
slave,
</if>
<if test="address != null">
address,
</if>
<if test="isReadonly != null">
is_readonly,
</if>
<if test="dataType != null and dataType != ''">
data_type,
</if>
<if test="quantity != null">
quantity,
</if>
<if test="type != null">
type,
</if>
<if test="delFlag != null">
del_flag,
</if>
<if test="createBy != null">
create_by,
</if>
<if test="createTime != null">
create_time,
</if>
<if test="updateBy != null">
update_by,
</if>
<if test="updateTime != null">
update_time,
</if>
<if test="remark != null">
remark,
</if>
<if test="sort != null">
sort,
</if>
<if test="bitOrder != null">
bit_order,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
#{id},
</if>
<if test="deviceId != null">
#{deviceId},
</if>
<if test="productId != null">
#{productId},
</if>
<if test="identifier != null and identifier != ''">
#{identifier},
</if>
<if test="slave != null">
#{slave},
</if>
<if test="address != null">
#{address},
</if>
<if test="isReadonly != null">
#{isReadonly},
</if>
<if test="dataType != null and dataType != ''">
#{dataType},
</if>
<if test="quantity != null">
#{quantity},
</if>
<if test="type != null">
#{type},
</if>
<if test="delFlag != null">
#{delFlag},
</if>
<if test="createBy != null">
#{createBy},
</if>
<if test="createTime != null">
#{createTime},
</if>
<if test="updateBy != null">
#{updateBy},
</if>
<if test="updateTime != null">
#{updateTime},
</if>
<if test="remark != null">
#{remark},
</if>
<if test="sort != null">
#{sort},
</if>
<if test="bitOrder != null">
#{bitOrder},
</if>
</trim>
</insert>
<update id="updateDeviceModbusConfig" parameterType="com.fastbee.iot.domain.DeviceModbusConfig">
update iot_device_modbus_config
<trim prefix="SET" suffixOverrides=",">
<if test="deviceId != null">
device_id = #{deviceId},
</if>
<if test="productId != null">
product_id = #{productId},
</if>
<if test="identifier != null and identifier != ''">
identifier = #{identifier},
</if>
<if test="slave != null">
slave = #{slave},
</if>
<if test="address != null">
address = #{address},
</if>
<if test="isReadonly != null">
is_readonly = #{isReadonly},
</if>
<if test="dataType != null and dataType != ''">
data_type = #{dataType},
</if>
<if test="quantity != null">
quantity = #{quantity},
</if>
<if test="type != null">
type = #{type},
</if>
<if test="delFlag != null">
del_flag = #{delFlag},
</if>
<if test="createBy != null">
create_by = #{createBy},
</if>
<if test="createTime != null">
create_time = #{createTime},
</if>
<if test="updateBy != null">
update_by = #{updateBy},
</if>
<if test="updateTime != null">
update_time = #{updateTime},
</if>
<if test="remark != null">
remark = #{remark},
</if>
<if test="sort != null">
sort = #{sort},
</if>
<if test="bitOrder != null">
bit_order = #{bitOrder},
</if>
</trim>
where id = #{id}
</update>
<delete id="deleteDeviceModbusConfigById" parameterType="Long">
delete
from iot_device_modbus_config
where id = #{id}
</delete>
<delete id="deleteDeviceModbusConfigByIds" parameterType="Long">
delete from iot_device_modbus_config where id in
<foreach collection="array" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<select id="selectShortListByDeviceId" parameterType="com.fastbee.iot.domain.DeviceModbusConfig"
resultMap="DeviceModbusConfigResult">
select id,
device_id,
product_id,
identifier,
slave,
address,
bit_order,
is_readonly,
data_type,
quantity,
type,
sort
from iot_device_modbus_config
<where>
<if test="deviceId != null ">
and device_id = #{deviceId}
</if>
<if test="productId != null ">
and product_id = #{productId}
</if>
<if test="type != null ">
and type = #{type}
</if>
<if test="address != null ">
and address = #{address}
</if>
</where>
order by sort,id
</select>
<select id="selectByIdentify" resultMap="DeviceModbusConfigResult">
<include refid="selectDeviceModbusConfigVo"/>
where device_id = #{deviceId}
and identifier = #{identity}
</select>
<!-- 批量插入设备modbus配置 -->
<insert id="insertModbusConfigBatch" parameterType="java.util.List">
insert into iot_device_modbus_config (
device_id, product_id, identifier, slave, address, is_readonly,
data_type, quantity, type, bit_order, sort, del_flag,
create_by, create_time, update_by, update_time, remark
) values
<foreach collection="list" item="item" separator=",">
(
#{item.deviceId}, #{item.productId}, #{item.identifier}, #{item.slave}, #{item.address}, #{item.isReadonly},
#{item.dataType}, #{item.quantity}, #{item.type}, #{item.bitOrder}, #{item.sort}, #{item.delFlag},
#{item.createBy}, #{item.createTime}, #{item.updateBy}, #{item.updateTime}, #{item.remark}
)
</foreach>
</insert>
<!-- 批量更新设备modbus配置(使用 CASE WHEN 单条SQL实现) -->
<update id="updateModbusConfigBatch" parameterType="java.util.List">
update iot_device_modbus_config
<trim prefix="set" suffixOverrides=",">
<trim prefix="device_id = case" suffix="end,">
<foreach collection="list" item="item">
when id = #{item.id} then #{item.deviceId}
</foreach>
</trim>
<trim prefix="product_id = case" suffix="end,">
<foreach collection="list" item="item">
when id = #{item.id} then #{item.productId}
</foreach>
</trim>
<trim prefix="identifier = case" suffix="end,">
<foreach collection="list" item="item">
when id = #{item.id} then #{item.identifier}
</foreach>
</trim>
<trim prefix="slave = case" suffix="end,">
<foreach collection="list" item="item">
when id = #{item.id} then #{item.slave}
</foreach>
</trim>
<trim prefix="address = case" suffix="end,">
<foreach collection="list" item="item">
when id = #{item.id} then #{item.address}
</foreach>
</trim>
<trim prefix="is_readonly = case" suffix="end,">
<foreach collection="list" item="item">
when id = #{item.id} then #{item.isReadonly}
</foreach>
</trim>
<trim prefix="data_type = case" suffix="end,">
<foreach collection="list" item="item">
when id = #{item.id} then #{item.dataType}
</foreach>
</trim>
<trim prefix="quantity = case" suffix="end,">
<foreach collection="list" item="item">
when id = #{item.id} then #{item.quantity}
</foreach>
</trim>
<trim prefix="type = case" suffix="end,">
<foreach collection="list" item="item">
when id = #{item.id} then #{item.type}
</foreach>
</trim>
<trim prefix="bit_order = case" suffix="end,">
<foreach collection="list" item="item">
when id = #{item.id} then #{item.bitOrder}
</foreach>
</trim>
<trim prefix="sort = case" suffix="end,">
<foreach collection="list" item="item">
when id = #{item.id} then #{item.sort}
</foreach>
</trim>
<trim prefix="update_by = case" suffix="end,">
<foreach collection="list" item="item">
when id = #{item.id} then #{item.updateBy}
</foreach>
</trim>
<trim prefix="update_time = case" suffix="end,">
<foreach collection="list" item="item">
when id = #{item.id} then #{item.updateTime}
</foreach>
</trim>
<trim prefix="remark = case" suffix="end,">
<foreach collection="list" item="item">
when id = #{item.id} then #{item.remark}
</foreach>
</trim>
</trim>
where id in
<foreach collection="list" item="item" open="(" separator="," close=")">
#{item.id}
</foreach>
</update>
</mapper>

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.fastbee.iot.mapper.DeviceModbusParamsMapper">
<resultMap type="com.fastbee.iot.domain.DeviceModbusParams" id="DeviceModbusParamsResult">
<result property="id" column="id"/>
<result property="deviceId" column="device_id"/>
<result property="productId" column="product_id"/>
<result property="pollType" column="poll_type"/>
<result property="slaveId" column="slave_id"/>
<result property="statusDeter" column="status_deter"/>
<result property="deterTimer" column="deter_timer"/>
<result property="pollLength" column="poll_length"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="selectDeviceModbusParamsVo">
select id, device_id, product_id, poll_type, poll_length, slave_id, status_deter, deter_timer,
create_by, create_time, update_by, update_time, remark
from iot_device_modbus_params
</sql>
<select id="getByDeviceId" resultMap="DeviceModbusParamsResult">
<include refid="selectDeviceModbusParamsVo"/>
where device_id = #{deviceId}
</select>
<delete id="deleteByDeviceId">
delete from iot_device_modbus_params where device_id = #{deviceId}
</delete>
</mapper>

View File

@ -801,6 +801,38 @@ CREATE TABLE `iot_modbus_config` (
-- Records of iot_modbus_config -- Records of iot_modbus_config
-- ---------------------------- -- ----------------------------
-- ----------------------------
-- Table structure for iot_device_modbus_config
-- ----------------------------
DROP TABLE IF EXISTS `iot_device_modbus_config`;
CREATE TABLE `iot_device_modbus_config` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '业务id',
`device_id` bigint(20) NOT NULL COMMENT '所属设备id',
`product_id` bigint(20) NOT NULL COMMENT '所属产品id',
`identifier` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '关联属性',
`slave` int(10) NULL DEFAULT NULL COMMENT '从机地址',
`address` int(10) NOT NULL COMMENT '寄存器地址',
`is_readonly` tinyint(1) NOT NULL COMMENT '是否只读(0-否,1-是)',
`data_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'modbus数据类型',
`quantity` int(2) NULL DEFAULT NULL COMMENT '读取个数',
`type` tinyint(1) NOT NULL COMMENT '寄存器类型 1-IO寄存器 2-数据寄存器',
`bit_order` int(2) NULL DEFAULT NULL COMMENT 'bit位排序',
`sort` int(6) NULL DEFAULT NULL COMMENT '排序',
`del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`) USING BTREE,
KEY `idx_device_id` (`device_id`) USING BTREE COMMENT '设备id索引',
KEY `idx_product_id` (`product_id`) USING BTREE COMMENT '产品id索引'
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '设备modbus配置表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of iot_device_modbus_config
-- ----------------------------
-- ---------------------------- -- ----------------------------
-- Table structure for iot_modbus_job -- Table structure for iot_modbus_job
-- ---------------------------- -- ----------------------------
@ -848,6 +880,33 @@ CREATE TABLE `iot_modbus_params` (
-- Records of iot_modbus_params -- Records of iot_modbus_params
-- ---------------------------- -- ----------------------------
-- ----------------------------
-- Table structure for iot_device_modbus_params
-- ----------------------------
DROP TABLE IF EXISTS `iot_device_modbus_params`;
CREATE TABLE `iot_device_modbus_params` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '业务id',
`device_id` bigint(20) NOT NULL COMMENT '设备id',
`product_id` bigint(20) NOT NULL COMMENT '产品id',
`poll_type` tinyint(1) NULL DEFAULT NULL COMMENT '是否启动云端轮训(0-云端轮训,1-边缘采集)',
`slave_id` int(3) NULL DEFAULT NULL COMMENT '默认的子设备地址',
`status_deter` tinyint(1) NULL DEFAULT NULL COMMENT '子设备状态判断方式 1-设备数据 2- 网关',
`deter_timer` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '设备数据来判断子设备状态的时长(s)',
`poll_length` int(3) NULL DEFAULT NULL COMMENT '批量读取的个数',
`create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_device_id` (`device_id`) USING BTREE COMMENT '设备唯一索引',
KEY `idx_product_id` (`product_id`) USING BTREE COMMENT '产品id索引'
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '设备modbus配置参数' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of iot_device_modbus_params
-- ----------------------------
-- ---------------------------- -- ----------------------------
-- Table structure for iot_product -- Table structure for iot_product
-- ---------------------------- -- ----------------------------

View File

@ -73,3 +73,54 @@ ALTER TABLE `iot_firmware`
ALTER TABLE `iot_things_model_translate` ALTER TABLE `iot_things_model_translate`
ADD COLUMN `product_id` bigint(20) NULL DEFAULT NULL COMMENT '产品ID'; ADD COLUMN `product_id` bigint(20) NULL DEFAULT NULL COMMENT '产品ID';
-- 设备级modbus配置表(用于设备覆盖产品模板)
CREATE TABLE IF NOT EXISTS `iot_device_modbus_config` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '业务id',
`device_id` bigint(20) NOT NULL COMMENT '所属设备id',
`product_id` bigint(20) NOT NULL COMMENT '所属产品id(兜底物模型)',
`identifier` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '关联属性',
`slave` int(10) DEFAULT NULL COMMENT '从机地址',
`address` int(10) NOT NULL COMMENT '寄存器地址',
`is_readonly` tinyint(1) NOT NULL COMMENT '是否只读(0-否,1-是)',
`data_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'modbus数据类型',
`quantity` int(2) DEFAULT NULL COMMENT '读取个数',
`type` tinyint(1) NOT NULL COMMENT '寄存器类型 1-IO寄存器 2-数据寄存器',
`bit_order` int(2) DEFAULT NULL COMMENT 'bit位排序',
`sort` int(6) DEFAULT NULL COMMENT '排序',
`del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_device_identifier` (`device_id`,`identifier`),
KEY `idx_device_address_bit` (`device_id`,`address`,`bit_order`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC COMMENT='设备级modbus配置表(覆盖产品模板)';
-- ----------------------------
-- 设备级modbus配置参数表 iot_device_modbus_params
-- 继承自产品级 iot_modbus_params,用于设备级别的个性化配置
-- @author Sumuxi
-- @date 2026/1/14
-- ----------------------------
DROP TABLE IF EXISTS `iot_device_modbus_params`;
CREATE TABLE `iot_device_modbus_params` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '业务id',
`device_id` bigint(20) NOT NULL COMMENT '设备id',
`product_id` bigint(20) NOT NULL COMMENT '产品id',
`poll_type` tinyint(1) NULL DEFAULT NULL COMMENT '是否启动云端轮训(0-云端轮训,1-边缘采集)',
`slave_id` int(3) NULL DEFAULT NULL COMMENT '默认的子设备地址',
`status_deter` tinyint(1) NULL DEFAULT NULL COMMENT '子设备状态判断方式 1-设备数据 2- 网关',
`deter_timer` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '设备数据来判断子设备状态的时长(s)',
`poll_length` int(3) NULL DEFAULT NULL COMMENT '批量读取的个数',
`create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_device_id` (`device_id`) USING BTREE COMMENT '设备唯一索引',
KEY `idx_product_id` (`product_id`) USING BTREE COMMENT '产品id索引'
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '设备modbus配置参数' ROW_FORMAT = DYNAMIC;

View File

@ -3538,3 +3538,57 @@ CREATE TABLE `qrtz_simprop_triggers`
PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE, PRIMARY KEY (`sched_name`, `trigger_name`, `trigger_group`) USING BTREE,
CONSTRAINT `QRTZ_SIMPROP_TRIGGERS_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`) CONSTRAINT `QRTZ_SIMPROP_TRIGGERS_ibfk_1` FOREIGN KEY (`sched_name`, `trigger_name`, `trigger_group`) REFERENCES `qrtz_triggers` (`sched_name`, `trigger_name`, `trigger_group`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='同步机制的行锁表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='同步机制的行锁表';
-- 设备级modbus配置表(用于设备覆盖产品模板)
CREATE TABLE IF NOT EXISTS `iot_device_modbus_config` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '业务id',
`device_id` bigint(20) NOT NULL COMMENT '所属设备id',
`product_id` bigint(20) NOT NULL COMMENT '所属产品id(兜底物模型)',
`identifier` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '关联属性',
`slave` int(10) DEFAULT NULL COMMENT '从机地址',
`address` int(10) NOT NULL COMMENT '寄存器地址',
`is_readonly` tinyint(1) NOT NULL COMMENT '是否只读(0-否,1-是)',
`data_type` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT 'modbus数据类型',
`quantity` int(2) DEFAULT NULL COMMENT '读取个数',
`type` tinyint(1) NOT NULL COMMENT '寄存器类型 1-IO寄存器 2-数据寄存器',
`bit_order` int(2) DEFAULT NULL COMMENT 'bit位排序',
`sort` int(6) DEFAULT NULL COMMENT '排序',
`del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_device_identifier` (`device_id`,`identifier`),
KEY `idx_device_address_bit` (`device_id`,`address`,`bit_order`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci ROW_FORMAT=DYNAMIC COMMENT='设备级modbus配置表(覆盖产品模板)';
-- ----------------------------
-- 设备级modbus配置参数表 iot_device_modbus_params
-- 继承自产品级 iot_modbus_params,用于设备级别的个性化配置
-- @author Sumuxi
-- @date 2026/1/14
-- ----------------------------
DROP TABLE IF EXISTS `iot_device_modbus_params`;
CREATE TABLE `iot_device_modbus_params` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '业务id',
`device_id` bigint(20) NOT NULL COMMENT '设备id',
`product_id` bigint(20) NOT NULL COMMENT '产品id',
`poll_type` tinyint(1) NULL DEFAULT NULL COMMENT '是否启动云端轮训(0-云端轮训,1-边缘采集)',
`slave_id` int(3) NULL DEFAULT NULL COMMENT '默认的子设备地址',
`status_deter` tinyint(1) NULL DEFAULT NULL COMMENT '子设备状态判断方式 1-设备数据 2- 网关',
`deter_timer` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '设备数据来判断子设备状态的时长(s)',
`poll_length` int(3) NULL DEFAULT NULL COMMENT '批量读取的个数',
`create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '创建者',
`create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '更新者',
`update_time` datetime NULL DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_device_id` (`device_id`) USING BTREE COMMENT '设备唯一索引',
KEY `idx_product_id` (`product_id`) USING BTREE COMMENT '产品id索引'
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci COMMENT = '设备modbus配置参数' ROW_FORMAT = DYNAMIC;

View File

@ -0,0 +1,28 @@
import request from '@/utils/request';
// 查询设备modbus配置列表
export function listDeviceConfig(query) {
return request({
url: '/deviceModbus/config/list',
method: 'get',
params: query,
});
}
// 批量新增或更新设备modbus配置
export function addDeviceBatch(data) {
return request({
url: '/deviceModbus/config/addBatch',
method: 'post',
data: data,
});
}
// 删除设备modbus配置
export function delDeviceConfig(ids) {
return request({
url: '/deviceModbus/config/' + ids,
method: 'delete',
});
}

View File

@ -1,19 +1,18 @@
export function loadBMap() { export function loadBMap() {
return new Promise(function(resolve, reject) { return new Promise(function (resolve, reject) {
if (typeof BMap !== 'undefined') { if (typeof BMap !== 'undefined') {
resolve(BMap) resolve(BMap)
return true return true
} }
window.onBMapCallback = function() { window.onBMapCallback = function () {
resolve(BMap) resolve(BMap)
} }
// 使用https协议需要添加一下meta标签 // 使用https协议需要添加一下meta标签
var protocolStr = document.location.protocol; var protocolStr = document.location.protocol;
if(protocolStr == "https:") if (protocolStr == "https:") {
{
let meta = document.createElement('meta') let meta = document.createElement('meta')
meta.httpEquiv = 'Content-Security-Policy' meta.httpEquiv = 'Content-Security-Policy'
meta.content ='upgrade-insecure-requests' meta.content = 'upgrade-insecure-requests'
meta.onerror = reject meta.onerror = reject
document.head.appendChild(meta) document.head.appendChild(meta)
} }
@ -21,7 +20,7 @@ export function loadBMap() {
let script = document.createElement('script') let script = document.createElement('script')
script.type = 'text/javascript' script.type = 'text/javascript'
script.src = script.src =
'http://api.map.baidu.com/api?v=2.0&ak='+ process.env.VUE_APP_BAI_DU_AK +'&s=1&__ec_v__=20190126&callback=onBMapCallback' 'http://api.map.baidu.com/api?v=2.0&ak=' + process.env.VUE_APP_BAI_DU_AK + '&s=1&__ec_v__=20190126&callback=onBMapCallback'
script.onerror = reject script.onerror = reject
document.head.appendChild(script) document.head.appendChild(script)
}) })

View File

@ -1,5 +1,5 @@
<template> <template>
<el-card style="margin: 6px; padding-bottom: 100px"> <el-card style="margin: 6px; padding-bottom: 100px" v-loading="pageLoading" element-loading-text="加载中...">
<el-tabs id="deviceDetailTab" v-model="activeName" tab-position="left" @tab-click="tabChange" style="padding: 10px; min-height: 400px" lazy> <el-tabs id="deviceDetailTab" v-model="activeName" tab-position="left" @tab-click="tabChange" style="padding: 10px; min-height: 400px" lazy>
<el-tab-pane name="basic"> <el-tab-pane name="basic">
<span slot="label">{{ $t('device.device-edit.148398-0') }}</span> <span slot="label">{{ $t('device.device-edit.148398-0') }}</span>
@ -175,6 +175,16 @@
<device-timer ref="deviceTimer" :device="form" /> <device-timer ref="deviceTimer" :device="form" />
</el-tab-pane> </el-tab-pane>
<el-tab-pane
name="deviceModbusConfig"
:disabled="form.deviceId == 0"
v-if="form.protocolCode == 'MODBUS-RTU' || form.protocolCode == 'MODBUS-TCP' || form.protocolCode == 'MODBUS-JSON-HP'"
lazy
>
<span slot="label"><span style="color: red">*</span>{{ $t('product.product-edit.473153-80') }}</span>
<device-modbus ref="deviceModbusConfig" :device="form" />
</el-tab-pane>
<el-tab-pane name="deviceUser" :disabled="form.deviceId == 0" lazy> <el-tab-pane name="deviceUser" :disabled="form.deviceId == 0" lazy>
<span slot="label">{{ $t('device.device-edit.148398-48') }}</span> <span slot="label">{{ $t('device.device-edit.148398-48') }}</span>
<device-user ref="deviceUser" :device="form" @userEvent="getUserData($event)" /> <device-user ref="deviceUser" :device="form" @userEvent="getUserData($event)" />
@ -329,6 +339,7 @@ import deviceMonitor from './device-monitor';
import deviceStatistic from './device-statistic'; import deviceStatistic from './device-statistic';
import instructionParsing from './instruction-parsing'; import instructionParsing from './instruction-parsing';
import deviceModbusTask from './device-modbus-task'; import deviceModbusTask from './device-modbus-task';
import deviceModbus from './device-modbus';
import deviceTimer from './device-timer'; import deviceTimer from './device-timer';
import channel from '../sip/channel'; import channel from '../sip/channel';
import player from '@/views/components/player/player.vue'; import player from '@/views/components/player/player.vue';
@ -367,6 +378,7 @@ export default {
runningStatus, runningStatus,
productList, productList,
deviceTimer, deviceTimer,
deviceModbus,
deviceFuncLog, deviceFuncLog,
deviceVideo, deviceVideo,
siptalk, siptalk,
@ -415,6 +427,8 @@ export default {
}, },
data() { data() {
return { return {
// 页面加载状态
pageLoading: false,
// 二维码内容 // 二维码内容
qrText: 'fastbee', qrText: 'fastbee',
// 打开设备配置对话框 // 打开设备配置对话框
@ -500,6 +514,8 @@ export default {
this.form.deviceId = this.$route.query && this.$route.query.deviceId; this.form.deviceId = this.$route.query && this.$route.query.deviceId;
if (this.form.deviceId != 0) { if (this.form.deviceId != 0) {
// this.connectMqtt(); // this.connectMqtt();
// 开启页面加载状态
this.pageLoading = true;
this.getDevice(this.form.deviceId); this.getDevice(this.form.deviceId);
} }
}, },
@ -726,6 +742,9 @@ export default {
this.form.protocolCode = response.data.protocolCode; this.form.protocolCode = response.data.protocolCode;
// 获取设备状态和物模型 // 获取设备状态和物模型
this.getDeviceStatusWitchThingsModel(response); this.getDeviceStatusWitchThingsModel(response);
}).catch(() => {
// 请求失败时关闭加载状态
this.pageLoading = false;
}); });
}, },
/**用户是否拥有分享设备权限*/ /**用户是否拥有分享设备权限*/
@ -962,6 +981,10 @@ export default {
this.$modal.alertSuccess(this.$t('device.device-edit.148398-70')); this.$modal.alertSuccess(this.$t('device.device-edit.148398-70'));
this.loadMap(); this.loadMap();
// Modbus协议设备新建后跳转到Modbus配置页面
if (this.form.protocolCode == 'MODBUS-RTU' || this.form.protocolCode == 'MODBUS-TCP' || this.form.protocolCode == 'MODBUS-JSON-HP') {
this.activeName = 'deviceModbusConfig';
}
} }
}); });
} }
@ -970,30 +993,38 @@ export default {
}, },
/** 获取设备状态和物模型 **/ /** 获取设备状态和物模型 **/
async getDeviceStatusWitchThingsModel(response) { async getDeviceStatusWitchThingsModel(response) {
// 获取缓存物模型 try {
response.data.cacheThingsModel = await this.getCacheThingsModdel(response.data.productId); // 并行获取缓存物模型和设备运行状态,加快加载速度
// 获取设备运行状态 const [cacheThingsModel, thingsModels] = await Promise.all([
response.data.thingsModels = await this.getDeviceStatus(response.data); this.getCacheThingsModdel(response.data.productId),
//分享设备过滤没有权限的物模型 this.getDeviceStatus(response.data),
// if (response.data.isOwner == 0) { ]);
// for (let i = 0; i < response.data.thingsModels.length; i++) { response.data.cacheThingsModel = cacheThingsModel;
// if (response.data.userPerms.indexOf(response.data.thingsModels[i].id) == -1) { response.data.thingsModels = thingsModels;
// response.data.thingsModels.splice(i--, 1); //分享设备过滤没有权限的物模型
// } // if (response.data.isOwner == 0) {
// } // for (let i = 0; i < response.data.thingsModels.length; i++) {
// } // if (response.data.userPerms.indexOf(response.data.thingsModels[i].id) == -1) {
// 格式化物模型,拆分出监测值,数组添加前缀 // response.data.thingsModels.splice(i--, 1);
this.formatThingsModel(response.data); // }
this.form = response.data; // }
// 解析设备摘要 // }
if (this.form.summary != null && this.form.summary != '') { // 格式化物模型,拆分出监测值,数组添加前缀
this.summary = JSON.parse(this.form.summary); this.formatThingsModel(response.data);
this.form = response.data;
// 解析设备摘要
if (this.form.summary != null && this.form.summary != '') {
this.summary = JSON.parse(this.form.summary);
}
this.oldDeviceStatus = this.form.status;
this.loadMap();
//Mqtt订阅
this.connectMqtt();
this.mqttSubscribe(this.form);
} finally {
// 关闭页面加载状态
this.pageLoading = false;
} }
this.oldDeviceStatus = this.form.status;
this.loadMap();
//Mqtt订阅
this.connectMqtt();
this.mqttSubscribe(this.form);
}, },
/**选择产品 */ /**选择产品 */
selectProduct() { selectProduct() {

View File

@ -0,0 +1,854 @@
<template>
<div class="modbus-wrap">
<div style="margin-bottom: 0px" v-if="showParamsSection">
<div class="card-title">
<div style="display: flex; justify-content: flex-start; margin-bottom: 30px">
<!-- <svg-icon icon-class="modbus" style="font-size: 20px;margin-top: 4px;margin-right: 10px;" /> -->
<div class="title" style="font-size: 22px">{{ $t('product.product-modbus.562372-0') }}</div>
<el-tooltip class="item" effect="dark" :content="$t('product.product-modbus.562372-1')" placement="top">
<i class="el-icon-question" style="margin-left: 10px; margin-top: 5px; font-size: large" />
</el-tooltip>
</div>
</div>
<div class="button-group" style="margin-top: -10px">
<el-button type="primary" plain icon="el-icon-edit" size="mini" @click="setSlave" v-if="!enableSetSlave && deviceId > 0">{{ $t('product.product-modbus.562372-2') }}</el-button>
<el-button type="primary" plain size="mini" @click="saveSlave" v-if="enableSetSlave" v-hasPermi="['modbus:config:edit']">{{ $t('product.product-modbus.562372-3') }}</el-button>
<el-button type="info" plain size="mini" @click="cancelSlave" v-if="enableSetSlave">{{ $t('product.product-modbus.562372-4') }}</el-button>
</div>
<el-form ref="form" :model="form" label-width="180px" :rules="rules" style="margin-left: -150px">
<el-form-item label="">
<el-tooltip placement="top">
<div slot="content" class="test_div">
{{ $t('product.product-modbus.562372-6') }}
<br />
{{ $t('product.product-modbus.562372-7') }}
<br />
{{ $t('product.product-modbus.562372-8') }}
</div>
<!-- elementui图标库:显示黑色的问号图标 -->
<i class="el-icon-question" style="color: dodgerblue" />
<span>{{ $t('product.product-modbus.562372-5') }}</span>
</el-tooltip>
<span style="margin-right: 10px">{{ $t('product.product-modbus.562372-5') }}</span>
<el-radio-group :disabled="!enableSetSlave" v-model="form.statusDeter">
<el-radio v-for="dict in dict.type.device_status_deter" :key="dict.value" :label="Number(dict.value)">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.statusDeter == '1'" label="">
<span>
<el-tooltip placement="top">
<div slot="content" class="test_div">
{{ $t('product.product-modbus.562372-10') }}
</div>
<!-- elementui图标库:显示黑色的问号图标 -->
<i class="el-icon-question" style="color: dodgerblue" />
</el-tooltip>
</span>
<span style="margin-right: 10px">{{ $t('product.product-modbus.562372-9') }}</span>
<el-select :disabled="!enableSetSlave" v-model="form.deterTimer" :placeholder="$t('product.product-modbus.562372-11')">
<el-option v-for="dict in dict.type.iot_modbus_poll_time" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
<span style="font-size: 14px; color: darkgrey">{{ $t('product.product-modbus.562372-12') }}</span>
</el-form-item>
<el-form-item label="">
<span style="margin-right: 10px">{{ $t('product.product-modbus.562372-13') }}</span>
<el-radio-group :disabled="!enableSetSlave" v-model="form.pollType" @change="changePollType">
<el-radio v-for="dict in dict.type.data_collect_type" :key="dict.value" :label="Number(dict.value)">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="" prop="slaveId">
<span style="margin-right: 10px">{{ $t('product.product-modbus.562372-14') }}</span>
<el-input :disabled="!enableSetSlave" v-model="form.slaveId" size="small" :label="$t('product.product-modbus.562372-15')" style="width: 230px" type="number"></el-input>
<span style="font-size: 14px; color: darkgrey">{{ $t('product.product-modbus.562372-16') }}</span>
</el-form-item>
</el-form>
<el-divider></el-divider>
</div>
<!-- IO寄存器 -->
<div class="head">
<div class="card-title">
<div style="display: flex; justify-content: flex-start; margin-bottom: 10px">
<!-- <svg-icon icon-class="modbus" style="font-size: 20px;margin-top: 4px;margin-right: 10px;" /> -->
<div class="title" style="font-size: 22px">{{ $t('product.product-modbus.562372-17') }}</div>
<el-tooltip class="item" effect="dark" :content="$t('product.product-modbus.562372-18')" placement="top">
<i class="el-icon-question" style="margin-left: 10px; margin-top: 5px; font-size: large" />
</el-tooltip>
</div>
</div>
<div class="button-group">
<el-button type="primary" plain icon="el-icon-edit" size="mini" @click="editIOModbus" v-if="!enableEditIO && deviceId > 0">{{ $t('product.product-modbus.562372-19') }}</el-button>
<el-button type="primary" plain size="mini" @click="submitFormIO" v-if="enableEditIO" v-hasPermi="['modbus:config:edit']">{{ $t('product.product-modbus.562372-22') }}</el-button>
<el-button type="info" plain size="mini" @click="handleCancelIO" v-if="enableEditIO">{{ $t('product.product-modbus.562372-23') }}</el-button>
<div class="right-btns">
<el-button type="primary" plain icon="el-icon-upload" v-if="enableImportExport" size="mini" @click.stop="batchImport('isSelectIo')" v-hasPermi="['modbus:config:import']">
{{ $t('product.product-modbus.562372-20') }}
</el-button>
<el-button plain icon="el-icon-download" v-if="enableImportExport" size="mini" @click.stop="exportModbus('isSelectIo')" v-hasPermi="['modbus:config:export']">
{{ $t('product.product-modbus.562372-21') }}
</el-button>
</div>
</div>
</div>
<el-table style="margin-bottom: 10px" v-loading="loadingIO" :data="configList" :key="configTableKey" data-key="id" ref="IOTable">
<el-table-column :label="$t('product.product-modbus.562372-24')" align="center" prop="sort"></el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-25')" align="center" prop="identifier">
<template slot-scope="scope">
<el-select
filterable
:disabled="!enableEditIO"
v-model="scope.row.identifier"
:placeholder="$t('product.product-modbus.562372-26')"
:ref="`selectIo${scope.$index}`"
size="mini"
@change="(e) => updateSelectThingsModel({ newVal: e, oldVal: $refs[`selectIo${scope.$index}`].value, justiceSelect: 'isSelectIo' })"
>
<el-option key="0" label="" value="" disabled style="width: 300px">
<span style="float: left">{{ $t('product.product-modbus.562372-27') }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ $t('product.product-modbus.562372-28') }}</span>
</el-option>
<el-option v-for="item in thingsModelList" :key="item.identifier" :label="`${item.modelName} (${item.identifier})`" :value="item.identifier" style="width: 300px" :disabled="!item.isSelectIo">
<span style="float: left">{{ item.modelName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.identifier }}</span>
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-29')" align="center" prop="slave">
<template slot-scope="scope">
<el-input-number :disabled="!enableEditIO" v-model="scope.row.slave" size="small" :min="1" :max="256" :label="$t('product.product-modbus.562372-29')"></el-input-number>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-30')" align="center" prop="address">
<template slot-scope="scope">
<el-input-number :disabled="!enableEditIO" v-model="scope.row.address" size="small" :min="0" :max="400000" :label="$t('product.product-modbus.562372-30')"></el-input-number>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-31')" align="center" prop="isReadonly">
<template slot-scope="scope">
<el-radio-group :disabled="!enableEditIO" size="mini" v-model="scope.row.isReadonly">
<el-radio-button label="0">{{ $t('product.product-modbus.562372-32') }}</el-radio-button>
<el-radio-button label="1">{{ $t('product.product-modbus.562372-33') }}</el-radio-button>
</el-radio-group>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-311')" align="center" prop="bitOrder">
<template slot-scope="scope">
<el-input-number :disabled="!enableEditIO" v-model="scope.row.bitOrder" size="small" :min="0" :max="15" :label="$t('product.product-modbus.562372-311')"></el-input-number>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-34')" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button v-if="enableEditIO" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row, scope.$index, configList, 'isSelectIo')" v-hasPermi="['iot:config:remove']">
{{ $t('product.product-modbus.562372-35') }}
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParamsIO.pageNum" :limit.sync="queryParamsIO.pageSize" @pagination="getIOList" />
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-if="enableEditIO">{{ $t('product.product-modbus.562372-36') }}</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAddBatch('isSelectIo')" v-if="enableEditIO">{{ $t('product.product-modbus.562372-37') }}</el-button>
</el-col>
</el-row>
<!-- 数据寄存器 -->
<div class="head">
<el-divider></el-divider>
<div class="card-title">
<div style="display: flex; justify-content: flex-start; margin-bottom: 10px">
<!-- <svg-icon icon-class="modbus" style="font-size: 20px;margin-top: 4px;margin-right: 10px;" /> -->
<div class="title" style="font-size: 22px">{{ $t('product.product-modbus.562372-38') }}</div>
<el-tooltip class="item" effect="dark" :content="$t('product.product-modbus.562372-39')" placement="top">
<i class="el-icon-question" style="margin-left: 10px; margin-top: 5px; font-size: large" />
</el-tooltip>
</div>
</div>
<div class="button-group">
<el-button type="primary" plain icon="el-icon-edit" size="mini" @click="editDataModbus" v-if="!enableEditData && deviceId > 0">{{ $t('product.product-modbus.562372-19') }}</el-button>
<el-button type="primary" plain size="mini" @click="submitFormData" v-if="enableEditData" v-hasPermi="['modbus:config:edit']">{{ $t('product.product-modbus.562372-22') }}</el-button>
<el-button type="info" plain size="mini" @click="handleCancelData" v-if="enableEditData">{{ $t('product.product-modbus.562372-23') }}</el-button>
<div class="right-btns">
<el-button type="primary" plain icon="el-icon-upload" v-if="enableImportExport" size="mini" @click.stop="batchImport('isSelectData')" v-hasPermi="['modbus:config:import']">
{{ $t('product.product-modbus.562372-40') }}
</el-button>
<el-button plain icon="el-icon-download" v-if="enableImportExport" size="mini" @click.stop="exportModbus('isSelectIo')" v-hasPermi="['modbus:config:export']">
{{ $t('product.product-modbus.562372-41') }}
</el-button>
</div>
</div>
</div>
<el-table style="margin-bottom: 10px" v-loading="loadingData" :data="dataModbusList" :key="dataTableKey" ref="Dataable">
<el-table-column :label="$t('product.product-modbus.562372-24')" align="center" prop="sort"></el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-25')" align="center" prop="identifier" width="250px">
<template slot-scope="scope">
<el-select
filterable
:disabled="!enableEditData"
v-model="scope.row.identifier"
:placeholder="$t('product.product-modbus.562372-26')"
size="mini"
:ref="`selectData${scope.$index}`"
@change="(e) => updateSelectThingsModel({ newVal: e, oldVal: $refs[`selectData${scope.$index}`].value, justiceSelect: 'isSelectData' })"
>
<el-option key="0" label="" value="" disabled style="width: 300px">
<span style="float: left">{{ $t('product.product-modbus.562372-27') }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ $t('product.product-modbus.562372-28') }}</span>
</el-option>
<el-option v-for="item in thingsModelList" :key="item.identifier" :label="`${item.modelName} (${item.identifier})`" :value="item.identifier" style="width: 300px" :disabled="!item.isSelectData">
<!-- 判断是否可选 -->
<span style="float: left">{{ item.modelName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.identifier }}</span>
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-29')" align="center" prop="identifier" width="170px">
<template slot-scope="scope">
<el-input-number :disabled="!enableEditData" v-model="scope.row.slave" size="small" :min="1" :max="256" :label="$t('product.product-modbus.562372-29')"></el-input-number>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-30')" align="center" prop="address" width="170px">
<template slot-scope="scope">
<el-input-number :disabled="!enableEditData" v-model="scope.row.address" size="small" :min="0" :max="400000" :label="$t('product.product-modbus.562372-30')"></el-input-number>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-31')" align="center" prop="isReadonly" width="170px">
<template slot-scope="scope">
<el-radio-group :disabled="!enableEditData" size="mini" v-model="scope.row.isReadonly">
<el-radio-button label="0">{{ $t('product.product-modbus.562372-32') }}</el-radio-button>
<el-radio-button label="1">{{ $t('product.product-modbus.562372-33') }}</el-radio-button>
</el-radio-group>
<!-- <el-tag v-show="!enableEditData">{{scope.row.isReadonly=='0'?'只读':'读写'}}</el-tag> -->
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-42')" align="center" prop="dataType" width="200px">
<template slot-scope="scope">
<el-select :disabled="!enableEditData" size="mini" v-model="scope.row.dataType" :placeholder="$t('product.product-modbus.562372-42')" style="display: inline-block; padding-right: 10px">
<el-option v-for="dict in dict.type.iot_modbus_data_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-43')" align="center" prop="quantity" width="170px">
<template slot-scope="scope">
<el-input-number :disabled="!enableEditData" v-model="scope.row.quantity" size="small" :min="1" :max="256" :label="$t('product.product-modbus.562372-43')"></el-input-number>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-34')" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button v-if="enableEditData" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row, scope.$index, dataModbusList, 'isSelectData')" v-hasPermi="['iot:config:remove']">
{{ $t('product.product-modbus.562372-35') }}
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="dataTotal > 0" :total="dataTotal" :page.sync="queryParamsData.pageNum" :limit.sync="queryParamsData.pageSize" @pagination="getDataList" />
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAddData" v-if="enableEditData">{{ $t('product.product-modbus.562372-44') }}</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAddBatch('isSelectData')" v-if="enableEditData">{{ $t('product.product-modbus.562372-45') }}</el-button>
</el-col>
</el-row>
<!-- 选择物模型 -->
<things-list ref="thingsListRef" :productId="productId" @productEvent="getThingsData($event)" :justiceSelect="justiceSelect" />
<!-- 批量导入 -->
<import-batch ref="importBatchRef" :productId="productId" @data-imported="handleDataImport" :justiceSelect="justiceSelect" />
</div>
</template>
<script>
import { listDeviceConfig, addDeviceBatch } from '@/api/iot/deviceModbusConfig';
import { getlListModbus } from '@/api/iot/model';
// 设备复用产品的物模型选择列表
import thingsList from '../product/things-model-list';
// 设备复用产品的批量导入组件
import importBatch from '../product/components/batchImportModbus';
import Sortable from 'sortablejs';
import { addOrUpdate, getByProductId } from '@/api/iot/params';
import { listProductJob } from '@/api/iot/modbusJob';
export default {
name: 'device-modbus',
dicts: ['iot_modbus_data_type', 'iot_yes_no', 'data_collect_type', 'device_status_deter', 'iot_modbus_poll_time'],
props: {
device: {
type: Object,
default: null,
},
},
components: {
thingsList,
importBatch,
},
watch: {
// 获取到父组件传递的productId后,刷新列表
device: function (newVal, oldVal) {
this.productInfo = newVal;
if (this.productInfo && this.productInfo.productId != 0) {
this.thingsModelParams.productId = this.productInfo.productId;
this.queryParamsIO.productId = this.productInfo.productId;
this.queryParamsData.productId = this.productInfo.productId;
this.queryParamsIO.deviceId = this.productInfo.deviceId;
this.queryParamsData.deviceId = this.productInfo.deviceId;
this.productId = this.productInfo.productId;
this.deviceId = this.productInfo.deviceId;
this.getIOList();
this.getDataList();
this.getThingsModelList();
this.getParams();
this.sendPollType();
}
},
enableEditIO: function (n, old) {
//设置行是否拖拽
if (this.sortableIo) this.sortableIo.option('disabled', !n);
//关闭编辑刷新页面
if (!n) {
this.getIOList();
this.getThingsModelList();
}
//清空删除数组
this.delIoIds = [];
this.getThingsModelList();
},
enableEditData: function (n, old) {
//设置行是否拖拽
if (this.sortableData) this.sortableData.option('disabled', !n);
//关闭编辑刷新页面
if (!n) {
this.getDataList();
this.getThingsModelList();
}
//清空删除数组
this.delDataIds = [];
},
},
data() {
return {
// 遮罩层
loadingIO: true,
loadingData: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数
total: 0,
// modbus配置表格数据
configList: [],
//io表格是否可以编辑
enableEditIO: false,
//子设备配置是否可编辑
enableSetSlave: false,
// 弹出层标题
title: '',
// 是否显示弹出层
open: false,
// 查询参数
queryParamsIO: {
pageNum: 1,
pageSize: 10,
identifier: null,
address: null,
isReadonly: null,
dataType: null,
quantity: null,
type: 1,
productId: 0,
deviceId: 0,
},
// 查询参数
queryParamsData: {
pageNum: 1,
pageSize: 10,
identifier: null,
address: null,
isReadonly: null,
dataType: null,
quantity: null,
type: 2,
productId: 0,
deviceId: 0,
},
//产品id
productId: 0,
//设备id
deviceId: 0,
//数据寄存器
dataModbusList: [],
//io表格是否可以编辑
enableEditData: false,
//数据寄存器总条数
dataTotal: 0,
// 数据寄存器遮罩层
dataLoading: true,
//可选属性
thingsModelList: [],
//物模型遮罩层
thingsLoading: true,
//thingsModel
thingsModelParams: {
pageNum: 1,
pageSize: 1000,
productId: 0,
},
thingsTotal: 0,
// 表单参数
form: {
statusDeter: 1,
slaveId: null,
pollType: 0,
deterTimer: '300',
},
productInfo: {},
// 表单校验
rules: {
slaveId: [{ required: true, message: this.$t('product.product-modbus.562372-46'), trigger: 'blur' }],
address: [{ required: true, message: this.$t('product.product-modbus.562372-47'), trigger: 'blur' }],
isReadonly: [{ required: true, message: this.$t('product.product-modbus.562372-48'), trigger: 'blur' }],
dataType: [{ required: true, message: this.$t('product.product-modbus.562372-49'), trigger: 'change' }],
quantity: [{ required: true, message: this.$t('product.product-modbus.562372-50'), trigger: 'blur' }],
type: [{ required: true, message: this.$t('product.product-modbus.562372-51'), trigger: 'change' }],
},
//IO行拖拽对象
sortableIo: null,
//ioTable的key值,主要用于拖拽后重绘
configTableKey: 0,
//ioTable删除的id
delIoIds: [],
//数据寄存器行拖拽对象
sortableData: null,
//dataTable的key值,主要用于拖拽后重绘
dataTableKey: 1000,
//dataTable删除的id
delDataIds: [],
//判断多选物模型是io还是data,isSelectIo是判断是否可选io的,isSelectData是判断数据寄存器的,都是false不可选,true可选
justiceSelect: 'isSelectData',
jobTotal: 0,
// 设备模式下默认关闭导入导出按钮
enableImportExport: false,
// 显示网关子设备配置(状态判断/采集方式/默认子设备地址)
showParamsSection: true,
};
},
mounted() {
this.rowDropIo();
this.rowDropData();
if (this.device && this.device.deviceId) {
this.productInfo = this.device;
this.productId = this.device.productId;
this.deviceId = this.device.deviceId;
this.queryParamsIO.productId = this.device.productId;
this.queryParamsIO.deviceId = this.device.deviceId;
this.queryParamsData.productId = this.device.productId;
this.queryParamsData.deviceId = this.device.deviceId;
this.thingsModelParams.productId = this.device.productId;
this.getIOList();
this.getDataList();
this.getThingsModelList();
this.getParams();
this.getTaskList(this.productInfo.productId);
this.sendPollType();
}
},
methods: {
/** 查询modbusIO寄存器列表 */
getIOList() {
this.loadingIO = true;
listDeviceConfig(this.queryParamsIO).then((response) => {
this.configList = response.rows;
this.total = response.total;
this.loadingIO = false;
});
},
//传值
sendPollType() {
if (!this.showParamsSection) return;
this.$emit('sendPollType', this.form.pollType);
},
handleDataImport(data) {
this.configList = data;
},
/** 查询modbus数据寄存器列表 */
getDataList() {
this.loadingData = true;
listDeviceConfig(this.queryParamsData).then((response) => {
this.dataModbusList = response.rows;
this.dataTotal = response.total;
this.loadingData = false;
});
this.$nextTick(() => {
this.$refs.Dataable.bodyWrapper.scrollTop = 0;
});
},
/** 查询产品物模型列表 */
getThingsModelList() {
this.thingsLoading = true;
getlListModbus(this.thingsModelParams).then((response) => {
this.$refs.thingsListRef.modelList = response.rows;
this.thingsModelList = response.rows;
this.thingsTotal = response.total;
this.thingsLoading = false;
});
},
// 取消按钮
cancel() {
this.open = false;
this.reset();
},
// 表单重置
reset() {
this.form = {
statusDeter: 1,
slaveId: null,
// pollType: '0',
deterTime: '300',
};
this.resetForm('form');
},
// 多选框选中数据
handleSelectionChange(selection) {
this.ids = selection.map((item) => item.id);
this.single = selection.length !== 1;
this.multiple = !selection.length;
},
/** 新增IO寄存器行操作 */
handleAdd() {
var d = {
identifier: '',
slave: 1,
address: 1,
isReadonly: 1,
type: 1,
quantity: 1,
sort: this.configList.length + 1,
};
this.configList.push(d);
setTimeout(() => {
this.$refs.IOTable.setCurrentRow(d);
}, 10);
},
/** 新增数据寄存器行操作 */
handleAddData() {
var d = {
identifier: '',
slave: 1,
address: 1,
isReadonly: 1,
dataType: 'ushort',
quantity: 1,
type: 2,
sort: this.dataModbusList.length + 1,
};
this.dataModbusList.push(d);
setTimeout(() => {
this.$refs.Dataable.setCurrentRow(d);
}, 10);
},
handleAddBatch(justiceSelect) {
this.justiceSelect = justiceSelect;
this.$refs.thingsListRef.open = true;
this.$refs.thingsListRef.selectedList = justiceSelect == 'isSelectData' ? this.dataModbusList : this.configList;
this.$refs.thingsListRef.getList();
},
/**点击编辑IO控制器*/
editIOModbus() {
this.enableEditIO = !this.enableEditIO;
},
/**点击取消IO寄存器编辑按钮*/
handleCancelIO() {
this.enableEditIO = !this.enableEditIO;
},
/**点击取消数据寄存器编辑按钮*/
handleCancelData() {
this.enableEditData = !this.enableEditData;
},
/**点击编辑数据寄存器*/
editDataModbus() {
this.enableEditData = !this.enableEditData;
},
/** IO寄存器提交按钮 */
submitFormIO() {
const configList = [];
this.configList.forEach((config, index) => {
if (config.identifier) {
config.sort = configList.length + 1;
configList.push(config);
}
});
this.loadingIO = true;
const data = { productId: this.productId, deviceId: this.deviceId, configList: configList, delIds: this.delIoIds };
addDeviceBatch(data)
.then((response) => {
this.$modal.msgSuccess('保存成功');
this.getIOList();
this.open = false;
this.loadingIO = false;
this.enableEditIO = false;
})
.catch((err) => {
this.loadingIO = false;
});
},
/** 数据寄存器提交按钮 */
submitFormData() {
const dataModbusList = [];
this.dataModbusList.forEach((dataModbus, index) => {
if (dataModbus.identifier) {
dataModbus.sort = dataModbusList.length + 1;
dataModbusList.push(dataModbus);
}
});
this.loadingData = true;
const data = { productId: this.productId, deviceId: this.deviceId, configList: dataModbusList, delIds: this.delDataIds };
addDeviceBatch(data)
.then((response) => {
this.$modal.msgSuccess(this.$t('product.product-modbus.562372-52'));
this.open = false;
this.enableEditData = false;
this.loadingData = false;
})
.catch((err) => {
this.loadingData = false;
});
},
/** 删除按钮操作 */
handleDelete(row, index, list, justiceSelect) {
const item = list.splice(index, 1)[0];
//如果是删除数据寄存器,并且有id就存id
if (justiceSelect == 'isSelectData' && row.id) this.delDataIds.push(row.id);
//如果是删除io,并且有id就存id
if (justiceSelect == 'isSelectIo' && row.id) this.delIoIds.push(row.id);
//处理选项是否可选
this.updateSelectThingsModel({ justiceSelect, oldVal: item.identifier });
},
/*获取选择的物模型*/
getThingsData(ids) {
const list = this.justiceSelect == 'isSelectData' ? this.dataModbusList : this.configList;
ids.forEach((id, index) => {
const findIndex = list.findIndex((item) => item.identifier == id);
if (findIndex == -1) {
list.push({
identifier: id,
slave: 1,
address: 1,
isReadonly: 1,
dataType: 'ushort',
quantity: 1,
type: this.justiceSelect == 'isSelectData' ? 2 : 1,
sort: list.length + 1,
});
this.updateSelectThingsModel({ justiceSelect: this.justiceSelect, newVal: id });
}
});
},
/**
* io拖拽行
*/
rowDropIo() {
const tbodyIo = this.$refs.IOTable.$el.children[2].children[0].children[1];
this.sortableIo = new Sortable(tbodyIo, {
disabled: true,
onEnd: ({ newIndex, oldIndex, to }) => {
this.dealDrop(this.configList, newIndex, oldIndex);
this.configTableKey++;
this.sortableIo.destroy();
this.$nextTick(() => {
this.rowDropIo();
this.sortableIo.option('disabled', false);
});
},
});
},
/**
* 数据寄存器拖拽
*/
rowDropData() {
const tbodyData = this.$refs.Dataable.$el.children[2].children[0].children[1];
this.sortableData = new Sortable(tbodyData, {
disabled: true,
onEnd: ({ newIndex, oldIndex, to }) => {
this.dealDrop(this.dataModbusList, newIndex, oldIndex);
this.dataTableKey++;
this.sortableData.destroy();
this.$nextTick(() => {
this.rowDropData();
this.sortableData.option('disabled', false);
});
},
});
},
//处理拖拽后的数据
dealDrop(list, newIndex, oldIndex) {
if (oldIndex == newIndex) return;
//找到选中那列,移除出数组
const curRow = list.splice(oldIndex, 1)[0];
//将选择中那列查到新位置
list.splice(newIndex, 0, curRow);
},
/**
* 更新物模型是否可选
*/
updateSelectThingsModel({ oldVal, newVal, justiceSelect }) {
//直接改前后值
const oldIndex = oldVal ? this.thingsModelList.findIndex((thingsModel) => thingsModel.identifier == oldVal) : -1;
const newIndex = newVal ? this.thingsModelList.findIndex((thingsModel) => thingsModel.identifier == newVal) : -1;
if (oldIndex != -1) this.thingsModelList[oldIndex][justiceSelect] = true;
if (newIndex != -1) this.thingsModelList[newIndex][justiceSelect] = false;
},
/**批量导入 */
batchImport(justiceSelect) {
this.justiceSelect = justiceSelect;
this.$refs.importBatchRef.upload.importDeviceDialog = true;
},
/**导出 */
exportModbus(justiceSelect) {
const type = justiceSelect == 'isSelectData' ? 2 : 1;
const name = justiceSelect == 'isSelectData' ? this.$t('product.product-modbus.562372-17') : this.$t('product.product-modbus.562372-38');
this.download('/modbus/config/exportModbus?type=' + type, {}, `${name}_${new Date().getTime()}.xlsx`);
},
/**获取modbus参数配置*/
getParams() {
if (!this.showParamsSection) return;
const params = { productId: this.productId };
getByProductId(params).then((response) => {
if (response.data) {
this.form = response.data;
this.sendPollType();
}
});
},
/**编辑modbus配置参数*/
setSlave() {
this.enableSetSlave = !this.enableSetSlave;
},
/** 保存modbus配置参数 */
saveSlave() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.enableSetSlave = !this.enableSetSlave;
this.form.productId = this.productId;
addOrUpdate(this.form).then((response) => {
this.$modal.msgSuccess(this.$t('product.product-modbus.562372-53'));
});
}
});
this.sendPollType();
},
//改变采集方式
changePollType() {
this.$nextTick(() => {
this.getTaskList(this.productInfo.productId);
});
setTimeout(() => {
if (this.form.pollType === 1) {
if (this.jobTotal > 0) {
this.$confirm(this.$t('product.product-modbus.562372-312'), this.$t('product.product-modbus.562372-313'), {
confirmButtonText: this.$t('product.product-modbus.562372-314'),
cancelButtonText: this.$t('product.product-modbus.562372-315'),
type: 'warning',
})
.then(() => {})
.catch(() => {
this.form.pollType = 0;
this.$message({
type: 'info',
message: this.$t('product.product-modbus.562372-316'),
});
});
}
}
}, 500);
},
/** 查询轮训任务列列表 */
getTaskList(productId) {
this.loading = true;
const params = {
pageNum: 1,
pageSize: 10,
productId: productId,
};
listProductJob(params).then((response) => {
this.jobList = response.rows;
this.jobTotal = response.total;
this.loading = false;
});
},
/**取消*/
cancelSlave() {
this.enableSetSlave = !this.enableSetSlave;
},
},
};
</script>
<style lang="scss">
.el-input-number.is-disabled .el-input__inner {
color: #111315;
}
.el-select .el-input.is-disabled .el-input__inner {
color: #0c0d0e;
}
.el-radio-button__orig-radio:disabled:checked + .el-radio-button__inner {
background-color: #409eff !important;
}
.head {
.head-title {
margin-bottom: 12px;
.title {
margin-top: 16px;
margin-bottom: 2px;
}
.tips {
color: grey;
line-height: 36px;
font-size: 14px;
}
}
.button-group {
margin-bottom: 15px;
display: flex;
.right-btns {
margin-left: auto;
}
}
}
.test_div {
width: 300px;
padding: 5px;
}
</style>

View File

@ -544,10 +544,10 @@ export default {
// 控制模块标题 // 控制模块标题
title: '设备控制 ', title: '设备控制 ',
// 未启用设备影子 // 未启用设备影子
shadowUnEnable: false, shadowUnEnable: true,
// 控制项标题背景 // 控制项标题背景(默认灰色表示离线/未激活状态)
statusColor: { statusColor: {
background: '#67C23A', background: '#909399',
color: '#fff', color: '#fff',
minWidth: '100px', minWidth: '100px',
}, },