Commit 9e2c1feae10b353cbb8bcb49a7bb5b643fe2c212
1 parent
5086e122
feat:【iot】modbus-tcp 协议接入:10% 初始化:基于 cheeky-plotting-pumpkin.md 规划
Showing
38 changed files
with
2933 additions
and
8 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java
| ... | ... | @@ -4,11 +4,13 @@ import cn.iocoder.yudao.framework.common.enums.RpcConstants; |
| 4 | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 5 | 5 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 6 | 6 | import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; |
| 7 | -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; | |
| 8 | -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; | |
| 9 | -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; | |
| 7 | +import cn.iocoder.yudao.module.iot.core.biz.dto.*; | |
| 10 | 8 | import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; |
| 9 | +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO; | |
| 10 | +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO; | |
| 11 | 11 | import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; |
| 12 | +import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusConfigService; | |
| 13 | +import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService; | |
| 12 | 14 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; |
| 13 | 15 | import cn.iocoder.yudao.module.iot.service.product.IotProductService; |
| 14 | 16 | import jakarta.annotation.Resource; |
| ... | ... | @@ -19,6 +21,9 @@ import org.springframework.web.bind.annotation.PostMapping; |
| 19 | 21 | import org.springframework.web.bind.annotation.RequestBody; |
| 20 | 22 | import org.springframework.web.bind.annotation.RestController; |
| 21 | 23 | |
| 24 | +import java.util.ArrayList; | |
| 25 | +import java.util.List; | |
| 26 | + | |
| 22 | 27 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 23 | 28 | |
| 24 | 29 | /** |
| ... | ... | @@ -35,6 +40,10 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { |
| 35 | 40 | private IotDeviceService deviceService; |
| 36 | 41 | @Resource |
| 37 | 42 | private IotProductService productService; |
| 43 | + @Resource | |
| 44 | + private IotDeviceModbusConfigService modbusConfigService; | |
| 45 | + @Resource | |
| 46 | + private IotDeviceModbusPointService modbusPointService; | |
| 38 | 47 | |
| 39 | 48 | @Override |
| 40 | 49 | @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth") |
| ... | ... | @@ -57,4 +66,47 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { |
| 57 | 66 | })); |
| 58 | 67 | } |
| 59 | 68 | |
| 69 | + @Override | |
| 70 | + @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/modbus/enabled-configs") | |
| 71 | + @PermitAll | |
| 72 | + public CommonResult<List<IotModbusDeviceConfigRespDTO>> getEnabledModbusDeviceConfigs() { | |
| 73 | + // 1. 获取所有启用的 Modbus 连接配置 | |
| 74 | + List<IotDeviceModbusConfigDO> configList = modbusConfigService.getEnabledModbusConfigList(); | |
| 75 | + if (configList.isEmpty()) { | |
| 76 | + return success(new ArrayList<>()); | |
| 77 | + } | |
| 78 | + | |
| 79 | + // 2. 组装返回结果 | |
| 80 | + List<IotModbusDeviceConfigRespDTO> result = new ArrayList<>(configList.size()); | |
| 81 | + for (IotDeviceModbusConfigDO config : configList) { | |
| 82 | + // 2.1 获取设备信息 | |
| 83 | + // TODO @AI:设备需要批量读取;(先暂时不处理) | |
| 84 | + IotDeviceDO device = deviceService.getDeviceFromCache(config.getDeviceId()); | |
| 85 | + if (device == null) { | |
| 86 | + continue; | |
| 87 | + } | |
| 88 | + | |
| 89 | + // 2.2 获取启用的点位列表 | |
| 90 | + // TODO @AI:看看是不是批量读取; | |
| 91 | + List<IotDeviceModbusPointDO> pointList = modbusPointService.getEnabledModbusPointListByDeviceId(config.getDeviceId()); | |
| 92 | + | |
| 93 | + // 2.3 构建 DTO | |
| 94 | + IotModbusDeviceConfigRespDTO dto = new IotModbusDeviceConfigRespDTO(); | |
| 95 | + dto.setDeviceId(config.getDeviceId()); | |
| 96 | + // TODO @AI:这个 productKey、deviceName 这个字段,要不要冗余到 IotDeviceModbusConfigDO 里面?(先暂时不处理) | |
| 97 | + dto.setProductKey(device.getProductKey()); | |
| 98 | + dto.setDeviceName(device.getDeviceName()); | |
| 99 | + dto.setTenantId(device.getTenantId()); | |
| 100 | + // TODO @AI:看看 dto 的转换,能不能通过 beanutils copy | |
| 101 | + dto.setIp(config.getIp()); | |
| 102 | + dto.setPort(config.getPort()); | |
| 103 | + dto.setSlaveId(config.getSlaveId()); | |
| 104 | + dto.setTimeout(config.getTimeout()); | |
| 105 | + dto.setRetryInterval(config.getRetryInterval()); | |
| 106 | + dto.setPoints(BeanUtils.toBean(pointList, IotModbusPointRespDTO.class)); | |
| 107 | + result.add(dto); | |
| 108 | + } | |
| 109 | + return success(result); | |
| 110 | + } | |
| 111 | + | |
| 60 | 112 | } |
| 61 | 113 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceModbusConfigController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.controller.admin.device; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | |
| 4 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 6 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigPageReqVO; | |
| 7 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigRespVO; | |
| 8 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO; | |
| 9 | +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO; | |
| 10 | +import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusConfigService; | |
| 11 | +import io.swagger.v3.oas.annotations.Operation; | |
| 12 | +import io.swagger.v3.oas.annotations.Parameter; | |
| 13 | +import io.swagger.v3.oas.annotations.tags.Tag; | |
| 14 | +import jakarta.annotation.Resource; | |
| 15 | +import jakarta.validation.Valid; | |
| 16 | +import org.springframework.security.access.prepost.PreAuthorize; | |
| 17 | +import org.springframework.validation.annotation.Validated; | |
| 18 | +import org.springframework.web.bind.annotation.*; | |
| 19 | + | |
| 20 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | |
| 21 | + | |
| 22 | +@Tag(name = "管理后台 - IoT 设备 Modbus 连接配置") | |
| 23 | +@RestController | |
| 24 | +@RequestMapping("/iot/device-modbus-config") | |
| 25 | +@Validated | |
| 26 | +public class IotDeviceModbusConfigController { | |
| 27 | + | |
| 28 | + @Resource | |
| 29 | + private IotDeviceModbusConfigService modbusConfigService; | |
| 30 | + | |
| 31 | + // TODO @AI:create 和 update 合并成 save 接口; | |
| 32 | + @PostMapping("/create") | |
| 33 | + @Operation(summary = "创建设备 Modbus 连接配置") | |
| 34 | + @PreAuthorize("@ss.hasPermission('iot:device-modbus-config:create')") | |
| 35 | + public CommonResult<Long> createModbusConfig(@Valid @RequestBody IotDeviceModbusConfigSaveReqVO createReqVO) { | |
| 36 | + return success(modbusConfigService.createModbusConfig(createReqVO)); | |
| 37 | + } | |
| 38 | + | |
| 39 | + @PutMapping("/update") | |
| 40 | + @Operation(summary = "更新设备 Modbus 连接配置") | |
| 41 | + @PreAuthorize("@ss.hasPermission('iot:device-modbus-config:update')") | |
| 42 | + public CommonResult<Boolean> updateModbusConfig(@Valid @RequestBody IotDeviceModbusConfigSaveReqVO updateReqVO) { | |
| 43 | + modbusConfigService.updateModbusConfig(updateReqVO); | |
| 44 | + return success(true); | |
| 45 | + } | |
| 46 | + | |
| 47 | + @DeleteMapping("/delete") | |
| 48 | + @Operation(summary = "删除设备 Modbus 连接配置") | |
| 49 | + @Parameter(name = "id", description = "编号", required = true) | |
| 50 | + @PreAuthorize("@ss.hasPermission('iot:device-modbus-config:delete')") | |
| 51 | + public CommonResult<Boolean> deleteModbusConfig(@RequestParam("id") Long id) { | |
| 52 | + modbusConfigService.deleteModbusConfig(id); | |
| 53 | + return success(true); | |
| 54 | + } | |
| 55 | + | |
| 56 | + // TODO @AI:这个接口改造,支持 id 或者 deviceId;二选一查询; | |
| 57 | + @GetMapping("/get") | |
| 58 | + @Operation(summary = "获得设备 Modbus 连接配置") | |
| 59 | + @Parameter(name = "id", description = "编号", required = true, example = "1024") | |
| 60 | + @PreAuthorize("@ss.hasPermission('iot:device-modbus-config:query')") | |
| 61 | + public CommonResult<IotDeviceModbusConfigRespVO> getModbusConfig(@RequestParam("id") Long id) { | |
| 62 | + IotDeviceModbusConfigDO modbusConfig = modbusConfigService.getModbusConfig(id); | |
| 63 | + return success(BeanUtils.toBean(modbusConfig, IotDeviceModbusConfigRespVO.class)); | |
| 64 | + } | |
| 65 | + | |
| 66 | + // TODO @AI:合并到 getModbusConfig 接口里; | |
| 67 | + @GetMapping("/get-by-device-id") | |
| 68 | + @Operation(summary = "根据设备编号获得 Modbus 连接配置") | |
| 69 | + @Parameter(name = "deviceId", description = "设备编号", required = true, example = "1024") | |
| 70 | + @PreAuthorize("@ss.hasPermission('iot:device-modbus-config:query')") | |
| 71 | + public CommonResult<IotDeviceModbusConfigRespVO> getModbusConfigByDeviceId(@RequestParam("deviceId") Long deviceId) { | |
| 72 | + IotDeviceModbusConfigDO modbusConfig = modbusConfigService.getModbusConfigByDeviceId(deviceId); | |
| 73 | + return success(BeanUtils.toBean(modbusConfig, IotDeviceModbusConfigRespVO.class)); | |
| 74 | + } | |
| 75 | + | |
| 76 | + @GetMapping("/page") | |
| 77 | + @Operation(summary = "获得设备 Modbus 连接配置分页") | |
| 78 | + @PreAuthorize("@ss.hasPermission('iot:device-modbus-config:query')") | |
| 79 | + public CommonResult<PageResult<IotDeviceModbusConfigRespVO>> getModbusConfigPage(@Valid IotDeviceModbusConfigPageReqVO pageReqVO) { | |
| 80 | + PageResult<IotDeviceModbusConfigDO> pageResult = modbusConfigService.getModbusConfigPage(pageReqVO); | |
| 81 | + return success(BeanUtils.toBean(pageResult, IotDeviceModbusConfigRespVO.class)); | |
| 82 | + } | |
| 83 | + | |
| 84 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceModbusPointController.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.controller.admin.device; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | |
| 4 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 6 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO; | |
| 7 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointRespVO; | |
| 8 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointSaveReqVO; | |
| 9 | +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO; | |
| 10 | +import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService; | |
| 11 | +import io.swagger.v3.oas.annotations.Operation; | |
| 12 | +import io.swagger.v3.oas.annotations.Parameter; | |
| 13 | +import io.swagger.v3.oas.annotations.tags.Tag; | |
| 14 | +import jakarta.annotation.Resource; | |
| 15 | +import jakarta.validation.Valid; | |
| 16 | +import org.springframework.security.access.prepost.PreAuthorize; | |
| 17 | +import org.springframework.validation.annotation.Validated; | |
| 18 | +import org.springframework.web.bind.annotation.*; | |
| 19 | + | |
| 20 | +import java.util.List; | |
| 21 | + | |
| 22 | +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | |
| 23 | + | |
| 24 | +@Tag(name = "管理后台 - IoT 设备 Modbus 点位配置") | |
| 25 | +@RestController | |
| 26 | +@RequestMapping("/iot/device-modbus-point") | |
| 27 | +@Validated | |
| 28 | +public class IotDeviceModbusPointController { | |
| 29 | + | |
| 30 | + @Resource | |
| 31 | + private IotDeviceModbusPointService modbusPointService; | |
| 32 | + | |
| 33 | + @PostMapping("/create") | |
| 34 | + @Operation(summary = "创建设备 Modbus 点位配置") | |
| 35 | + @PreAuthorize("@ss.hasPermission('iot:device-modbus-point:create')") | |
| 36 | + public CommonResult<Long> createModbusPoint(@Valid @RequestBody IotDeviceModbusPointSaveReqVO createReqVO) { | |
| 37 | + return success(modbusPointService.createModbusPoint(createReqVO)); | |
| 38 | + } | |
| 39 | + | |
| 40 | + @PutMapping("/update") | |
| 41 | + @Operation(summary = "更新设备 Modbus 点位配置") | |
| 42 | + @PreAuthorize("@ss.hasPermission('iot:device-modbus-point:update')") | |
| 43 | + public CommonResult<Boolean> updateModbusPoint(@Valid @RequestBody IotDeviceModbusPointSaveReqVO updateReqVO) { | |
| 44 | + modbusPointService.updateModbusPoint(updateReqVO); | |
| 45 | + return success(true); | |
| 46 | + } | |
| 47 | + | |
| 48 | + @DeleteMapping("/delete") | |
| 49 | + @Operation(summary = "删除设备 Modbus 点位配置") | |
| 50 | + @Parameter(name = "id", description = "编号", required = true) | |
| 51 | + @PreAuthorize("@ss.hasPermission('iot:device-modbus-point:delete')") | |
| 52 | + public CommonResult<Boolean> deleteModbusPoint(@RequestParam("id") Long id) { | |
| 53 | + modbusPointService.deleteModbusPoint(id); | |
| 54 | + return success(true); | |
| 55 | + } | |
| 56 | + | |
| 57 | + @GetMapping("/get") | |
| 58 | + @Operation(summary = "获得设备 Modbus 点位配置") | |
| 59 | + @Parameter(name = "id", description = "编号", required = true, example = "1024") | |
| 60 | + @PreAuthorize("@ss.hasPermission('iot:device-modbus-point:query')") | |
| 61 | + public CommonResult<IotDeviceModbusPointRespVO> getModbusPoint(@RequestParam("id") Long id) { | |
| 62 | + IotDeviceModbusPointDO modbusPoint = modbusPointService.getModbusPoint(id); | |
| 63 | + return success(BeanUtils.toBean(modbusPoint, IotDeviceModbusPointRespVO.class)); | |
| 64 | + } | |
| 65 | + | |
| 66 | + @GetMapping("/page") | |
| 67 | + @Operation(summary = "获得设备 Modbus 点位配置分页") | |
| 68 | + @PreAuthorize("@ss.hasPermission('iot:device-modbus-point:query')") | |
| 69 | + public CommonResult<PageResult<IotDeviceModbusPointRespVO>> getModbusPointPage(@Valid IotDeviceModbusPointPageReqVO pageReqVO) { | |
| 70 | + PageResult<IotDeviceModbusPointDO> pageResult = modbusPointService.getModbusPointPage(pageReqVO); | |
| 71 | + return success(BeanUtils.toBean(pageResult, IotDeviceModbusPointRespVO.class)); | |
| 72 | + } | |
| 73 | + | |
| 74 | + // TODO @AI:应该用不上这个接口?只需要 getModbusPointPage 分页 | |
| 75 | + @GetMapping("/list-by-device-id") | |
| 76 | + @Operation(summary = "根据设备编号获得 Modbus 点位配置列表") | |
| 77 | + @Parameter(name = "deviceId", description = "设备编号", required = true, example = "1024") | |
| 78 | + @PreAuthorize("@ss.hasPermission('iot:device-modbus-point:query')") | |
| 79 | + public CommonResult<List<IotDeviceModbusPointRespVO>> getModbusPointListByDeviceId(@RequestParam("deviceId") Long deviceId) { | |
| 80 | + List<IotDeviceModbusPointDO> list = modbusPointService.getModbusPointListByDeviceId(deviceId); | |
| 81 | + return success(BeanUtils.toBean(list, IotDeviceModbusPointRespVO.class)); | |
| 82 | + } | |
| 83 | + | |
| 84 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusConfigPageReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | |
| 4 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 5 | +import lombok.Data; | |
| 6 | +import lombok.EqualsAndHashCode; | |
| 7 | +import lombok.ToString; | |
| 8 | + | |
| 9 | +// TODO @AI:不需要分页接口; | |
| 10 | +@Schema(description = "管理后台 - IoT 设备 Modbus 连接配置分页 Request VO") | |
| 11 | +@Data | |
| 12 | +@EqualsAndHashCode(callSuper = true) | |
| 13 | +@ToString(callSuper = true) | |
| 14 | +public class IotDeviceModbusConfigPageReqVO extends PageParam { | |
| 15 | + | |
| 16 | + @Schema(description = "设备编号", example = "1024") | |
| 17 | + private Long deviceId; | |
| 18 | + | |
| 19 | + @Schema(description = "Modbus 服务器 IP 地址", example = "192.168.1.100") | |
| 20 | + private String ip; | |
| 21 | + | |
| 22 | + @Schema(description = "状态", example = "0") | |
| 23 | + private Integer status; | |
| 24 | + | |
| 25 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusConfigRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.time.LocalDateTime; | |
| 7 | + | |
| 8 | +@Schema(description = "管理后台 - IoT 设备 Modbus 连接配置 Response VO") | |
| 9 | +@Data | |
| 10 | +public class IotDeviceModbusConfigRespVO { | |
| 11 | + | |
| 12 | + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 13 | + private Long id; | |
| 14 | + | |
| 15 | + @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 16 | + private Long deviceId; | |
| 17 | + | |
| 18 | + @Schema(description = "设备名称", example = "温湿度传感器") | |
| 19 | + private String deviceName; | |
| 20 | + | |
| 21 | + @Schema(description = "Modbus 服务器 IP 地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "192.168.1.100") | |
| 22 | + private String ip; | |
| 23 | + | |
| 24 | + @Schema(description = "Modbus 端口", requiredMode = Schema.RequiredMode.REQUIRED, example = "502") | |
| 25 | + private Integer port; | |
| 26 | + | |
| 27 | + @Schema(description = "从站地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 28 | + private Integer slaveId; | |
| 29 | + | |
| 30 | + @Schema(description = "连接超时时间(毫秒)", example = "3000") | |
| 31 | + private Integer timeout; | |
| 32 | + | |
| 33 | + @Schema(description = "重试间隔(毫秒)", example = "1000") | |
| 34 | + private Integer retryInterval; | |
| 35 | + | |
| 36 | + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | |
| 37 | + private Integer status; | |
| 38 | + | |
| 39 | + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 40 | + private LocalDateTime createTime; | |
| 41 | + | |
| 42 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusConfigSaveReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import jakarta.validation.constraints.NotEmpty; | |
| 5 | +import jakarta.validation.constraints.NotNull; | |
| 6 | +import lombok.Data; | |
| 7 | + | |
| 8 | +@Schema(description = "管理后台 - IoT 设备 Modbus 连接配置新增/修改 Request VO") | |
| 9 | +@Data | |
| 10 | +public class IotDeviceModbusConfigSaveReqVO { | |
| 11 | + | |
| 12 | + @Schema(description = "主键", example = "1") | |
| 13 | + private Long id; | |
| 14 | + | |
| 15 | + @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 16 | + @NotNull(message = "设备编号不能为空") | |
| 17 | + private Long deviceId; | |
| 18 | + | |
| 19 | + @Schema(description = "Modbus 服务器 IP 地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "192.168.1.100") | |
| 20 | + @NotEmpty(message = "Modbus 服务器 IP 地址不能为空") | |
| 21 | + private String ip; | |
| 22 | + | |
| 23 | + @Schema(description = "Modbus 端口", requiredMode = Schema.RequiredMode.REQUIRED, example = "502") | |
| 24 | + @NotNull(message = "Modbus 端口不能为空") | |
| 25 | + private Integer port; | |
| 26 | + | |
| 27 | + @Schema(description = "从站地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 28 | + @NotNull(message = "从站地址不能为空") | |
| 29 | + private Integer slaveId; | |
| 30 | + | |
| 31 | + @Schema(description = "连接超时时间(毫秒)", example = "3000") | |
| 32 | + private Integer timeout; | |
| 33 | + | |
| 34 | + @Schema(description = "重试间隔(毫秒)", example = "1000") | |
| 35 | + private Integer retryInterval; | |
| 36 | + | |
| 37 | + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | |
| 38 | + @NotNull(message = "状态不能为空") | |
| 39 | + private Integer status; | |
| 40 | + | |
| 41 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusPointPageReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageParam; | |
| 4 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 5 | +import lombok.Data; | |
| 6 | +import lombok.EqualsAndHashCode; | |
| 7 | +import lombok.ToString; | |
| 8 | + | |
| 9 | +@Schema(description = "管理后台 - IoT 设备 Modbus 点位配置分页 Request VO") | |
| 10 | +@Data | |
| 11 | +@EqualsAndHashCode(callSuper = true) | |
| 12 | +@ToString(callSuper = true) | |
| 13 | +public class IotDeviceModbusPointPageReqVO extends PageParam { | |
| 14 | + | |
| 15 | + @Schema(description = "设备编号", example = "1024") | |
| 16 | + private Long deviceId; | |
| 17 | + | |
| 18 | + @Schema(description = "属性标识符", example = "temperature") | |
| 19 | + private String identifier; | |
| 20 | + | |
| 21 | + @Schema(description = "属性名称", example = "温度") | |
| 22 | + private String name; | |
| 23 | + | |
| 24 | + @Schema(description = "Modbus 功能码", example = "3") | |
| 25 | + private Integer functionCode; | |
| 26 | + | |
| 27 | + @Schema(description = "状态", example = "0") | |
| 28 | + private Integer status; | |
| 29 | + | |
| 30 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusPointRespVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import lombok.Data; | |
| 5 | + | |
| 6 | +import java.math.BigDecimal; | |
| 7 | +import java.time.LocalDateTime; | |
| 8 | + | |
| 9 | +@Schema(description = "管理后台 - IoT 设备 Modbus 点位配置 Response VO") | |
| 10 | +@Data | |
| 11 | +public class IotDeviceModbusPointRespVO { | |
| 12 | + | |
| 13 | + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 14 | + private Long id; | |
| 15 | + | |
| 16 | + @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 17 | + private Long deviceId; | |
| 18 | + | |
| 19 | + @Schema(description = "物模型属性编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") | |
| 20 | + private Long thingModelId; | |
| 21 | + | |
| 22 | + @Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature") | |
| 23 | + private String identifier; | |
| 24 | + | |
| 25 | + @Schema(description = "属性名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "温度") | |
| 26 | + private String name; | |
| 27 | + | |
| 28 | + @Schema(description = "Modbus 功能码", requiredMode = Schema.RequiredMode.REQUIRED, example = "3") | |
| 29 | + private Integer functionCode; | |
| 30 | + | |
| 31 | + @Schema(description = "寄存器起始地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | |
| 32 | + private Integer registerAddress; | |
| 33 | + | |
| 34 | + @Schema(description = "寄存器数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 35 | + private Integer registerCount; | |
| 36 | + | |
| 37 | + @Schema(description = "字节序", requiredMode = Schema.RequiredMode.REQUIRED, example = "AB") | |
| 38 | + private String byteOrder; | |
| 39 | + | |
| 40 | + @Schema(description = "原始数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "INT16") | |
| 41 | + private String rawDataType; | |
| 42 | + | |
| 43 | + @Schema(description = "缩放因子", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0") | |
| 44 | + private BigDecimal scale; | |
| 45 | + | |
| 46 | + @Schema(description = "轮询间隔(毫秒)", requiredMode = Schema.RequiredMode.REQUIRED, example = "5000") | |
| 47 | + private Integer pollInterval; | |
| 48 | + | |
| 49 | + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | |
| 50 | + private Integer status; | |
| 51 | + | |
| 52 | + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) | |
| 53 | + private LocalDateTime createTime; | |
| 54 | + | |
| 55 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusPointSaveReqVO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus; | |
| 2 | + | |
| 3 | +import io.swagger.v3.oas.annotations.media.Schema; | |
| 4 | +import jakarta.validation.constraints.NotEmpty; | |
| 5 | +import jakarta.validation.constraints.NotNull; | |
| 6 | +import lombok.Data; | |
| 7 | + | |
| 8 | +import java.math.BigDecimal; | |
| 9 | + | |
| 10 | +@Schema(description = "管理后台 - IoT 设备 Modbus 点位配置新增/修改 Request VO") | |
| 11 | +@Data | |
| 12 | +public class IotDeviceModbusPointSaveReqVO { | |
| 13 | + | |
| 14 | + @Schema(description = "主键", example = "1") | |
| 15 | + private Long id; | |
| 16 | + | |
| 17 | + @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") | |
| 18 | + @NotNull(message = "设备编号不能为空") | |
| 19 | + private Long deviceId; | |
| 20 | + | |
| 21 | + @Schema(description = "物模型属性编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") | |
| 22 | + @NotNull(message = "物模型属性编号不能为空") | |
| 23 | + private Long thingModelId; | |
| 24 | + | |
| 25 | + @Schema(description = "Modbus 功能码", requiredMode = Schema.RequiredMode.REQUIRED, example = "3") | |
| 26 | + @NotNull(message = "Modbus 功能码不能为空") | |
| 27 | + private Integer functionCode; | |
| 28 | + | |
| 29 | + @Schema(description = "寄存器起始地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | |
| 30 | + @NotNull(message = "寄存器起始地址不能为空") | |
| 31 | + private Integer registerAddress; | |
| 32 | + | |
| 33 | + @Schema(description = "寄存器数量", example = "1") | |
| 34 | + private Integer registerCount; | |
| 35 | + | |
| 36 | + @Schema(description = "字节序", requiredMode = Schema.RequiredMode.REQUIRED, example = "AB") | |
| 37 | + @NotEmpty(message = "字节序不能为空") | |
| 38 | + private String byteOrder; | |
| 39 | + | |
| 40 | + @Schema(description = "原始数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "INT16") | |
| 41 | + @NotEmpty(message = "原始数据类型不能为空") | |
| 42 | + private String rawDataType; | |
| 43 | + | |
| 44 | + @Schema(description = "缩放因子", example = "1.0") | |
| 45 | + private BigDecimal scale; | |
| 46 | + | |
| 47 | + @Schema(description = "轮询间隔(毫秒)", example = "5000") | |
| 48 | + private Integer pollInterval; | |
| 49 | + | |
| 50 | + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | |
| 51 | + @NotNull(message = "状态不能为空") | |
| 52 | + private Integer status; | |
| 53 | + | |
| 54 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusConfigDO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.dal.dataobject.device; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; | |
| 4 | +import com.baomidou.mybatisplus.annotation.KeySequence; | |
| 5 | +import com.baomidou.mybatisplus.annotation.TableId; | |
| 6 | +import com.baomidou.mybatisplus.annotation.TableName; | |
| 7 | +import lombok.*; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * IoT 设备 Modbus 连接配置 DO | |
| 11 | + * | |
| 12 | + * @author 芋道源码 | |
| 13 | + */ | |
| 14 | +@TableName("iot_device_modbus_config") | |
| 15 | +@KeySequence("iot_device_modbus_config_seq") | |
| 16 | +@Data | |
| 17 | +@Builder | |
| 18 | +@NoArgsConstructor | |
| 19 | +@AllArgsConstructor | |
| 20 | +public class IotDeviceModbusConfigDO extends TenantBaseDO { | |
| 21 | + | |
| 22 | + /** | |
| 23 | + * 主键 | |
| 24 | + */ | |
| 25 | + @TableId | |
| 26 | + private Long id; | |
| 27 | + // TODO @AI:增加 productId; | |
| 28 | + /** | |
| 29 | + * 设备编号 | |
| 30 | + * | |
| 31 | + * 关联 {@link IotDeviceDO#getId()} | |
| 32 | + */ | |
| 33 | + private Long deviceId; | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * Modbus 服务器 IP 地址 | |
| 37 | + */ | |
| 38 | + private String ip; | |
| 39 | + /** | |
| 40 | + * Modbus 服务器端口 | |
| 41 | + */ | |
| 42 | + private Integer port; | |
| 43 | + /** | |
| 44 | + * 从站地址 | |
| 45 | + */ | |
| 46 | + private Integer slaveId; | |
| 47 | + /** | |
| 48 | + * 连接超时时间,单位:毫秒 | |
| 49 | + */ | |
| 50 | + private Integer timeout; | |
| 51 | + /** | |
| 52 | + * 重试间隔,单位:毫秒 | |
| 53 | + */ | |
| 54 | + private Integer retryInterval; | |
| 55 | + /** | |
| 56 | + * 状态 | |
| 57 | + * | |
| 58 | + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum} | |
| 59 | + */ | |
| 60 | + private Integer status; | |
| 61 | + | |
| 62 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusPointDO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.dal.dataobject.device; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; | |
| 4 | +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; | |
| 5 | +import cn.iocoder.yudao.module.iot.enums.device.IotModbusByteOrderEnum; | |
| 6 | +import cn.iocoder.yudao.module.iot.enums.device.IotModbusFunctionCodeEnum; | |
| 7 | +import cn.iocoder.yudao.module.iot.enums.device.IotModbusRawDataTypeEnum; | |
| 8 | +import com.baomidou.mybatisplus.annotation.KeySequence; | |
| 9 | +import com.baomidou.mybatisplus.annotation.TableId; | |
| 10 | +import com.baomidou.mybatisplus.annotation.TableName; | |
| 11 | +import lombok.*; | |
| 12 | + | |
| 13 | +import java.math.BigDecimal; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * IoT 设备 Modbus 点位配置 DO | |
| 17 | + * | |
| 18 | + * @author 芋道源码 | |
| 19 | + */ | |
| 20 | +@TableName("iot_device_modbus_point") | |
| 21 | +@KeySequence("iot_device_modbus_point_seq") | |
| 22 | +@Data | |
| 23 | +@Builder | |
| 24 | +@NoArgsConstructor | |
| 25 | +@AllArgsConstructor | |
| 26 | +public class IotDeviceModbusPointDO extends TenantBaseDO { | |
| 27 | + | |
| 28 | + /** | |
| 29 | + * 主键 | |
| 30 | + */ | |
| 31 | + @TableId | |
| 32 | + private Long id; | |
| 33 | + // TODO @AI:增加 productId; | |
| 34 | + /** | |
| 35 | + * 设备编号 | |
| 36 | + * | |
| 37 | + * 关联 {@link IotDeviceDO#getId()} | |
| 38 | + */ | |
| 39 | + private Long deviceId; | |
| 40 | + /** | |
| 41 | + * 物模型属性编号 | |
| 42 | + * | |
| 43 | + * 关联 {@link IotThingModelDO#getId()} | |
| 44 | + */ | |
| 45 | + private Long thingModelId; | |
| 46 | + // TODO @AI:每次物模型的变更时,需要按需刷下 identifier、name 配置; | |
| 47 | + /** | |
| 48 | + * 属性标识符 | |
| 49 | + * | |
| 50 | + * 冗余 {@link IotThingModelDO#getIdentifier()} | |
| 51 | + */ | |
| 52 | + private String identifier; | |
| 53 | + /** | |
| 54 | + * 属性名称 | |
| 55 | + * | |
| 56 | + * 冗余 {@link IotThingModelDO#getName()} | |
| 57 | + */ | |
| 58 | + private String name; | |
| 59 | + | |
| 60 | + // ========== Modbus 协议配置 ========== | |
| 61 | + | |
| 62 | + /** | |
| 63 | + * Modbus 功能码 | |
| 64 | + * | |
| 65 | + * 枚举 {@link IotModbusFunctionCodeEnum} | |
| 66 | + */ | |
| 67 | + private Integer functionCode; | |
| 68 | + /** | |
| 69 | + * 寄存器起始地址 | |
| 70 | + */ | |
| 71 | + private Integer registerAddress; | |
| 72 | + /** | |
| 73 | + * 寄存器数量 | |
| 74 | + */ | |
| 75 | + private Integer registerCount; | |
| 76 | + /** | |
| 77 | + * 字节序 | |
| 78 | + * | |
| 79 | + * 枚举 {@link IotModbusByteOrderEnum} | |
| 80 | + */ | |
| 81 | + private String byteOrder; | |
| 82 | + /** | |
| 83 | + * 原始数据类型 | |
| 84 | + * | |
| 85 | + * 枚举 {@link IotModbusRawDataTypeEnum} | |
| 86 | + */ | |
| 87 | + private String rawDataType; | |
| 88 | + /** | |
| 89 | + * 缩放因子 | |
| 90 | + */ | |
| 91 | + private BigDecimal scale; | |
| 92 | + /** | |
| 93 | + * 轮询间隔(毫秒) | |
| 94 | + */ | |
| 95 | + private Integer pollInterval; | |
| 96 | + /** | |
| 97 | + * 状态 | |
| 98 | + * | |
| 99 | + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum} | |
| 100 | + */ | |
| 101 | + private Integer status; | |
| 102 | + | |
| 103 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceModbusConfigMapper.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.dal.mysql.device; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 4 | +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | |
| 5 | +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | |
| 6 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigPageReqVO; | |
| 7 | +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO; | |
| 8 | +import org.apache.ibatis.annotations.Mapper; | |
| 9 | + | |
| 10 | +import java.util.List; | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * IoT 设备 Modbus 连接配置 Mapper | |
| 14 | + * | |
| 15 | + * @author 芋道源码 | |
| 16 | + */ | |
| 17 | +@Mapper | |
| 18 | +public interface IotDeviceModbusConfigMapper extends BaseMapperX<IotDeviceModbusConfigDO> { | |
| 19 | + | |
| 20 | + default PageResult<IotDeviceModbusConfigDO> selectPage(IotDeviceModbusConfigPageReqVO reqVO) { | |
| 21 | + return selectPage(reqVO, new LambdaQueryWrapperX<IotDeviceModbusConfigDO>() | |
| 22 | + .eqIfPresent(IotDeviceModbusConfigDO::getDeviceId, reqVO.getDeviceId()) | |
| 23 | + .likeIfPresent(IotDeviceModbusConfigDO::getIp, reqVO.getIp()) | |
| 24 | + .eqIfPresent(IotDeviceModbusConfigDO::getStatus, reqVO.getStatus()) | |
| 25 | + .orderByDesc(IotDeviceModbusConfigDO::getId)); | |
| 26 | + } | |
| 27 | + | |
| 28 | + default IotDeviceModbusConfigDO selectByDeviceId(Long deviceId) { | |
| 29 | + return selectOne(IotDeviceModbusConfigDO::getDeviceId, deviceId); | |
| 30 | + } | |
| 31 | + | |
| 32 | + default List<IotDeviceModbusConfigDO> selectListByStatus(Integer status) { | |
| 33 | + return selectList(IotDeviceModbusConfigDO::getStatus, status); | |
| 34 | + } | |
| 35 | + | |
| 36 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceModbusPointMapper.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.dal.mysql.device; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 4 | +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | |
| 5 | +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | |
| 6 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO; | |
| 7 | +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO; | |
| 8 | +import org.apache.ibatis.annotations.Mapper; | |
| 9 | + | |
| 10 | +import java.util.List; | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * IoT 设备 Modbus 点位配置 Mapper | |
| 14 | + * | |
| 15 | + * @author 芋道源码 | |
| 16 | + */ | |
| 17 | +@Mapper | |
| 18 | +public interface IotDeviceModbusPointMapper extends BaseMapperX<IotDeviceModbusPointDO> { | |
| 19 | + | |
| 20 | + default PageResult<IotDeviceModbusPointDO> selectPage(IotDeviceModbusPointPageReqVO reqVO) { | |
| 21 | + return selectPage(reqVO, new LambdaQueryWrapperX<IotDeviceModbusPointDO>() | |
| 22 | + .eqIfPresent(IotDeviceModbusPointDO::getDeviceId, reqVO.getDeviceId()) | |
| 23 | + .likeIfPresent(IotDeviceModbusPointDO::getIdentifier, reqVO.getIdentifier()) | |
| 24 | + .likeIfPresent(IotDeviceModbusPointDO::getName, reqVO.getName()) | |
| 25 | + .eqIfPresent(IotDeviceModbusPointDO::getFunctionCode, reqVO.getFunctionCode()) | |
| 26 | + .eqIfPresent(IotDeviceModbusPointDO::getStatus, reqVO.getStatus()) | |
| 27 | + .orderByDesc(IotDeviceModbusPointDO::getId)); | |
| 28 | + } | |
| 29 | + | |
| 30 | + default List<IotDeviceModbusPointDO> selectListByDeviceId(Long deviceId) { | |
| 31 | + return selectList(IotDeviceModbusPointDO::getDeviceId, deviceId); | |
| 32 | + } | |
| 33 | + | |
| 34 | + // TODO @AI:是不是 selectList(f1, v1, f2, v2); | |
| 35 | + default List<IotDeviceModbusPointDO> selectListByDeviceIdAndStatus(Long deviceId, Integer status) { | |
| 36 | + return selectList(new LambdaQueryWrapperX<IotDeviceModbusPointDO>() | |
| 37 | + .eq(IotDeviceModbusPointDO::getDeviceId, deviceId) | |
| 38 | + .eq(IotDeviceModbusPointDO::getStatus, status)); | |
| 39 | + } | |
| 40 | + | |
| 41 | + // TODO @AI:是不是 selectOne(f1, v1, f2, v2); | |
| 42 | + default IotDeviceModbusPointDO selectByDeviceIdAndIdentifier(Long deviceId, String identifier) { | |
| 43 | + return selectOne(new LambdaQueryWrapperX<IotDeviceModbusPointDO>() | |
| 44 | + .eq(IotDeviceModbusPointDO::getDeviceId, deviceId) | |
| 45 | + .eq(IotDeviceModbusPointDO::getIdentifier, identifier)); | |
| 46 | + } | |
| 47 | + | |
| 48 | + // TODO @AI:是不是删除这个方法; | |
| 49 | + default void deleteByDeviceId(Long deviceId) { | |
| 50 | + delete(IotDeviceModbusPointDO::getDeviceId, deviceId); | |
| 51 | + } | |
| 52 | + | |
| 53 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
| ... | ... | @@ -41,6 +41,14 @@ public interface ErrorCodeConstants { |
| 41 | 41 | ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在"); |
| 42 | 42 | ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除"); |
| 43 | 43 | |
| 44 | + // ========== 设备 Modbus 配置 1-050-006-000 ========== | |
| 45 | + ErrorCode DEVICE_MODBUS_CONFIG_NOT_EXISTS = new ErrorCode(1_050_006_000, "设备 Modbus 连接配置不存在"); | |
| 46 | + ErrorCode DEVICE_MODBUS_CONFIG_EXISTS = new ErrorCode(1_050_006_001, "设备 Modbus 连接配置已存在"); | |
| 47 | + | |
| 48 | + // ========== 设备 Modbus 点位 1-050-007-000 ========== | |
| 49 | + ErrorCode DEVICE_MODBUS_POINT_NOT_EXISTS = new ErrorCode(1_050_007_000, "设备 Modbus 点位配置不存在"); | |
| 50 | + ErrorCode DEVICE_MODBUS_POINT_EXISTS = new ErrorCode(1_050_007_001, "设备 Modbus 点位配置已存在"); | |
| 51 | + | |
| 44 | 52 | // ========== OTA 固件相关 1-050-008-000 ========== |
| 45 | 53 | |
| 46 | 54 | ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在"); | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotModbusByteOrderEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.enums.device; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.core.ArrayValuable; | |
| 4 | +import lombok.Getter; | |
| 5 | +import lombok.RequiredArgsConstructor; | |
| 6 | + | |
| 7 | +import java.util.Arrays; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * IoT Modbus 字节序枚举 | |
| 11 | + * | |
| 12 | + * @author 芋道源码 | |
| 13 | + */ | |
| 14 | +@Getter | |
| 15 | +@RequiredArgsConstructor | |
| 16 | +public enum IotModbusByteOrderEnum implements ArrayValuable<String> { | |
| 17 | + | |
| 18 | + AB("AB", "大端序(16位)", 2), | |
| 19 | + BA("BA", "小端序(16位)", 2), | |
| 20 | + ABCD("ABCD", "大端序(32位)", 4), | |
| 21 | + CDAB("CDAB", "大端字交换(32位)", 4), | |
| 22 | + DCBA("DCBA", "小端序(32位)", 4), | |
| 23 | + BADC("BADC", "小端字交换(32位)", 4); | |
| 24 | + | |
| 25 | + public static final String[] ARRAYS = Arrays.stream(values()) | |
| 26 | + .map(IotModbusByteOrderEnum::getOrder) | |
| 27 | + .toArray(String[]::new); | |
| 28 | + | |
| 29 | + /** | |
| 30 | + * 字节序 | |
| 31 | + */ | |
| 32 | + private final String order; | |
| 33 | + /** | |
| 34 | + * 名称 | |
| 35 | + */ | |
| 36 | + private final String name; | |
| 37 | + /** | |
| 38 | + * 字节数 | |
| 39 | + */ | |
| 40 | + private final Integer byteCount; | |
| 41 | + | |
| 42 | + @Override | |
| 43 | + public String[] array() { | |
| 44 | + return ARRAYS; | |
| 45 | + } | |
| 46 | + | |
| 47 | + // TODO @AI:如果不需要,可以删除掉这个方法; | |
| 48 | + /** | |
| 49 | + * 根据字节序获取枚举 | |
| 50 | + * | |
| 51 | + * @param order 字节序 | |
| 52 | + * @return 枚举 | |
| 53 | + */ | |
| 54 | + public static IotModbusByteOrderEnum getByOrder(String order) { | |
| 55 | + return Arrays.stream(values()) | |
| 56 | + .filter(e -> e.getOrder().equals(order)) | |
| 57 | + .findFirst() | |
| 58 | + .orElse(null); | |
| 59 | + } | |
| 60 | + | |
| 61 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotModbusFunctionCodeEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.enums.device; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.core.ArrayValuable; | |
| 4 | +import lombok.Getter; | |
| 5 | +import lombok.RequiredArgsConstructor; | |
| 6 | + | |
| 7 | +import java.util.Arrays; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * IoT Modbus 功能码枚举 | |
| 11 | + * | |
| 12 | + * @author 芋道源码 | |
| 13 | + */ | |
| 14 | +@Getter | |
| 15 | +@RequiredArgsConstructor | |
| 16 | +public enum IotModbusFunctionCodeEnum implements ArrayValuable<Integer> { | |
| 17 | + | |
| 18 | + READ_COILS(1, "读线圈", true, 5, 15), | |
| 19 | + READ_DISCRETE_INPUTS(2, "读离散输入", false, null, null), | |
| 20 | + READ_HOLDING_REGISTERS(3, "读保持寄存器", true, 6, 16), | |
| 21 | + READ_INPUT_REGISTERS(4, "读输入寄存器", false, null, null); | |
| 22 | + | |
| 23 | + public static final Integer[] ARRAYS = Arrays.stream(values()) | |
| 24 | + .map(IotModbusFunctionCodeEnum::getCode) | |
| 25 | + .toArray(Integer[]::new); | |
| 26 | + | |
| 27 | + /** | |
| 28 | + * 功能码 | |
| 29 | + */ | |
| 30 | + private final Integer code; | |
| 31 | + /** | |
| 32 | + * 名称 | |
| 33 | + */ | |
| 34 | + private final String name; | |
| 35 | + /** | |
| 36 | + * 是否支持写操作 | |
| 37 | + */ | |
| 38 | + private final Boolean writable; | |
| 39 | + /** | |
| 40 | + * 单个写功能码 | |
| 41 | + */ | |
| 42 | + private final Integer writeSingleCode; | |
| 43 | + /** | |
| 44 | + * 多个写功能码 | |
| 45 | + */ | |
| 46 | + private final Integer writeMultipleCode; | |
| 47 | + | |
| 48 | + @Override | |
| 49 | + public Integer[] array() { | |
| 50 | + return ARRAYS; | |
| 51 | + } | |
| 52 | + | |
| 53 | + /** | |
| 54 | + * 根据功能码获取枚举 | |
| 55 | + * | |
| 56 | + * @param code 功能码 | |
| 57 | + * @return 枚举 | |
| 58 | + */ | |
| 59 | + public static IotModbusFunctionCodeEnum valueOf(Integer code) { | |
| 60 | + return Arrays.stream(values()) | |
| 61 | + .filter(e -> e.getCode().equals(code)) | |
| 62 | + .findFirst() | |
| 63 | + .orElse(null); | |
| 64 | + } | |
| 65 | + | |
| 66 | + // TODO @AI:如果用不到,可以暂时删除哈; | |
| 67 | + /** | |
| 68 | + * 获取写功能码 | |
| 69 | + * | |
| 70 | + * @param registerCount 寄存器数量 | |
| 71 | + * @return 写功能码 | |
| 72 | + */ | |
| 73 | + public Integer getWriteCode(int registerCount) { | |
| 74 | + if (!writable) { | |
| 75 | + return null; | |
| 76 | + } | |
| 77 | + return registerCount == 1 ? writeSingleCode : writeMultipleCode; | |
| 78 | + } | |
| 79 | + | |
| 80 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotModbusRawDataTypeEnum.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.enums.device; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.core.ArrayValuable; | |
| 4 | +import lombok.Getter; | |
| 5 | +import lombok.RequiredArgsConstructor; | |
| 6 | + | |
| 7 | +import java.util.Arrays; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * IoT Modbus 原始数据类型枚举 | |
| 11 | + * | |
| 12 | + * @author 芋道源码 | |
| 13 | + */ | |
| 14 | +@Getter | |
| 15 | +@RequiredArgsConstructor | |
| 16 | +public enum IotModbusRawDataTypeEnum implements ArrayValuable<String> { | |
| 17 | + | |
| 18 | + INT16("INT16", "有符号 16 位整数", 1), | |
| 19 | + UINT16("UINT16", "无符号 16 位整数", 1), | |
| 20 | + INT32("INT32", "有符号 32 位整数", 2), | |
| 21 | + UINT32("UINT32", "无符号 32 位整数", 2), | |
| 22 | + FLOAT("FLOAT", "32 位浮点数", 2), | |
| 23 | + DOUBLE("DOUBLE", "64 位浮点数", 4), | |
| 24 | + BOOLEAN("BOOLEAN", "布尔值(用于线圈)", 1), | |
| 25 | + STRING("STRING", "字符串", -1); // -1 表示可变长度 | |
| 26 | + | |
| 27 | + public static final String[] ARRAYS = Arrays.stream(values()) | |
| 28 | + .map(IotModbusRawDataTypeEnum::getType) | |
| 29 | + .toArray(String[]::new); | |
| 30 | + | |
| 31 | + /** | |
| 32 | + * 数据类型 | |
| 33 | + */ | |
| 34 | + private final String type; | |
| 35 | + /** | |
| 36 | + * 名称 | |
| 37 | + */ | |
| 38 | + private final String name; | |
| 39 | + // TODO @AI:去掉 default 会好点。null 表示可变; | |
| 40 | + /** | |
| 41 | + * 默认寄存器数量(-1 表示可变) | |
| 42 | + */ | |
| 43 | + private final Integer defaultRegisterCount; | |
| 44 | + | |
| 45 | + @Override | |
| 46 | + public String[] array() { | |
| 47 | + return ARRAYS; | |
| 48 | + } | |
| 49 | + | |
| 50 | + // TODO @AI:如果不用,可以删除掉 | |
| 51 | + /** | |
| 52 | + * 根据类型获取枚举 | |
| 53 | + * | |
| 54 | + * @param type 类型 | |
| 55 | + * @return 枚举 | |
| 56 | + */ | |
| 57 | + public static IotModbusRawDataTypeEnum getByType(String type) { | |
| 58 | + return Arrays.stream(values()) | |
| 59 | + .filter(e -> e.getType().equals(type)) | |
| 60 | + .findFirst() | |
| 61 | + .orElse(null); | |
| 62 | + } | |
| 63 | + | |
| 64 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusConfigService.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.device; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 4 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigPageReqVO; | |
| 5 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO; | |
| 6 | +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO; | |
| 7 | +import jakarta.validation.Valid; | |
| 8 | + | |
| 9 | +import java.util.List; | |
| 10 | + | |
| 11 | +/** | |
| 12 | + * IoT 设备 Modbus 连接配置 Service 接口 | |
| 13 | + * | |
| 14 | + * @author 芋道源码 | |
| 15 | + */ | |
| 16 | +public interface IotDeviceModbusConfigService { | |
| 17 | + | |
| 18 | + /** | |
| 19 | + * 创建设备 Modbus 连接配置 | |
| 20 | + * | |
| 21 | + * @param createReqVO 创建信息 | |
| 22 | + * @return 编号 | |
| 23 | + */ | |
| 24 | + Long createModbusConfig(@Valid IotDeviceModbusConfigSaveReqVO createReqVO); | |
| 25 | + | |
| 26 | + /** | |
| 27 | + * 更新设备 Modbus 连接配置 | |
| 28 | + * | |
| 29 | + * @param updateReqVO 更新信息 | |
| 30 | + */ | |
| 31 | + void updateModbusConfig(@Valid IotDeviceModbusConfigSaveReqVO updateReqVO); | |
| 32 | + | |
| 33 | + /** | |
| 34 | + * 删除设备 Modbus 连接配置 | |
| 35 | + * | |
| 36 | + * @param id 编号 | |
| 37 | + */ | |
| 38 | + void deleteModbusConfig(Long id); | |
| 39 | + | |
| 40 | + /** | |
| 41 | + * 获得设备 Modbus 连接配置 | |
| 42 | + * | |
| 43 | + * @param id 编号 | |
| 44 | + * @return 设备 Modbus 连接配置 | |
| 45 | + */ | |
| 46 | + IotDeviceModbusConfigDO getModbusConfig(Long id); | |
| 47 | + | |
| 48 | + /** | |
| 49 | + * 根据设备编号获得 Modbus 连接配置 | |
| 50 | + * | |
| 51 | + * @param deviceId 设备编号 | |
| 52 | + * @return 设备 Modbus 连接配置 | |
| 53 | + */ | |
| 54 | + IotDeviceModbusConfigDO getModbusConfigByDeviceId(Long deviceId); | |
| 55 | + | |
| 56 | + /** | |
| 57 | + * 获得设备 Modbus 连接配置分页 | |
| 58 | + * | |
| 59 | + * @param pageReqVO 分页查询 | |
| 60 | + * @return 设备 Modbus 连接配置分页 | |
| 61 | + */ | |
| 62 | + PageResult<IotDeviceModbusConfigDO> getModbusConfigPage(IotDeviceModbusConfigPageReqVO pageReqVO); | |
| 63 | + | |
| 64 | + /** | |
| 65 | + * 获得所有启用的 Modbus 连接配置列表 | |
| 66 | + * | |
| 67 | + * @return 启用的 Modbus 连接配置列表 | |
| 68 | + */ | |
| 69 | + List<IotDeviceModbusConfigDO> getEnabledModbusConfigList(); | |
| 70 | + | |
| 71 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusConfigServiceImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.device; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 4 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 6 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigPageReqVO; | |
| 7 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO; | |
| 8 | +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO; | |
| 9 | +import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceModbusConfigMapper; | |
| 10 | +import jakarta.annotation.Resource; | |
| 11 | +import org.springframework.stereotype.Service; | |
| 12 | +import org.springframework.validation.annotation.Validated; | |
| 13 | + | |
| 14 | +import java.util.List; | |
| 15 | + | |
| 16 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 17 | +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_MODBUS_CONFIG_EXISTS; | |
| 18 | +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.DEVICE_MODBUS_CONFIG_NOT_EXISTS; | |
| 19 | + | |
| 20 | +/** | |
| 21 | + * IoT 设备 Modbus 连接配置 Service 实现类 | |
| 22 | + * | |
| 23 | + * @author 芋道源码 | |
| 24 | + */ | |
| 25 | +@Service | |
| 26 | +@Validated | |
| 27 | +public class IotDeviceModbusConfigServiceImpl implements IotDeviceModbusConfigService { | |
| 28 | + | |
| 29 | + @Resource | |
| 30 | + private IotDeviceModbusConfigMapper modbusConfigMapper; | |
| 31 | + | |
| 32 | + @Resource | |
| 33 | + private IotDeviceService deviceService; | |
| 34 | + | |
| 35 | + // TODO @AI:是不是搞成 save 接口?因为前端也不知道是 create 还是 update; | |
| 36 | + @Override | |
| 37 | + public Long createModbusConfig(IotDeviceModbusConfigSaveReqVO createReqVO) { | |
| 38 | + // 1.1 校验设备存在 | |
| 39 | + deviceService.validateDeviceExists(createReqVO.getDeviceId()); | |
| 40 | + // 1.2 校验设备是否已有 Modbus 配置 | |
| 41 | + validateModbusConfigUnique(createReqVO.getDeviceId(), null); | |
| 42 | + | |
| 43 | + // 2. 插入 | |
| 44 | + IotDeviceModbusConfigDO modbusConfig = BeanUtils.toBean(createReqVO, IotDeviceModbusConfigDO.class); | |
| 45 | + setDefaultValues(modbusConfig); | |
| 46 | + modbusConfigMapper.insert(modbusConfig); | |
| 47 | + return modbusConfig.getId(); | |
| 48 | + } | |
| 49 | + | |
| 50 | + @Override | |
| 51 | + public void updateModbusConfig(IotDeviceModbusConfigSaveReqVO updateReqVO) { | |
| 52 | + // 1.1 校验存在 | |
| 53 | + validateModbusConfigExists(updateReqVO.getId()); | |
| 54 | + // 1.2 校验设备存在 | |
| 55 | + deviceService.validateDeviceExists(updateReqVO.getDeviceId()); | |
| 56 | + // 1.3 校验唯一性 | |
| 57 | + validateModbusConfigUnique(updateReqVO.getDeviceId(), updateReqVO.getId()); | |
| 58 | + | |
| 59 | + // 2. 更新 | |
| 60 | + IotDeviceModbusConfigDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceModbusConfigDO.class); | |
| 61 | + modbusConfigMapper.updateById(updateObj); | |
| 62 | + } | |
| 63 | + | |
| 64 | + @Override | |
| 65 | + public void deleteModbusConfig(Long id) { | |
| 66 | + // 校验存在 | |
| 67 | + validateModbusConfigExists(id); | |
| 68 | + // 删除 | |
| 69 | + modbusConfigMapper.deleteById(id); | |
| 70 | + } | |
| 71 | + | |
| 72 | + private void validateModbusConfigExists(Long id) { | |
| 73 | + if (modbusConfigMapper.selectById(id) == null) { | |
| 74 | + throw exception(DEVICE_MODBUS_CONFIG_NOT_EXISTS); | |
| 75 | + } | |
| 76 | + } | |
| 77 | + | |
| 78 | + private void validateModbusConfigUnique(Long deviceId, Long excludeId) { | |
| 79 | + IotDeviceModbusConfigDO config = modbusConfigMapper.selectByDeviceId(deviceId); | |
| 80 | + // TODO @AI:ObjUtil notequals | |
| 81 | + if (config != null && !config.getId().equals(excludeId)) { | |
| 82 | + throw exception(DEVICE_MODBUS_CONFIG_EXISTS); | |
| 83 | + } | |
| 84 | + } | |
| 85 | + | |
| 86 | + // TODO @AI:不要这个;前端都必须传递; | |
| 87 | + private void setDefaultValues(IotDeviceModbusConfigDO config) { | |
| 88 | + if (config.getPort() == null) { | |
| 89 | + config.setPort(502); | |
| 90 | + } | |
| 91 | + if (config.getSlaveId() == null) { | |
| 92 | + config.setSlaveId(1); | |
| 93 | + } | |
| 94 | + if (config.getTimeout() == null) { | |
| 95 | + config.setTimeout(3000); | |
| 96 | + } | |
| 97 | + if (config.getRetryInterval() == null) { | |
| 98 | + config.setRetryInterval(1000); | |
| 99 | + } | |
| 100 | + } | |
| 101 | + | |
| 102 | + @Override | |
| 103 | + public IotDeviceModbusConfigDO getModbusConfig(Long id) { | |
| 104 | + return modbusConfigMapper.selectById(id); | |
| 105 | + } | |
| 106 | + | |
| 107 | + @Override | |
| 108 | + public IotDeviceModbusConfigDO getModbusConfigByDeviceId(Long deviceId) { | |
| 109 | + return modbusConfigMapper.selectByDeviceId(deviceId); | |
| 110 | + } | |
| 111 | + | |
| 112 | + @Override | |
| 113 | + public PageResult<IotDeviceModbusConfigDO> getModbusConfigPage(IotDeviceModbusConfigPageReqVO pageReqVO) { | |
| 114 | + return modbusConfigMapper.selectPage(pageReqVO); | |
| 115 | + } | |
| 116 | + | |
| 117 | + @Override | |
| 118 | + public List<IotDeviceModbusConfigDO> getEnabledModbusConfigList() { | |
| 119 | + return modbusConfigMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); | |
| 120 | + } | |
| 121 | + | |
| 122 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusPointService.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.device; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 4 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO; | |
| 5 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointSaveReqVO; | |
| 6 | +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO; | |
| 7 | +import jakarta.validation.Valid; | |
| 8 | + | |
| 9 | +import java.util.List; | |
| 10 | + | |
| 11 | +/** | |
| 12 | + * IoT 设备 Modbus 点位配置 Service 接口 | |
| 13 | + * | |
| 14 | + * @author 芋道源码 | |
| 15 | + */ | |
| 16 | +public interface IotDeviceModbusPointService { | |
| 17 | + | |
| 18 | + /** | |
| 19 | + * 创建设备 Modbus 点位配置 | |
| 20 | + * | |
| 21 | + * @param createReqVO 创建信息 | |
| 22 | + * @return 编号 | |
| 23 | + */ | |
| 24 | + Long createModbusPoint(@Valid IotDeviceModbusPointSaveReqVO createReqVO); | |
| 25 | + | |
| 26 | + /** | |
| 27 | + * 更新设备 Modbus 点位配置 | |
| 28 | + * | |
| 29 | + * @param updateReqVO 更新信息 | |
| 30 | + */ | |
| 31 | + void updateModbusPoint(@Valid IotDeviceModbusPointSaveReqVO updateReqVO); | |
| 32 | + | |
| 33 | + /** | |
| 34 | + * 删除设备 Modbus 点位配置 | |
| 35 | + * | |
| 36 | + * @param id 编号 | |
| 37 | + */ | |
| 38 | + void deleteModbusPoint(Long id); | |
| 39 | + | |
| 40 | + /** | |
| 41 | + * 获得设备 Modbus 点位配置 | |
| 42 | + * | |
| 43 | + * @param id 编号 | |
| 44 | + * @return 设备 Modbus 点位配置 | |
| 45 | + */ | |
| 46 | + IotDeviceModbusPointDO getModbusPoint(Long id); | |
| 47 | + | |
| 48 | + /** | |
| 49 | + * 获得设备 Modbus 点位配置分页 | |
| 50 | + * | |
| 51 | + * @param pageReqVO 分页查询 | |
| 52 | + * @return 设备 Modbus 点位配置分页 | |
| 53 | + */ | |
| 54 | + PageResult<IotDeviceModbusPointDO> getModbusPointPage(IotDeviceModbusPointPageReqVO pageReqVO); | |
| 55 | + | |
| 56 | + /** | |
| 57 | + * 根据设备编号获得点位配置列表 | |
| 58 | + * | |
| 59 | + * @param deviceId 设备编号 | |
| 60 | + * @return 点位配置列表 | |
| 61 | + */ | |
| 62 | + List<IotDeviceModbusPointDO> getModbusPointListByDeviceId(Long deviceId); | |
| 63 | + | |
| 64 | + /** | |
| 65 | + * 根据设备编号获得启用的点位配置列表 | |
| 66 | + * | |
| 67 | + * @param deviceId 设备编号 | |
| 68 | + * @return 启用的点位配置列表 | |
| 69 | + */ | |
| 70 | + List<IotDeviceModbusPointDO> getEnabledModbusPointListByDeviceId(Long deviceId); | |
| 71 | + | |
| 72 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusPointServiceImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.service.device; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 4 | +import cn.iocoder.yudao.framework.common.pojo.PageResult; | |
| 5 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 6 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO; | |
| 7 | +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointSaveReqVO; | |
| 8 | +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO; | |
| 9 | +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; | |
| 10 | +import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceModbusPointMapper; | |
| 11 | +import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService; | |
| 12 | +import jakarta.annotation.Resource; | |
| 13 | +import org.springframework.stereotype.Service; | |
| 14 | +import org.springframework.validation.annotation.Validated; | |
| 15 | + | |
| 16 | +import java.math.BigDecimal; | |
| 17 | +import java.util.List; | |
| 18 | + | |
| 19 | +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | |
| 20 | +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; | |
| 21 | + | |
| 22 | +/** | |
| 23 | + * IoT 设备 Modbus 点位配置 Service 实现类 | |
| 24 | + * | |
| 25 | + * @author 芋道源码 | |
| 26 | + */ | |
| 27 | +@Service | |
| 28 | +@Validated | |
| 29 | +public class IotDeviceModbusPointServiceImpl implements IotDeviceModbusPointService { | |
| 30 | + | |
| 31 | + @Resource | |
| 32 | + private IotDeviceModbusPointMapper modbusPointMapper; | |
| 33 | + | |
| 34 | + @Resource | |
| 35 | + private IotDeviceService deviceService; | |
| 36 | + | |
| 37 | + @Resource | |
| 38 | + private IotThingModelService thingModelService; | |
| 39 | + | |
| 40 | + @Override | |
| 41 | + public Long createModbusPoint(IotDeviceModbusPointSaveReqVO createReqVO) { | |
| 42 | + // 1.1 校验设备存在 | |
| 43 | + deviceService.validateDeviceExists(createReqVO.getDeviceId()); | |
| 44 | + // 1.2 校验物模型属性存在 | |
| 45 | + IotThingModelDO thingModel = validateThingModelExists(createReqVO.getThingModelId()); | |
| 46 | + // 1.3 校验同一设备下点位唯一性(基于 identifier) | |
| 47 | + validateModbusPointUnique(createReqVO.getDeviceId(), thingModel.getIdentifier(), null); | |
| 48 | + | |
| 49 | + // 2. 插入 | |
| 50 | + IotDeviceModbusPointDO modbusPoint = BeanUtils.toBean(createReqVO, IotDeviceModbusPointDO.class, | |
| 51 | + o -> o.setIdentifier(thingModel.getIdentifier()).setName(thingModel.getName())); | |
| 52 | + modbusPointMapper.insert(modbusPoint); | |
| 53 | + return modbusPoint.getId(); | |
| 54 | + } | |
| 55 | + | |
| 56 | + @Override | |
| 57 | + public void updateModbusPoint(IotDeviceModbusPointSaveReqVO updateReqVO) { | |
| 58 | + // 1.1 校验存在 | |
| 59 | + validateModbusPointExists(updateReqVO.getId()); | |
| 60 | + // 1.2 校验设备存在 | |
| 61 | + deviceService.validateDeviceExists(updateReqVO.getDeviceId()); | |
| 62 | + // 1.3 校验物模型属性存在 | |
| 63 | + IotThingModelDO thingModel = validateThingModelExists(updateReqVO.getThingModelId()); | |
| 64 | + // 1.4 校验同一设备下点位唯一性 | |
| 65 | + validateModbusPointUnique(updateReqVO.getDeviceId(), thingModel.getIdentifier(), updateReqVO.getId()); | |
| 66 | + | |
| 67 | + // 2. 更新 | |
| 68 | + IotDeviceModbusPointDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceModbusPointDO.class); | |
| 69 | + // TODO @AI:这块 | |
| 70 | + modbusPointMapper.updateById(updateObj); | |
| 71 | + } | |
| 72 | + | |
| 73 | + private IotThingModelDO validateThingModelExists(Long id) { | |
| 74 | + IotThingModelDO thingModel = thingModelService.getThingModel(id); | |
| 75 | + if (thingModel == null) { | |
| 76 | + throw exception(THING_MODEL_NOT_EXISTS); | |
| 77 | + } | |
| 78 | + return thingModel; | |
| 79 | + } | |
| 80 | + | |
| 81 | + @Override | |
| 82 | + public void deleteModbusPoint(Long id) { | |
| 83 | + // 校验存在 | |
| 84 | + validateModbusPointExists(id); | |
| 85 | + // 删除 | |
| 86 | + modbusPointMapper.deleteById(id); | |
| 87 | + } | |
| 88 | + | |
| 89 | + private IotDeviceModbusPointDO validateModbusPointExists(Long id) { | |
| 90 | + IotDeviceModbusPointDO point = modbusPointMapper.selectById(id); | |
| 91 | + if (point == null) { | |
| 92 | + throw exception(DEVICE_MODBUS_POINT_NOT_EXISTS); | |
| 93 | + } | |
| 94 | + return point; | |
| 95 | + } | |
| 96 | + | |
| 97 | + private void validateModbusPointUnique(Long deviceId, String identifier, Long excludeId) { | |
| 98 | + IotDeviceModbusPointDO point = modbusPointMapper.selectByDeviceIdAndIdentifier(deviceId, identifier); | |
| 99 | + // TODO @AI:ObjUtil notequals; | |
| 100 | + if (point != null && !point.getId().equals(excludeId)) { | |
| 101 | + throw exception(DEVICE_MODBUS_POINT_EXISTS); | |
| 102 | + } | |
| 103 | + } | |
| 104 | + | |
| 105 | + // TODO @AI:这块 | |
| 106 | + private void setDefaultValues(IotDeviceModbusPointDO point) { | |
| 107 | + if (point.getRegisterCount() == null) { | |
| 108 | + point.setRegisterCount(1); | |
| 109 | + } | |
| 110 | + if (point.getScale() == null) { | |
| 111 | + point.setScale(BigDecimal.ONE); | |
| 112 | + } | |
| 113 | + if (point.getPollInterval() == null) { | |
| 114 | + point.setPollInterval(5000); | |
| 115 | + } | |
| 116 | + } | |
| 117 | + | |
| 118 | + @Override | |
| 119 | + public IotDeviceModbusPointDO getModbusPoint(Long id) { | |
| 120 | + return modbusPointMapper.selectById(id); | |
| 121 | + } | |
| 122 | + | |
| 123 | + @Override | |
| 124 | + public PageResult<IotDeviceModbusPointDO> getModbusPointPage(IotDeviceModbusPointPageReqVO pageReqVO) { | |
| 125 | + return modbusPointMapper.selectPage(pageReqVO); | |
| 126 | + } | |
| 127 | + | |
| 128 | + @Override | |
| 129 | + public List<IotDeviceModbusPointDO> getModbusPointListByDeviceId(Long deviceId) { | |
| 130 | + return modbusPointMapper.selectListByDeviceId(deviceId); | |
| 131 | + } | |
| 132 | + | |
| 133 | + @Override | |
| 134 | + public List<IotDeviceModbusPointDO> getEnabledModbusPointListByDeviceId(Long deviceId) { | |
| 135 | + return modbusPointMapper.selectListByDeviceIdAndStatus(deviceId, CommonStatusEnum.ENABLE.getStatus()); | |
| 136 | + } | |
| 137 | + | |
| 138 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/IotDeviceCommonApi.java
| ... | ... | @@ -4,6 +4,9 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 4 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 5 | 5 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; |
| 6 | 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; |
| 7 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; | |
| 8 | + | |
| 9 | +import java.util.List; | |
| 7 | 10 | |
| 8 | 11 | /** |
| 9 | 12 | * IoT 设备通用 API |
| ... | ... | @@ -28,4 +31,11 @@ public interface IotDeviceCommonApi { |
| 28 | 31 | */ |
| 29 | 32 | CommonResult<IotDeviceRespDTO> getDevice(IotDeviceGetReqDTO infoReqDTO); |
| 30 | 33 | |
| 34 | + /** | |
| 35 | + * 获取所有启用的 Modbus 设备配置列表 | |
| 36 | + * | |
| 37 | + * @return Modbus 设备配置列表 | |
| 38 | + */ | |
| 39 | + CommonResult<List<IotModbusDeviceConfigRespDTO>> getEnabledModbusDeviceConfigs(); | |
| 40 | + | |
| 31 | 41 | } | ... | ... |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusDeviceConfigRespDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.core.biz.dto; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +import java.util.List; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * IoT Modbus 设备配置 Response DTO | |
| 9 | + * | |
| 10 | + * @author 芋道源码 | |
| 11 | + */ | |
| 12 | +@Data | |
| 13 | +public class IotModbusDeviceConfigRespDTO { | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * 设备编号 | |
| 17 | + */ | |
| 18 | + private Long deviceId; | |
| 19 | + /** | |
| 20 | + * 产品标识 | |
| 21 | + */ | |
| 22 | + private String productKey; | |
| 23 | + /** | |
| 24 | + * 设备名称 | |
| 25 | + */ | |
| 26 | + private String deviceName; | |
| 27 | + /** | |
| 28 | + * 租户编号 | |
| 29 | + */ | |
| 30 | + private Long tenantId; | |
| 31 | + | |
| 32 | + // ========== Modbus 连接配置 ========== | |
| 33 | + | |
| 34 | + /** | |
| 35 | + * Modbus 服务器 IP 地址 | |
| 36 | + */ | |
| 37 | + private String ip; | |
| 38 | + /** | |
| 39 | + * Modbus 端口 | |
| 40 | + */ | |
| 41 | + private Integer port; | |
| 42 | + /** | |
| 43 | + * 从站地址 | |
| 44 | + */ | |
| 45 | + private Integer slaveId; | |
| 46 | + /** | |
| 47 | + * 连接超时时间(毫秒) | |
| 48 | + */ | |
| 49 | + private Integer timeout; | |
| 50 | + /** | |
| 51 | + * 重试间隔(毫秒) | |
| 52 | + */ | |
| 53 | + private Integer retryInterval; | |
| 54 | + | |
| 55 | + // ========== 点位配置 ========== | |
| 56 | + | |
| 57 | + /** | |
| 58 | + * 点位列表 | |
| 59 | + */ | |
| 60 | + private List<IotModbusPointRespDTO> points; | |
| 61 | + | |
| 62 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusPointRespDTO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.core.biz.dto; | |
| 2 | + | |
| 3 | +import lombok.Data; | |
| 4 | + | |
| 5 | +import java.math.BigDecimal; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * IoT Modbus 点位配置 Response DTO | |
| 9 | + * | |
| 10 | + * @author 芋道源码 | |
| 11 | + */ | |
| 12 | +@Data | |
| 13 | +public class IotModbusPointRespDTO { | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * 点位编号 | |
| 17 | + */ | |
| 18 | + private Long id; | |
| 19 | + /** | |
| 20 | + * 属性标识符(物模型的 identifier) | |
| 21 | + */ | |
| 22 | + private String identifier; | |
| 23 | + /** | |
| 24 | + * 属性名称(物模型的 name) | |
| 25 | + */ | |
| 26 | + private String name; | |
| 27 | + | |
| 28 | + // ========== Modbus 协议配置 ========== | |
| 29 | + | |
| 30 | + // TODO @AI:所有的枚举,通过 @,不要写上去; | |
| 31 | + /** | |
| 32 | + * Modbus 功能码 | |
| 33 | + * | |
| 34 | + * 1-ReadCoils 2-ReadDiscreteInputs 3-ReadHoldingRegisters 4-ReadInputRegisters | |
| 35 | + */ | |
| 36 | + private Integer functionCode; | |
| 37 | + /** | |
| 38 | + * 寄存器起始地址 | |
| 39 | + */ | |
| 40 | + private Integer registerAddress; | |
| 41 | + /** | |
| 42 | + * 寄存器数量 | |
| 43 | + */ | |
| 44 | + private Integer registerCount; | |
| 45 | + /** | |
| 46 | + * 字节序 | |
| 47 | + * | |
| 48 | + * AB/BA/ABCD/CDAB/DCBA/BADC | |
| 49 | + */ | |
| 50 | + private String byteOrder; | |
| 51 | + /** | |
| 52 | + * 原始数据类型 | |
| 53 | + * | |
| 54 | + * INT16/UINT16/INT32/UINT32/FLOAT/DOUBLE/BOOLEAN/STRING | |
| 55 | + */ | |
| 56 | + private String rawDataType; | |
| 57 | + /** | |
| 58 | + * 缩放因子 | |
| 59 | + */ | |
| 60 | + private BigDecimal scale; | |
| 61 | + /** | |
| 62 | + * 轮询间隔(毫秒) | |
| 63 | + */ | |
| 64 | + private Integer pollInterval; | |
| 65 | + | |
| 66 | + // ========== 物模型相关字段 ========== | |
| 67 | + | |
| 68 | + // TODO @AI:分析一下,是否有必要返回 | |
| 69 | + /** | |
| 70 | + * 数据类型(来自物模型) | |
| 71 | + */ | |
| 72 | + private String dataType; | |
| 73 | + | |
| 74 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/pom.xml
| ... | ... | @@ -33,7 +33,7 @@ |
| 33 | 33 | <groupId>org.apache.rocketmq</groupId> |
| 34 | 34 | <artifactId>rocketmq-spring-boot-starter</artifactId> |
| 35 | 35 | <!-- TODO @芋艿:消息队列,后续可能去掉,默认不使用 rocketmq --> |
| 36 | -<!-- <optional>true</optional> --> | |
| 36 | + <optional>true</optional> | |
| 37 | 37 | </dependency> |
| 38 | 38 | |
| 39 | 39 | <!-- 工具类相关 --> |
| ... | ... | @@ -48,6 +48,13 @@ |
| 48 | 48 | <artifactId>vertx-mqtt</artifactId> |
| 49 | 49 | </dependency> |
| 50 | 50 | |
| 51 | + <!-- Modbus 相关 --> | |
| 52 | + <dependency> | |
| 53 | + <groupId>com.ghgande</groupId> | |
| 54 | + <artifactId>j2mod</artifactId> | |
| 55 | + <version>3.2.1</version> | |
| 56 | + </dependency> | |
| 57 | + | |
| 51 | 58 | <!-- 测试相关 --> |
| 52 | 59 | <dependency> |
| 53 | 60 | <groupId>cn.iocoder.boot</groupId> | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
| ... | ... | @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxDownstreamSubscr |
| 6 | 6 | import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol; |
| 7 | 7 | import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpDownstreamSubscriber; |
| 8 | 8 | import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol; |
| 9 | +import cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp.*; | |
| 9 | 10 | import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttDownstreamSubscriber; |
| 10 | 11 | import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol; |
| 11 | 12 | import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; |
| ... | ... | @@ -194,4 +195,38 @@ public class IotGatewayConfiguration { |
| 194 | 195 | |
| 195 | 196 | } |
| 196 | 197 | |
| 198 | + /** | |
| 199 | + * IoT 网关 Modbus TCP 协议配置类 | |
| 200 | + */ | |
| 201 | + @Configuration | |
| 202 | + @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.modbus-tcp", name = "enabled", havingValue = "true") | |
| 203 | + @Slf4j | |
| 204 | + public static class ModbusTcpProtocolConfiguration { | |
| 205 | + | |
| 206 | + @Bean(name = "modbusTcpVertx", destroyMethod = "close") | |
| 207 | + public Vertx modbusTcpVertx() { | |
| 208 | + return Vertx.vertx(); | |
| 209 | + } | |
| 210 | + | |
| 211 | + @Bean | |
| 212 | + public IotModbusTcpUpstreamProtocol iotModbusTcpUpstreamProtocol(IotGatewayProperties gatewayProperties, | |
| 213 | + IotDeviceMessageService messageService, | |
| 214 | + IotModbusTcpConnectionManager connectionManager, | |
| 215 | + IotModbusTcpPollScheduler pollScheduler, | |
| 216 | + IotModbusTcpConfigCacheService configCacheService, | |
| 217 | + IotModbusTcpUpstreamHandler upstreamHandler, | |
| 218 | + @Qualifier("modbusTcpVertx") Vertx modbusTcpVertx) { | |
| 219 | + return new IotModbusTcpUpstreamProtocol(gatewayProperties.getProtocol().getModbusTcp(), | |
| 220 | + messageService, connectionManager, pollScheduler, configCacheService, upstreamHandler, modbusTcpVertx); | |
| 221 | + } | |
| 222 | + | |
| 223 | + @Bean | |
| 224 | + public IotModbusTcpDownstreamSubscriber iotModbusTcpDownstreamSubscriber(IotModbusTcpUpstreamProtocol upstreamProtocol, | |
| 225 | + IotModbusTcpDownstreamHandler downstreamHandler, | |
| 226 | + IotMessageBus messageBus) { | |
| 227 | + return new IotModbusTcpDownstreamSubscriber(upstreamProtocol, downstreamHandler, messageBus); | |
| 228 | + } | |
| 229 | + | |
| 230 | + } | |
| 231 | + | |
| 197 | 232 | } | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
| ... | ... | @@ -93,6 +93,11 @@ public class IotGatewayProperties { |
| 93 | 93 | */ |
| 94 | 94 | private MqttWsProperties mqttWs; |
| 95 | 95 | |
| 96 | + /** | |
| 97 | + * Modbus TCP 组件配置 | |
| 98 | + */ | |
| 99 | + private ModbusTcpProperties modbusTcp; | |
| 100 | + | |
| 96 | 101 | } |
| 97 | 102 | |
| 98 | 103 | @Data |
| ... | ... | @@ -503,4 +508,21 @@ public class IotGatewayProperties { |
| 503 | 508 | |
| 504 | 509 | } |
| 505 | 510 | |
| 511 | + @Data | |
| 512 | + public static class ModbusTcpProperties { | |
| 513 | + | |
| 514 | + /** | |
| 515 | + * 是否开启 | |
| 516 | + */ | |
| 517 | + @NotNull(message = "是否开启不能为空") | |
| 518 | + private Boolean enabled; | |
| 519 | + | |
| 520 | + /** | |
| 521 | + * 配置刷新间隔(秒) | |
| 522 | + */ | |
| 523 | + // TODO @AI:需要校验下非空; | |
| 524 | + private Integer configRefreshInterval = 30; | |
| 525 | + | |
| 526 | + } | |
| 527 | + | |
| 506 | 528 | } | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusDataConverter.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; | |
| 4 | +import lombok.extern.slf4j.Slf4j; | |
| 5 | +import org.springframework.stereotype.Component; | |
| 6 | + | |
| 7 | +import java.math.BigDecimal; | |
| 8 | +import java.math.RoundingMode; | |
| 9 | +import java.nio.ByteBuffer; | |
| 10 | +import java.nio.ByteOrder; | |
| 11 | + | |
| 12 | +// TODO @AI:注释上:希望 1 和 2,然后 3 只是辅助说明 | |
| 13 | +/** | |
| 14 | + * IoT Modbus 数据转换器 | |
| 15 | + * | |
| 16 | + * 负责: | |
| 17 | + * 1. 将 Modbus 原始寄存器值转换为物模型属性值 | |
| 18 | + * 2. 将物模型属性值转换为 Modbus 原始寄存器值 | |
| 19 | + * 3. 处理字节序、数据类型、缩放因子 | |
| 20 | + * | |
| 21 | + * @author 芋道源码 | |
| 22 | + */ | |
| 23 | +// TODO @AI:希望它的初始化,在 configuration 里; | |
| 24 | +@Component | |
| 25 | +@Slf4j | |
| 26 | +public class IotModbusDataConverter { | |
| 27 | + | |
| 28 | + /** | |
| 29 | + * 将原始值转换为物模型属性值 | |
| 30 | + * | |
| 31 | + * @param rawValues 原始值数组(寄存器值或线圈值) | |
| 32 | + * @param point 点位配置 | |
| 33 | + * @return 转换后的属性值 | |
| 34 | + */ | |
| 35 | + public Object convertToPropertyValue(int[] rawValues, IotModbusPointRespDTO point) { | |
| 36 | + // TODO @AI:CollUtil.isEmpty; | |
| 37 | + if (rawValues == null || rawValues.length == 0) { | |
| 38 | + return null; | |
| 39 | + } | |
| 40 | + String rawDataType = point.getRawDataType(); | |
| 41 | + String byteOrder = point.getByteOrder(); | |
| 42 | + // TODO @AI:defaultIfNull; | |
| 43 | + BigDecimal scale = point.getScale() != null ? point.getScale() : BigDecimal.ONE; | |
| 44 | + | |
| 45 | + // 1. 根据原始数据类型解析原始数值 | |
| 46 | + Number rawNumber = parseRawValue(rawValues, rawDataType, byteOrder); | |
| 47 | + if (rawNumber == null) { | |
| 48 | + return null; | |
| 49 | + } | |
| 50 | + | |
| 51 | + // 2. 应用缩放因子:实际值 = 原始值 × scale | |
| 52 | + BigDecimal actualValue = new BigDecimal(rawNumber.toString()).multiply(scale); | |
| 53 | + | |
| 54 | + // 3. 根据数据类型返回合适的 Java 类型 | |
| 55 | + return formatValue(actualValue, rawDataType); | |
| 56 | + } | |
| 57 | + | |
| 58 | + /** | |
| 59 | + * 将物模型属性值转换为原始寄存器值 | |
| 60 | + * | |
| 61 | + * @param propertyValue 属性值 | |
| 62 | + * @param point 点位配置 | |
| 63 | + * @return 原始值数组 | |
| 64 | + */ | |
| 65 | + public int[] convertToRawValues(Object propertyValue, IotModbusPointRespDTO point) { | |
| 66 | + if (propertyValue == null) { | |
| 67 | + return new int[0]; | |
| 68 | + } | |
| 69 | + String rawDataType = point.getRawDataType(); | |
| 70 | + String byteOrder = point.getByteOrder(); | |
| 71 | + BigDecimal scale = point.getScale() != null ? point.getScale() : BigDecimal.ONE; | |
| 72 | + int registerCount = point.getRegisterCount() != null ? point.getRegisterCount() : 1; | |
| 73 | + | |
| 74 | + // 1. 转换为 BigDecimal | |
| 75 | + BigDecimal actualValue = new BigDecimal(propertyValue.toString()); | |
| 76 | + | |
| 77 | + // 2. 应用缩放因子:原始值 = 实际值 ÷ scale | |
| 78 | + BigDecimal rawValue = actualValue.divide(scale, 0, RoundingMode.HALF_UP); | |
| 79 | + | |
| 80 | + // 3. 根据原始数据类型编码为寄存器值 | |
| 81 | + return encodeToRegisters(rawValue, rawDataType, byteOrder, registerCount); | |
| 82 | + } | |
| 83 | + | |
| 84 | + /** | |
| 85 | + * 解析原始值 | |
| 86 | + */ | |
| 87 | + private Number parseRawValue(int[] rawValues, String rawDataType, String byteOrder) { | |
| 88 | + // TODO @AI:是不是可以用枚举?复用 IotModbusRawDataTypeEnum 里的; | |
| 89 | + switch (rawDataType.toUpperCase()) { | |
| 90 | + case "BOOLEAN": | |
| 91 | + return rawValues[0] != 0 ? 1 : 0; | |
| 92 | + case "INT16": | |
| 93 | + return (short) rawValues[0]; | |
| 94 | + case "UINT16": | |
| 95 | + return rawValues[0] & 0xFFFF; | |
| 96 | + case "INT32": | |
| 97 | + return parseInt32(rawValues, byteOrder); | |
| 98 | + case "UINT32": | |
| 99 | + return parseUint32(rawValues, byteOrder); | |
| 100 | + case "FLOAT": | |
| 101 | + return parseFloat(rawValues, byteOrder); | |
| 102 | + case "DOUBLE": | |
| 103 | + return parseDouble(rawValues, byteOrder); | |
| 104 | + // TODO @AI:未知抛出异常; | |
| 105 | + default: | |
| 106 | + log.warn("[parseRawValue][不支持的数据类型: {}]", rawDataType); | |
| 107 | + return rawValues[0]; | |
| 108 | + } | |
| 109 | + } | |
| 110 | + | |
| 111 | + // TODO @AI:这些转换,有没一些工具类,可以优化;类似 hutool 的? | |
| 112 | + private int parseInt32(int[] rawValues, String byteOrder) { | |
| 113 | + if (rawValues.length < 2) { | |
| 114 | + return rawValues[0]; | |
| 115 | + } | |
| 116 | + byte[] bytes = reorderBytes(registersToBytes(rawValues, 2), byteOrder); | |
| 117 | + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt(); | |
| 118 | + } | |
| 119 | + | |
| 120 | + private long parseUint32(int[] rawValues, String byteOrder) { | |
| 121 | + if (rawValues.length < 2) { | |
| 122 | + return rawValues[0] & 0xFFFFFFFFL; | |
| 123 | + } | |
| 124 | + byte[] bytes = reorderBytes(registersToBytes(rawValues, 2), byteOrder); | |
| 125 | + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getInt() & 0xFFFFFFFFL; | |
| 126 | + } | |
| 127 | + | |
| 128 | + private float parseFloat(int[] rawValues, String byteOrder) { | |
| 129 | + if (rawValues.length < 2) { | |
| 130 | + return (float) rawValues[0]; | |
| 131 | + } | |
| 132 | + byte[] bytes = reorderBytes(registersToBytes(rawValues, 2), byteOrder); | |
| 133 | + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getFloat(); | |
| 134 | + } | |
| 135 | + | |
| 136 | + private double parseDouble(int[] rawValues, String byteOrder) { | |
| 137 | + if (rawValues.length < 4) { | |
| 138 | + return rawValues[0]; | |
| 139 | + } | |
| 140 | + byte[] bytes = reorderBytes(registersToBytes(rawValues, 4), byteOrder); | |
| 141 | + return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getDouble(); | |
| 142 | + } | |
| 143 | + | |
| 144 | + /** | |
| 145 | + * 将寄存器值转换为字节数组 | |
| 146 | + */ | |
| 147 | + private byte[] registersToBytes(int[] registers, int count) { | |
| 148 | + byte[] bytes = new byte[count * 2]; | |
| 149 | + for (int i = 0; i < Math.min(registers.length, count); i++) { | |
| 150 | + bytes[i * 2] = (byte) ((registers[i] >> 8) & 0xFF); | |
| 151 | + bytes[i * 2 + 1] = (byte) (registers[i] & 0xFF); | |
| 152 | + } | |
| 153 | + return bytes; | |
| 154 | + } | |
| 155 | + | |
| 156 | + /** | |
| 157 | + * 根据字节序重排字节 | |
| 158 | + */ | |
| 159 | + private byte[] reorderBytes(byte[] bytes, String byteOrder) { | |
| 160 | + // 大端序,不需要调整 | |
| 161 | + // TODO @AI:StrUtil.equals;null 要抛出异常; | |
| 162 | + if (byteOrder == null || "ABCD".equals(byteOrder) || "AB".equals(byteOrder)) { | |
| 163 | + return bytes; | |
| 164 | + } | |
| 165 | + | |
| 166 | + // 其他字节序调整 | |
| 167 | + byte[] result = new byte[bytes.length]; | |
| 168 | + switch (byteOrder.toUpperCase()) { | |
| 169 | + // TODO @AI:走枚举;sortOrder; | |
| 170 | + case "BA": // 小端序(16 位) | |
| 171 | + if (bytes.length >= 2) { | |
| 172 | + result[0] = bytes[1]; | |
| 173 | + result[1] = bytes[0]; | |
| 174 | + } | |
| 175 | + break; | |
| 176 | + case "CDAB": // 大端字交换(32 位) | |
| 177 | + if (bytes.length >= 4) { | |
| 178 | + result[0] = bytes[2]; | |
| 179 | + result[1] = bytes[3]; | |
| 180 | + result[2] = bytes[0]; | |
| 181 | + result[3] = bytes[1]; | |
| 182 | + } | |
| 183 | + break; | |
| 184 | + case "DCBA": // 小端序(32 位) | |
| 185 | + if (bytes.length >= 4) { | |
| 186 | + result[0] = bytes[3]; | |
| 187 | + result[1] = bytes[2]; | |
| 188 | + result[2] = bytes[1]; | |
| 189 | + result[3] = bytes[0]; | |
| 190 | + } | |
| 191 | + break; | |
| 192 | + case "BADC": // 小端字交换(32 位) | |
| 193 | + if (bytes.length >= 4) { | |
| 194 | + result[0] = bytes[1]; | |
| 195 | + result[1] = bytes[0]; | |
| 196 | + result[2] = bytes[3]; | |
| 197 | + result[3] = bytes[2]; | |
| 198 | + } | |
| 199 | + break; | |
| 200 | + // TODO @AI:未知就抛出异常; | |
| 201 | + default: | |
| 202 | + return bytes; | |
| 203 | + } | |
| 204 | + return result; | |
| 205 | + } | |
| 206 | + | |
| 207 | + /** | |
| 208 | + * 编码为寄存器值 | |
| 209 | + */ | |
| 210 | + private int[] encodeToRegisters(BigDecimal rawValue, String rawDataType, String byteOrder, int registerCount) { | |
| 211 | + // TODO @AI:是不是可以用枚举?复用 IotModbusRawDataTypeEnum 里的; | |
| 212 | + switch (rawDataType.toUpperCase()) { | |
| 213 | + case "BOOLEAN": | |
| 214 | + return new int[]{rawValue.intValue() != 0 ? 1 : 0}; | |
| 215 | + case "INT16": | |
| 216 | + case "UINT16": | |
| 217 | + return new int[]{rawValue.intValue() & 0xFFFF}; | |
| 218 | + case "INT32": | |
| 219 | + case "UINT32": | |
| 220 | + return encodeInt32(rawValue.intValue(), byteOrder); | |
| 221 | + case "FLOAT": | |
| 222 | + return encodeFloat(rawValue.floatValue(), byteOrder); | |
| 223 | + case "DOUBLE": | |
| 224 | + return encodeDouble(rawValue.doubleValue(), byteOrder); | |
| 225 | + default: | |
| 226 | + return new int[]{rawValue.intValue()}; | |
| 227 | + } | |
| 228 | + } | |
| 229 | + | |
| 230 | + // TODO @AI:这些转换,有没一些工具类,可以优化;类似 hutool 的? | |
| 231 | + private int[] encodeInt32(int value, String byteOrder) { | |
| 232 | + byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(value).array(); | |
| 233 | + bytes = reorderBytes(bytes, byteOrder); | |
| 234 | + return bytesToRegisters(bytes); | |
| 235 | + } | |
| 236 | + | |
| 237 | + private int[] encodeFloat(float value, String byteOrder) { | |
| 238 | + byte[] bytes = ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putFloat(value).array(); | |
| 239 | + bytes = reorderBytes(bytes, byteOrder); | |
| 240 | + return bytesToRegisters(bytes); | |
| 241 | + } | |
| 242 | + | |
| 243 | + private int[] encodeDouble(double value, String byteOrder) { | |
| 244 | + byte[] bytes = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putDouble(value).array(); | |
| 245 | + bytes = reorderBytes(bytes, byteOrder); | |
| 246 | + return bytesToRegisters(bytes); | |
| 247 | + } | |
| 248 | + | |
| 249 | + private int[] bytesToRegisters(byte[] bytes) { | |
| 250 | + int[] registers = new int[bytes.length / 2]; | |
| 251 | + for (int i = 0; i < registers.length; i++) { | |
| 252 | + registers[i] = ((bytes[i * 2] & 0xFF) << 8) | (bytes[i * 2 + 1] & 0xFF); | |
| 253 | + } | |
| 254 | + return registers; | |
| 255 | + } | |
| 256 | + | |
| 257 | + /** | |
| 258 | + * 格式化返回值 | |
| 259 | + */ | |
| 260 | + private Object formatValue(BigDecimal value, String rawDataType) { | |
| 261 | + // TODO @AI:是不是可以用枚举?复用 IotModbusRawDataTypeEnum 里的; | |
| 262 | + switch (rawDataType.toUpperCase()) { | |
| 263 | + case "BOOLEAN": | |
| 264 | + return value.intValue() != 0; | |
| 265 | + case "INT16": | |
| 266 | + case "INT32": | |
| 267 | + return value.intValue(); | |
| 268 | + case "UINT16": | |
| 269 | + case "UINT32": | |
| 270 | + return value.longValue(); | |
| 271 | + case "FLOAT": | |
| 272 | + return value.floatValue(); | |
| 273 | + case "DOUBLE": | |
| 274 | + return value.doubleValue(); | |
| 275 | + // TODO @AI:未知抛出异常; | |
| 276 | + default: | |
| 277 | + return value; | |
| 278 | + } | |
| 279 | + } | |
| 280 | + | |
| 281 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpClient.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; | |
| 4 | +import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction; | |
| 5 | +import com.ghgande.j2mod.modbus.msg.*; | |
| 6 | +import com.ghgande.j2mod.modbus.procimg.InputRegister; | |
| 7 | +import com.ghgande.j2mod.modbus.procimg.Register; | |
| 8 | +import com.ghgande.j2mod.modbus.procimg.SimpleRegister; | |
| 9 | +import com.ghgande.j2mod.modbus.util.BitVector; | |
| 10 | +import io.vertx.core.Future; | |
| 11 | +import lombok.RequiredArgsConstructor; | |
| 12 | +import lombok.extern.slf4j.Slf4j; | |
| 13 | +import org.springframework.stereotype.Component; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * IoT Modbus TCP 客户端 | |
| 17 | + * | |
| 18 | + * 负责: | |
| 19 | + * 1. 封装 Modbus 读写操作 | |
| 20 | + * 2. 根据功能码执行对应的 Modbus 请求 | |
| 21 | + * | |
| 22 | + * @author 芋道源码 | |
| 23 | + */ | |
| 24 | +// TODO @AI:希望它的初始化,在 configuration 里; | |
| 25 | +@Component | |
| 26 | +@RequiredArgsConstructor // TODO @AI:这个注解,是不是可以去掉? | |
| 27 | +@Slf4j | |
| 28 | +public class IotModbusTcpClient { | |
| 29 | + | |
| 30 | + /** | |
| 31 | + * 读取 Modbus 数据 | |
| 32 | + * | |
| 33 | + * @param connection Modbus 连接 | |
| 34 | + * @param slaveId 从站地址 | |
| 35 | + * @param point 点位配置 | |
| 36 | + * @return 原始值(int 数组) | |
| 37 | + */ | |
| 38 | + public Future<int[]> read(IotModbusTcpConnectionManager.ModbusConnection connection, | |
| 39 | + Integer slaveId, | |
| 40 | + IotModbusPointRespDTO point) { | |
| 41 | + return connection.executeBlocking(tcpConnection -> { | |
| 42 | + try { | |
| 43 | + // 1. 创建请求 | |
| 44 | + ModbusRequest request = createReadRequest(point.getFunctionCode(), | |
| 45 | + point.getRegisterAddress(), point.getRegisterCount()); | |
| 46 | + request.setUnitID(slaveId); | |
| 47 | + | |
| 48 | + // 2. 执行事务(请求) | |
| 49 | + ModbusTCPTransaction transaction = new ModbusTCPTransaction(tcpConnection); | |
| 50 | + transaction.setRequest(request); | |
| 51 | + transaction.execute(); | |
| 52 | + | |
| 53 | + // 3. 解析响应 | |
| 54 | + ModbusResponse response = transaction.getResponse(); | |
| 55 | + return extractValues(response, point.getFunctionCode()); | |
| 56 | + } catch (Exception e) { | |
| 57 | + // TODO @AI:抛出异常时,增加更多的上下文信息,比如设备、点位等 | |
| 58 | + throw new RuntimeException("Modbus 读取失败", e); | |
| 59 | + } | |
| 60 | + }); | |
| 61 | + } | |
| 62 | + | |
| 63 | + /** | |
| 64 | + * 写入 Modbus 数据 | |
| 65 | + * | |
| 66 | + * @param connection Modbus 连接 | |
| 67 | + * @param slaveId 从站地址 | |
| 68 | + * @param point 点位配置 | |
| 69 | + * @param values 要写入的值 | |
| 70 | + * @return 是否成功 | |
| 71 | + */ | |
| 72 | + public Future<Boolean> write(IotModbusTcpConnectionManager.ModbusConnection connection, | |
| 73 | + Integer slaveId, | |
| 74 | + IotModbusPointRespDTO point, | |
| 75 | + int[] values) { | |
| 76 | + return connection.executeBlocking(tcpConnection -> { | |
| 77 | + try { | |
| 78 | + // 1. 创建请求 | |
| 79 | + ModbusRequest request = createWriteRequest(point.getFunctionCode(), | |
| 80 | + point.getRegisterAddress(), point.getRegisterCount(), values); | |
| 81 | + if (request == null) { | |
| 82 | + throw new RuntimeException("功能码 " + point.getFunctionCode() + " 不支持写操作"); | |
| 83 | + } | |
| 84 | + request.setUnitID(slaveId); | |
| 85 | + | |
| 86 | + // 2. 执行事务(请求) | |
| 87 | + ModbusTCPTransaction transaction = new ModbusTCPTransaction(tcpConnection); | |
| 88 | + transaction.setRequest(request); | |
| 89 | + transaction.execute(); | |
| 90 | + return true; | |
| 91 | + } catch (Exception e) { | |
| 92 | + // TODO @AI:抛出异常时,增加更多的上下文信息,比如设备、点位等; | |
| 93 | + throw new RuntimeException("Modbus 写入失败", e); | |
| 94 | + } | |
| 95 | + }); | |
| 96 | + } | |
| 97 | + | |
| 98 | + /** | |
| 99 | + * 创建读取请求 | |
| 100 | + */ | |
| 101 | + private ModbusRequest createReadRequest(Integer functionCode, Integer address, Integer count) { | |
| 102 | + // TODO @AI:1、2、3、4 能不能有枚举哈?这样 1、2、3、4 那的注释就不用写; | |
| 103 | + switch (functionCode) { | |
| 104 | + case 1: // ReadCoils | |
| 105 | + return new ReadCoilsRequest(address, count); | |
| 106 | + case 2: // ReadDiscreteInputs | |
| 107 | + return new ReadInputDiscretesRequest(address, count); | |
| 108 | + case 3: // ReadHoldingRegisters | |
| 109 | + return new ReadMultipleRegistersRequest(address, count); | |
| 110 | + case 4: // ReadInputRegisters | |
| 111 | + return new ReadInputRegistersRequest(address, count); | |
| 112 | + default: | |
| 113 | + throw new IllegalArgumentException("不支持的功能码: " + functionCode); | |
| 114 | + } | |
| 115 | + } | |
| 116 | + | |
| 117 | + /** | |
| 118 | + * 创建写入请求 | |
| 119 | + */ | |
| 120 | + private ModbusRequest createWriteRequest(Integer functionCode, Integer address, Integer count, int[] values) { | |
| 121 | + // TODO @AI:5、6、15、16 能不能有枚举哈?这样 5、6、15、16 那的注释就不用写; | |
| 122 | + switch (functionCode) { | |
| 123 | + case 1: // WriteCoils (使用 5 或 15) | |
| 124 | + if (count == 1) { | |
| 125 | + return new WriteCoilRequest(address, values[0] != 0); | |
| 126 | + } else { | |
| 127 | + // 多线圈写入 | |
| 128 | + BitVector bv = new BitVector(count); | |
| 129 | + for (int i = 0; i < Math.min(values.length, count); i++) { | |
| 130 | + bv.setBit(i, values[i] != 0); | |
| 131 | + } | |
| 132 | + return new WriteMultipleCoilsRequest(address, bv); | |
| 133 | + } | |
| 134 | + case 3: // WriteHoldingRegisters (使用 6 或 16) | |
| 135 | + if (count == 1) { | |
| 136 | + return new WriteSingleRegisterRequest(address, new SimpleRegister(values[0])); | |
| 137 | + } else { | |
| 138 | + Register[] registers = new com.ghgande.j2mod.modbus.procimg.SimpleRegister[count]; | |
| 139 | + for (int i = 0; i < count; i++) { | |
| 140 | + registers[i] = new SimpleRegister(i < values.length ? values[i] : 0); | |
| 141 | + } | |
| 142 | + return new WriteMultipleRegistersRequest(address, registers); | |
| 143 | + } | |
| 144 | + case 2: // ReadDiscreteInputs - 只读 | |
| 145 | + case 4: // ReadInputRegisters - 只读 | |
| 146 | + return null; | |
| 147 | + default: | |
| 148 | + throw new IllegalArgumentException("不支持的功能码: " + functionCode); | |
| 149 | + } | |
| 150 | + } | |
| 151 | + | |
| 152 | + /** | |
| 153 | + * 从响应中提取值 | |
| 154 | + */ | |
| 155 | + private int[] extractValues(ModbusResponse response, Integer functionCode) { | |
| 156 | + // TODO @AI:1、2、3、4 能不能有枚举哈?这样 1、2、3、4 那的注释就不用写; | |
| 157 | + switch (functionCode) { | |
| 158 | + case 1: // ReadCoils | |
| 159 | + ReadCoilsResponse coilsResponse = (ReadCoilsResponse) response; | |
| 160 | + int bitCount = coilsResponse.getBitCount(); | |
| 161 | + int[] coilValues = new int[bitCount]; | |
| 162 | + for (int i = 0; i < bitCount; i++) { | |
| 163 | + coilValues[i] = coilsResponse.getCoilStatus(i) ? 1 : 0; | |
| 164 | + } | |
| 165 | + return coilValues; | |
| 166 | + case 2: // ReadDiscreteInputs | |
| 167 | + ReadInputDiscretesResponse discretesResponse = (ReadInputDiscretesResponse) response; | |
| 168 | + int discreteCount = discretesResponse.getBitCount(); | |
| 169 | + int[] discreteValues = new int[discreteCount]; | |
| 170 | + for (int i = 0; i < discreteCount; i++) { | |
| 171 | + discreteValues[i] = discretesResponse.getDiscreteStatus(i) ? 1 : 0; | |
| 172 | + } | |
| 173 | + return discreteValues; | |
| 174 | + case 3: // ReadHoldingRegisters | |
| 175 | + ReadMultipleRegistersResponse holdingResponse = (ReadMultipleRegistersResponse) response; | |
| 176 | + InputRegister[] holdingRegisters = holdingResponse.getRegisters(); | |
| 177 | + int[] holdingValues = new int[holdingRegisters.length]; | |
| 178 | + for (int i = 0; i < holdingRegisters.length; i++) { | |
| 179 | + holdingValues[i] = holdingRegisters[i].getValue(); | |
| 180 | + } | |
| 181 | + return holdingValues; | |
| 182 | + case 4: // ReadInputRegisters | |
| 183 | + ReadInputRegistersResponse inputResponse = (ReadInputRegistersResponse) response; | |
| 184 | + InputRegister[] inputRegisters = inputResponse.getRegisters(); | |
| 185 | + int[] inputValues = new int[inputRegisters.length]; | |
| 186 | + for (int i = 0; i < inputRegisters.length; i++) { | |
| 187 | + inputValues[i] = inputRegisters[i].getValue(); | |
| 188 | + } | |
| 189 | + return inputValues; | |
| 190 | + default: | |
| 191 | + throw new IllegalArgumentException("不支持的功能码: " + functionCode); | |
| 192 | + } | |
| 193 | + } | |
| 194 | + | |
| 195 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpConfigCacheService.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.pojo.CommonResult; | |
| 4 | +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; | |
| 5 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; | |
| 6 | +import lombok.RequiredArgsConstructor; | |
| 7 | +import lombok.extern.slf4j.Slf4j; | |
| 8 | +import org.springframework.stereotype.Service; | |
| 9 | + | |
| 10 | +import java.util.*; | |
| 11 | +import java.util.concurrent.ConcurrentHashMap; | |
| 12 | +import java.util.function.Consumer; | |
| 13 | + | |
| 14 | +// TODO @AI:是不是 1、2、3 注释可以合并下; | |
| 15 | +/** | |
| 16 | + * IoT Modbus TCP 配置缓存服务 | |
| 17 | + * | |
| 18 | + * 负责: | |
| 19 | + * 1. 从 biz 拉取 Modbus 设备配置 | |
| 20 | + * 2. 缓存配置数据 | |
| 21 | + * 3. 检测配置变更 | |
| 22 | + * | |
| 23 | + * @author 芋道源码 | |
| 24 | + */ | |
| 25 | +// TODO @AI:希望它的初始化,在 configuration 里; | |
| 26 | +@Service | |
| 27 | +@RequiredArgsConstructor | |
| 28 | +@Slf4j | |
| 29 | +public class IotModbusTcpConfigCacheService { | |
| 30 | + | |
| 31 | + private final IotDeviceCommonApi deviceApi; | |
| 32 | + | |
| 33 | + /** | |
| 34 | + * 配置缓存:deviceId -> 配置 | |
| 35 | + */ | |
| 36 | + private final Map<Long, IotModbusDeviceConfigRespDTO> configCache = new ConcurrentHashMap<>(); | |
| 37 | + | |
| 38 | + /** | |
| 39 | + * 已知的设备 ID 集合 | |
| 40 | + */ | |
| 41 | + private final Set<Long> knownDeviceIds = ConcurrentHashMap.newKeySet(); | |
| 42 | + | |
| 43 | + /** | |
| 44 | + * 刷新配置 | |
| 45 | + * | |
| 46 | + * @return 最新的配置列表 | |
| 47 | + */ | |
| 48 | + public List<IotModbusDeviceConfigRespDTO> refreshConfig() { | |
| 49 | + try { | |
| 50 | + // 1. 从远程获取配置 | |
| 51 | + CommonResult<List<IotModbusDeviceConfigRespDTO>> result = deviceApi.getEnabledModbusDeviceConfigs(); | |
| 52 | + if (result == null || !result.isSuccess() || result.getData() == null) { | |
| 53 | + log.warn("[refreshConfig][获取 Modbus 配置失败: {}]", result); | |
| 54 | + return new ArrayList<>(configCache.values()); | |
| 55 | + } | |
| 56 | + List<IotModbusDeviceConfigRespDTO> configs = result.getData(); | |
| 57 | + | |
| 58 | + // 2. 更新缓存 | |
| 59 | + for (IotModbusDeviceConfigRespDTO config : configs) { | |
| 60 | + configCache.put(config.getDeviceId(), config); | |
| 61 | + knownDeviceIds.add(config.getDeviceId()); | |
| 62 | + } | |
| 63 | + return configs; | |
| 64 | + } catch (Exception e) { | |
| 65 | + log.error("[refreshConfig][刷新配置失败]", e); | |
| 66 | + return new ArrayList<>(configCache.values()); | |
| 67 | + } | |
| 68 | + } | |
| 69 | + | |
| 70 | + /** | |
| 71 | + * 获取设备配置 | |
| 72 | + * | |
| 73 | + * @param deviceId 设备 ID | |
| 74 | + * @return 配置 | |
| 75 | + */ | |
| 76 | + public IotModbusDeviceConfigRespDTO getConfig(Long deviceId) { | |
| 77 | + return configCache.get(deviceId); | |
| 78 | + } | |
| 79 | + | |
| 80 | + /** | |
| 81 | + * 清理已删除设备的资源 | |
| 82 | + * | |
| 83 | + * @param currentConfigs 当前有效的配置列表 | |
| 84 | + * @param cleanupAction 清理动作 | |
| 85 | + */ | |
| 86 | + public void cleanupRemovedDevices(List<IotModbusDeviceConfigRespDTO> currentConfigs, Consumer<Long> cleanupAction) { | |
| 87 | + // 1.1 获取当前有效的设备 ID | |
| 88 | + // TODO @AI:convertSet 简化; | |
| 89 | + Set<Long> currentDeviceIds = new HashSet<>(); | |
| 90 | + for (IotModbusDeviceConfigRespDTO config : currentConfigs) { | |
| 91 | + currentDeviceIds.add(config.getDeviceId()); | |
| 92 | + } | |
| 93 | + // 1.2 找出已删除的设备 | |
| 94 | + Set<Long> removedDeviceIds = new HashSet<>(knownDeviceIds); | |
| 95 | + removedDeviceIds.removeAll(currentDeviceIds); | |
| 96 | + | |
| 97 | + // 2. 清理已删除设备 | |
| 98 | + for (Long deviceId : removedDeviceIds) { | |
| 99 | + log.info("[cleanupRemovedDevices][清理已删除设备: {}]", deviceId); | |
| 100 | + configCache.remove(deviceId); | |
| 101 | + knownDeviceIds.remove(deviceId); | |
| 102 | + cleanupAction.accept(deviceId); | |
| 103 | + } | |
| 104 | + } | |
| 105 | + | |
| 106 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpConnectionManager.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; | |
| 4 | +import com.ghgande.j2mod.modbus.net.TCPMasterConnection; | |
| 5 | +import io.vertx.core.Context; | |
| 6 | +import io.vertx.core.Future; | |
| 7 | +import io.vertx.core.Vertx; | |
| 8 | +import lombok.Data; | |
| 9 | +import lombok.RequiredArgsConstructor; | |
| 10 | +import lombok.extern.slf4j.Slf4j; | |
| 11 | +import org.redisson.api.RLock; | |
| 12 | +import org.redisson.api.RedissonClient; | |
| 13 | +import org.springframework.stereotype.Component; | |
| 14 | + | |
| 15 | +import java.net.InetAddress; | |
| 16 | +import java.util.Map; | |
| 17 | +import java.util.concurrent.ConcurrentHashMap; | |
| 18 | + | |
| 19 | +/** | |
| 20 | + * IoT Modbus TCP 连接管理器 | |
| 21 | + * | |
| 22 | + * 负责: | |
| 23 | + * 1. 管理 TCP 连接(相同 ip:port 共用连接) | |
| 24 | + * 2. 分布式锁管理(连接级别) | |
| 25 | + * 3. 连接重试和故障恢复 | |
| 26 | + * | |
| 27 | + * @author 芋道源码 | |
| 28 | + */ | |
| 29 | +// TODO @AI:希望它的初始化,在 configuration 里; | |
| 30 | +@Component | |
| 31 | +@RequiredArgsConstructor | |
| 32 | +@Slf4j | |
| 33 | +public class IotModbusTcpConnectionManager { | |
| 34 | + | |
| 35 | + // TODO @AI:iot:modbus-tcp:connection: | |
| 36 | + private static final String LOCK_KEY_PREFIX = "iot:modbus:connection:"; | |
| 37 | + | |
| 38 | + private final RedissonClient redissonClient; | |
| 39 | + private final Vertx vertx; | |
| 40 | + | |
| 41 | + /** | |
| 42 | + * 连接池:key = ip:port | |
| 43 | + */ | |
| 44 | + private final Map<String, ModbusConnection> connectionPool = new ConcurrentHashMap<>(); | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * 设备 ID 到连接 key 的映射 | |
| 48 | + */ | |
| 49 | + private final Map<Long, String> deviceConnectionMap = new ConcurrentHashMap<>(); | |
| 50 | + | |
| 51 | + /** | |
| 52 | + * 确保连接存在 | |
| 53 | + * | |
| 54 | + * @param config 设备配置 | |
| 55 | + */ | |
| 56 | + public void ensureConnection(IotModbusDeviceConfigRespDTO config) { | |
| 57 | + // 1. 记录设备与连接的关系 | |
| 58 | + String connectionKey = buildConnectionKey(config.getIp(), config.getPort()); | |
| 59 | + deviceConnectionMap.put(config.getDeviceId(), connectionKey); | |
| 60 | + | |
| 61 | + // 2. 情况一:连接已存在,添加设备引用 | |
| 62 | + ModbusConnection connection = connectionPool.get(connectionKey); | |
| 63 | + if (connection != null) { | |
| 64 | + // 添加设备引用 | |
| 65 | + connection.addDevice(config.getDeviceId(), config.getSlaveId()); | |
| 66 | + // 更新连接参数(取最小值) | |
| 67 | + // TODO @AI:(不确定)如果后续最小值被移除后,是不是无法灰度到上一个最小值? | |
| 68 | + connection.updateParams(config.getTimeout(), config.getRetryInterval()); | |
| 69 | + return; | |
| 70 | + } | |
| 71 | + | |
| 72 | + // 3. 情况二:连接不存在,创建新连接 | |
| 73 | + // 3.1 尝试获取分布式锁 | |
| 74 | + RLock lock = redissonClient.getLock(LOCK_KEY_PREFIX + connectionKey); | |
| 75 | + if (!lock.tryLock()) { | |
| 76 | + log.debug("[ensureConnection][获取锁失败, 由其他节点负责: {}]", connectionKey); | |
| 77 | + return; | |
| 78 | + } | |
| 79 | + // 3.2 创建新连接 | |
| 80 | + try { | |
| 81 | + connection = createConnection(config, lock); | |
| 82 | + connectionPool.put(connectionKey, connection); | |
| 83 | + log.info("[ensureConnection][创建 Modbus 连接成功: {}]", connectionKey); | |
| 84 | + } catch (Exception e) { | |
| 85 | + log.error("[ensureConnection][创建 Modbus 连接失败: {}]", connectionKey, e); | |
| 86 | + lock.unlock(); | |
| 87 | + } | |
| 88 | + } | |
| 89 | + | |
| 90 | + /** | |
| 91 | + * 创建 Modbus TCP 连接 | |
| 92 | + */ | |
| 93 | + private ModbusConnection createConnection(IotModbusDeviceConfigRespDTO config, RLock lock) throws Exception { | |
| 94 | + // 创建 TCP 连接 | |
| 95 | + // TODO @AI:需要重连么? | |
| 96 | + TCPMasterConnection tcpConnection = new TCPMasterConnection(InetAddress.getByName(config.getIp())); | |
| 97 | + tcpConnection.setPort(config.getPort()); | |
| 98 | + tcpConnection.setTimeout(config.getTimeout()); | |
| 99 | + tcpConnection.connect(); | |
| 100 | + | |
| 101 | + // 创建 Modbus 连接对象 | |
| 102 | + ModbusConnection connection = new ModbusConnection(); | |
| 103 | + // TODO @AI:链式调用,简化下; | |
| 104 | + connection.setConnectionKey(buildConnectionKey(config.getIp(), config.getPort())); | |
| 105 | + connection.setTcpConnection(tcpConnection); | |
| 106 | + connection.setLock(lock); | |
| 107 | + connection.setTimeout(config.getTimeout()); | |
| 108 | + connection.setRetryInterval(config.getRetryInterval()); | |
| 109 | + connection.setContext(vertx.getOrCreateContext()); | |
| 110 | + connection.addDevice(config.getDeviceId(), config.getSlaveId()); | |
| 111 | + return connection; | |
| 112 | + } | |
| 113 | + | |
| 114 | + /** | |
| 115 | + * 获取连接 | |
| 116 | + */ | |
| 117 | + public ModbusConnection getConnection(Long deviceId) { | |
| 118 | + String connectionKey = deviceConnectionMap.get(deviceId); | |
| 119 | + if (connectionKey == null) { | |
| 120 | + return null; | |
| 121 | + } | |
| 122 | + return connectionPool.get(connectionKey); | |
| 123 | + } | |
| 124 | + | |
| 125 | + /** | |
| 126 | + * 获取设备的 slave ID | |
| 127 | + */ | |
| 128 | + public Integer getSlaveId(Long deviceId) { | |
| 129 | + ModbusConnection connection = getConnection(deviceId); | |
| 130 | + if (connection == null) { | |
| 131 | + return null; | |
| 132 | + } | |
| 133 | + return connection.getSlaveId(deviceId); | |
| 134 | + } | |
| 135 | + | |
| 136 | + /** | |
| 137 | + * 移除设备 | |
| 138 | + */ | |
| 139 | + public void removeDevice(Long deviceId) { | |
| 140 | + // 1. 移除设备引用 | |
| 141 | + String connectionKey = deviceConnectionMap.remove(deviceId); | |
| 142 | + if (connectionKey == null) { | |
| 143 | + return; | |
| 144 | + } | |
| 145 | + ModbusConnection connection = connectionPool.get(connectionKey); | |
| 146 | + if (connection == null) { | |
| 147 | + return; | |
| 148 | + } | |
| 149 | + connection.removeDevice(deviceId); | |
| 150 | + | |
| 151 | + // 2. 如果没有设备引用了,关闭连接 | |
| 152 | + if (connection.getDeviceCount() == 0) { | |
| 153 | + closeConnection(connectionKey); | |
| 154 | + } | |
| 155 | + } | |
| 156 | + | |
| 157 | + /** | |
| 158 | + * 关闭指定连接 | |
| 159 | + */ | |
| 160 | + private void closeConnection(String connectionKey) { | |
| 161 | + ModbusConnection connection = connectionPool.remove(connectionKey); | |
| 162 | + if (connection == null) { | |
| 163 | + return; | |
| 164 | + } | |
| 165 | + | |
| 166 | + try { | |
| 167 | + if (connection.getTcpConnection() != null) { | |
| 168 | + connection.getTcpConnection().close(); | |
| 169 | + } | |
| 170 | + // TODO @AI:(不确定)是不是要当前线程?还是当前进程就 ok 了。 | |
| 171 | + if (connection.getLock() != null && connection.getLock().isHeldByCurrentThread()) { | |
| 172 | + connection.getLock().unlock(); | |
| 173 | + } | |
| 174 | + log.info("[closeConnection][关闭 Modbus 连接: {}]", connectionKey); | |
| 175 | + } catch (Exception e) { | |
| 176 | + log.error("[closeConnection][关闭连接失败: {}]", connectionKey, e); | |
| 177 | + } | |
| 178 | + } | |
| 179 | + | |
| 180 | + /** | |
| 181 | + * 关闭所有连接 | |
| 182 | + */ | |
| 183 | + public void closeAll() { | |
| 184 | + for (String connectionKey : connectionPool.keySet()) { | |
| 185 | + closeConnection(connectionKey); | |
| 186 | + } | |
| 187 | + deviceConnectionMap.clear(); | |
| 188 | + } | |
| 189 | + | |
| 190 | + private String buildConnectionKey(String ip, Integer port) { | |
| 191 | + return ip + ":" + port; | |
| 192 | + } | |
| 193 | + | |
| 194 | + /** | |
| 195 | + * Modbus 连接信息 | |
| 196 | + */ | |
| 197 | + @Data | |
| 198 | + public static class ModbusConnection { | |
| 199 | + | |
| 200 | + private String connectionKey; | |
| 201 | + private TCPMasterConnection tcpConnection; | |
| 202 | + private RLock lock; | |
| 203 | + private Integer timeout; | |
| 204 | + private Integer retryInterval; | |
| 205 | + private Context context; | |
| 206 | + | |
| 207 | + /** | |
| 208 | + * 设备 ID 到 slave ID 的映射 | |
| 209 | + */ | |
| 210 | + private final Map<Long, Integer> deviceSlaveMap = new ConcurrentHashMap<>(); | |
| 211 | + | |
| 212 | + public void addDevice(Long deviceId, Integer slaveId) { | |
| 213 | + deviceSlaveMap.put(deviceId, slaveId); | |
| 214 | + } | |
| 215 | + | |
| 216 | + public void removeDevice(Long deviceId) { | |
| 217 | + deviceSlaveMap.remove(deviceId); | |
| 218 | + } | |
| 219 | + | |
| 220 | + public int getDeviceCount() { | |
| 221 | + return deviceSlaveMap.size(); | |
| 222 | + } | |
| 223 | + | |
| 224 | + public Integer getSlaveId(Long deviceId) { | |
| 225 | + return deviceSlaveMap.get(deviceId); | |
| 226 | + } | |
| 227 | + | |
| 228 | + public void updateParams(Integer timeout, Integer retryInterval) { | |
| 229 | + // 取最小值 | |
| 230 | + if (timeout != null && (this.timeout == null || timeout < this.timeout)) { | |
| 231 | + this.timeout = timeout; | |
| 232 | + } | |
| 233 | + if (retryInterval != null && (this.retryInterval == null || retryInterval < this.retryInterval)) { | |
| 234 | + this.retryInterval = retryInterval; | |
| 235 | + } | |
| 236 | + } | |
| 237 | + | |
| 238 | + /** | |
| 239 | + * 执行 Modbus 读取操作(阻塞方式,在 Vert.x worker 线程执行) | |
| 240 | + */ | |
| 241 | + public <T> Future<T> executeBlocking(java.util.function.Function<TCPMasterConnection, T> operation) { | |
| 242 | + // TODO @AI:executeBlocking 方法,已经废弃了,看看要不要替换。 | |
| 243 | + // TODO @AI:疑问。这里会不会线程阻塞?多个设备之间,担心性能; | |
| 244 | + return context.executeBlocking(promise -> { | |
| 245 | + try { | |
| 246 | + T result = operation.apply(tcpConnection); | |
| 247 | + promise.complete(result); | |
| 248 | + } catch (Exception e) { | |
| 249 | + promise.fail(e); | |
| 250 | + } | |
| 251 | + }, true); // ordered=true 保证同一连接串行执行 | |
| 252 | + } | |
| 253 | + } | |
| 254 | + | |
| 255 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpDownstreamHandler.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; | |
| 4 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; | |
| 5 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 6 | +import lombok.RequiredArgsConstructor; | |
| 7 | +import lombok.extern.slf4j.Slf4j; | |
| 8 | +import org.springframework.stereotype.Component; | |
| 9 | + | |
| 10 | +import java.util.Map; | |
| 11 | + | |
| 12 | +// TODO @AI:看看注释能不能优化下; | |
| 13 | +/** | |
| 14 | + * IoT Modbus TCP 下行消息处理器 | |
| 15 | + * | |
| 16 | + * 负责: | |
| 17 | + * 1. 处理属性设置消息(thing.service.property.set) | |
| 18 | + * 2. 将属性值转换为 Modbus 原始值 | |
| 19 | + * 3. 执行 Modbus 写入操作 | |
| 20 | + * | |
| 21 | + * @author 芋道源码 | |
| 22 | + */ | |
| 23 | +// TODO @AI:希望它的初始化,在 configuration 里; | |
| 24 | +@Component | |
| 25 | +@RequiredArgsConstructor | |
| 26 | +@Slf4j | |
| 27 | +public class IotModbusTcpDownstreamHandler { | |
| 28 | + | |
| 29 | + private final IotModbusTcpConnectionManager connectionManager; | |
| 30 | + private final IotModbusTcpClient modbusClient; | |
| 31 | + private final IotModbusDataConverter dataConverter; | |
| 32 | + private final IotModbusTcpConfigCacheService configCacheService; | |
| 33 | + | |
| 34 | + /** | |
| 35 | + * 处理下行消息 | |
| 36 | + */ | |
| 37 | + @SuppressWarnings("unchecked") | |
| 38 | + public void handle(IotDeviceMessage message) { | |
| 39 | + // 1.1 检查是否是属性设置消息 | |
| 40 | + if (!"thing.service.property.set".equals(message.getMethod())) { | |
| 41 | + log.debug("[handle][忽略非属性设置消息: {}]", message.getMethod()); | |
| 42 | + return; | |
| 43 | + } | |
| 44 | + // 1.2 获取设备配置 | |
| 45 | + IotModbusDeviceConfigRespDTO config = configCacheService.getConfig(message.getDeviceId()); | |
| 46 | + if (config == null) { | |
| 47 | + log.warn("[handle][设备 {} 没有 Modbus 配置]", message.getDeviceId()); | |
| 48 | + return; | |
| 49 | + } | |
| 50 | + | |
| 51 | + // 2. 解析属性值并写入 | |
| 52 | + Object params = message.getParams(); | |
| 53 | + if (!(params instanceof Map)) { | |
| 54 | + log.warn("[handle][params 不是 Map 类型: {}]", params); | |
| 55 | + return; | |
| 56 | + } | |
| 57 | + Map<String, Object> propertyMap = (Map<String, Object>) params; | |
| 58 | + for (Map.Entry<String, Object> entry : propertyMap.entrySet()) { | |
| 59 | + String identifier = entry.getKey(); | |
| 60 | + Object value = entry.getValue(); | |
| 61 | + // 2.1.1 查找对应的点位配置 | |
| 62 | + IotModbusPointRespDTO point = findPoint(config, identifier); | |
| 63 | + if (point == null) { | |
| 64 | + log.warn("[handle][设备 {} 没有点位配置: {}]", message.getDeviceId(), identifier); | |
| 65 | + continue; | |
| 66 | + } | |
| 67 | + // 2.1.2 检查是否支持写操作 | |
| 68 | + if (!isWritable(point.getFunctionCode())) { | |
| 69 | + log.warn("[handle][点位 {} 不支持写操作, 功能码={}]", identifier, point.getFunctionCode()); | |
| 70 | + continue; | |
| 71 | + } | |
| 72 | + | |
| 73 | + // 2.2 执行写入 | |
| 74 | + writeProperty(config, point, value); | |
| 75 | + } | |
| 76 | + } | |
| 77 | + | |
| 78 | + /** | |
| 79 | + * 写入属性值 | |
| 80 | + */ | |
| 81 | + private void writeProperty(IotModbusDeviceConfigRespDTO config, IotModbusPointRespDTO point, Object value) { | |
| 82 | + // 1.1 获取连接 | |
| 83 | + IotModbusTcpConnectionManager.ModbusConnection connection = connectionManager.getConnection(config.getDeviceId()); | |
| 84 | + if (connection == null) { | |
| 85 | + log.warn("[writeProperty][设备 {} 没有连接]", config.getDeviceId()); | |
| 86 | + return; | |
| 87 | + } | |
| 88 | + // 1.2 获取 slave ID | |
| 89 | + Integer slaveId = connectionManager.getSlaveId(config.getDeviceId()); | |
| 90 | + if (slaveId == null) { | |
| 91 | + log.warn("[writeProperty][设备 {} 没有 slaveId]", config.getDeviceId()); | |
| 92 | + return; | |
| 93 | + } | |
| 94 | + | |
| 95 | + // 2.1 转换属性值为原始值 | |
| 96 | + int[] rawValues = dataConverter.convertToRawValues(value, point); | |
| 97 | + // 2.2 执行 Modbus 写入 | |
| 98 | + modbusClient.write(connection, slaveId, point, rawValues) | |
| 99 | + .onSuccess(success -> log.info("[writeProperty][写入成功, deviceId={}, identifier={}, value={}]", | |
| 100 | + config.getDeviceId(), point.getIdentifier(), value)) | |
| 101 | + .onFailure(e -> log.error("[writeProperty][写入失败, deviceId={}, identifier={}]", | |
| 102 | + config.getDeviceId(), point.getIdentifier(), e)); | |
| 103 | + } | |
| 104 | + | |
| 105 | + /** | |
| 106 | + * 查找点位配置 | |
| 107 | + */ | |
| 108 | + private IotModbusPointRespDTO findPoint(IotModbusDeviceConfigRespDTO config, String identifier) { | |
| 109 | + if (config.getPoints() == null) { | |
| 110 | + return null; | |
| 111 | + } | |
| 112 | + // TODO @AI:hutool findOne? | |
| 113 | + return config.getPoints().stream() | |
| 114 | + .filter(p -> identifier.equals(p.getIdentifier())) | |
| 115 | + .findFirst() | |
| 116 | + .orElse(null); | |
| 117 | + } | |
| 118 | + | |
| 119 | + /** | |
| 120 | + * 检查功能码是否支持写操作 | |
| 121 | + */ | |
| 122 | + private boolean isWritable(Integer functionCode) { | |
| 123 | + // TODO @AI:能不能通过 枚举优化下? | |
| 124 | + // 功能码 1(ReadCoils)和 3(ReadHoldingRegisters)支持写操作 | |
| 125 | + return functionCode != null && (functionCode == 1 || functionCode == 3); | |
| 126 | + } | |
| 127 | + | |
| 128 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpDownstreamSubscriber.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; | |
| 4 | +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; | |
| 5 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 6 | +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; | |
| 7 | +import jakarta.annotation.PostConstruct; | |
| 8 | +import lombok.RequiredArgsConstructor; | |
| 9 | +import lombok.extern.slf4j.Slf4j; | |
| 10 | + | |
| 11 | +/** | |
| 12 | + * IoT Modbus TCP 下行消息订阅器 | |
| 13 | + * | |
| 14 | + * 负责: | |
| 15 | + * 1. 订阅消息总线的下行消息 | |
| 16 | + * 2. 将属性设置消息转发给下行处理器 | |
| 17 | + * | |
| 18 | + * @author 芋道源码 | |
| 19 | + */ | |
| 20 | +// TODO @AI:希望它的初始化,在 configuration 里; | |
| 21 | +@RequiredArgsConstructor | |
| 22 | +@Slf4j | |
| 23 | +public class IotModbusTcpDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> { | |
| 24 | + | |
| 25 | + private final IotModbusTcpUpstreamProtocol upstreamProtocol; | |
| 26 | + private final IotModbusTcpDownstreamHandler downstreamHandler; | |
| 27 | + private final IotMessageBus messageBus; | |
| 28 | + | |
| 29 | + @PostConstruct | |
| 30 | + public void subscribe() { | |
| 31 | + messageBus.register(this); | |
| 32 | + log.info("[subscribe][Modbus TCP 下行消息订阅器已启动, topic={}]", getTopic()); | |
| 33 | + } | |
| 34 | + | |
| 35 | + @Override | |
| 36 | + public String getTopic() { | |
| 37 | + return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(upstreamProtocol.getServerId()); | |
| 38 | + } | |
| 39 | + | |
| 40 | + @Override | |
| 41 | + public String getGroup() { | |
| 42 | + return getTopic(); // 点对点消费 | |
| 43 | + } | |
| 44 | + | |
| 45 | + @Override | |
| 46 | + public void onMessage(IotDeviceMessage message) { | |
| 47 | + log.debug("[onMessage][收到下行消息: {}]", message); | |
| 48 | + try { | |
| 49 | + downstreamHandler.handle(message); | |
| 50 | + } catch (Exception e) { | |
| 51 | + log.error("[onMessage][处理下行消息失败]", e); | |
| 52 | + } | |
| 53 | + } | |
| 54 | + | |
| 55 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpPollScheduler.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; | |
| 4 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; | |
| 5 | +import io.vertx.core.Vertx; | |
| 6 | +import lombok.RequiredArgsConstructor; | |
| 7 | +import lombok.extern.slf4j.Slf4j; | |
| 8 | +import org.springframework.stereotype.Component; | |
| 9 | + | |
| 10 | +import java.util.ArrayList; | |
| 11 | +import java.util.List; | |
| 12 | +import java.util.Map; | |
| 13 | +import java.util.concurrent.ConcurrentHashMap; | |
| 14 | + | |
| 15 | +/** | |
| 16 | + * IoT Modbus TCP 轮询调度器 | |
| 17 | + * | |
| 18 | + * 负责: | |
| 19 | + * 1. 管理每个点位的轮询定时器 | |
| 20 | + * 2. 调度 Modbus 读取任务 | |
| 21 | + * 3. 处理读取结果并上报 | |
| 22 | + * | |
| 23 | + * @author 芋道源码 | |
| 24 | + */ | |
| 25 | +// TODO @AI:希望它的初始化,在 configuration 里; | |
| 26 | +@Component | |
| 27 | +@RequiredArgsConstructor | |
| 28 | +@Slf4j | |
| 29 | +public class IotModbusTcpPollScheduler { | |
| 30 | + | |
| 31 | + private final Vertx vertx; | |
| 32 | + private final IotModbusTcpConnectionManager connectionManager; | |
| 33 | + private final IotModbusTcpClient modbusClient; | |
| 34 | + private final IotModbusTcpUpstreamHandler upstreamHandler; | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * 设备的定时器列表:deviceId -> timerId 列表 | |
| 38 | + */ | |
| 39 | + private final Map<Long, List<Long>> deviceTimers = new ConcurrentHashMap<>(); | |
| 40 | + | |
| 41 | + /** | |
| 42 | + * 更新轮询任务 | |
| 43 | + */ | |
| 44 | + public void updatePolling(IotModbusDeviceConfigRespDTO config) { | |
| 45 | + Long deviceId = config.getDeviceId(); | |
| 46 | + | |
| 47 | + // 1. 停止旧的轮询任务 | |
| 48 | + stopPolling(deviceId); | |
| 49 | + | |
| 50 | + // 2.1 为每个点位创建新的轮询任务 | |
| 51 | + List<Long> timerIds = new ArrayList<>(); | |
| 52 | + // TODO @AI:if return 简化;上面的 size 加下;config.getPoints() | |
| 53 | + if (config.getPoints() != null) { | |
| 54 | + for (IotModbusPointRespDTO point : config.getPoints()) { | |
| 55 | + Long timerId = createPollTimer(config, point); | |
| 56 | + if (timerId != null) { | |
| 57 | + timerIds.add(timerId); | |
| 58 | + } | |
| 59 | + } | |
| 60 | + } | |
| 61 | + // 2.2 记录定时器 | |
| 62 | + // TODO @AI:CollUtil.isNotEmpty;if return 简化; | |
| 63 | + if (!timerIds.isEmpty()) { | |
| 64 | + deviceTimers.put(deviceId, timerIds); | |
| 65 | + log.debug("[updatePolling][设备 {} 创建了 {} 个轮询定时器]", deviceId, timerIds.size()); | |
| 66 | + } | |
| 67 | + } | |
| 68 | + | |
| 69 | + /** | |
| 70 | + * 创建轮询定时器 | |
| 71 | + */ | |
| 72 | + private Long createPollTimer(IotModbusDeviceConfigRespDTO config, IotModbusPointRespDTO point) { | |
| 73 | + if (point.getPollInterval() == null || point.getPollInterval() <= 0) { | |
| 74 | + return null; | |
| 75 | + } | |
| 76 | + return vertx.setPeriodic(point.getPollInterval(), timerId -> { | |
| 77 | + try { | |
| 78 | + pollPoint(config, point); | |
| 79 | + } catch (Exception e) { | |
| 80 | + log.error("[createPollTimer][轮询点位失败, deviceId={}, identifier={}]", | |
| 81 | + config.getDeviceId(), point.getIdentifier(), e); | |
| 82 | + } | |
| 83 | + }); | |
| 84 | + } | |
| 85 | + | |
| 86 | + /** | |
| 87 | + * 轮询单个点位 | |
| 88 | + */ | |
| 89 | + private void pollPoint(IotModbusDeviceConfigRespDTO config, IotModbusPointRespDTO point) { | |
| 90 | + // 1.1 获取连接 | |
| 91 | + IotModbusTcpConnectionManager.ModbusConnection connection = connectionManager.getConnection(config.getDeviceId()); | |
| 92 | + if (connection == null) { | |
| 93 | + log.warn("[pollPoint][设备 {} 没有连接]", config.getDeviceId()); | |
| 94 | + return; | |
| 95 | + } | |
| 96 | + // 1.2 获取 slave ID | |
| 97 | + Integer slaveId = connectionManager.getSlaveId(config.getDeviceId()); | |
| 98 | + if (slaveId == null) { | |
| 99 | + log.warn("[pollPoint][设备 {} 没有 slaveId]", config.getDeviceId()); | |
| 100 | + return; | |
| 101 | + } | |
| 102 | + | |
| 103 | + // 2. 执行 Modbus 读取 | |
| 104 | + // TODO @AI:超时时间,有实现么??? | |
| 105 | + modbusClient.read(connection, slaveId, point) | |
| 106 | + .onSuccess(rawValue -> upstreamHandler.handleReadResult(config, point, rawValue)) | |
| 107 | + .onFailure(e -> log.error("[pollPoint][读取点位失败, deviceId={}, identifier={}]", | |
| 108 | + config.getDeviceId(), point.getIdentifier(), e)); | |
| 109 | + } | |
| 110 | + | |
| 111 | + /** | |
| 112 | + * 停止设备的轮询 | |
| 113 | + */ | |
| 114 | + public void stopPolling(Long deviceId) { | |
| 115 | + List<Long> timerIds = deviceTimers.remove(deviceId); | |
| 116 | + // TODO @AI:CollUtil.isNotEmpty;并且 if return; | |
| 117 | + if (timerIds != null) { | |
| 118 | + for (Long timerId : timerIds) { | |
| 119 | + vertx.cancelTimer(timerId); | |
| 120 | + } | |
| 121 | + log.debug("[stopPolling][设备 {} 停止了 {} 个轮询定时器]", deviceId, timerIds.size()); | |
| 122 | + } | |
| 123 | + } | |
| 124 | + | |
| 125 | + /** | |
| 126 | + * 停止所有轮询 | |
| 127 | + */ | |
| 128 | + public void stopAll() { | |
| 129 | + for (Long deviceId : deviceTimers.keySet()) { | |
| 130 | + stopPolling(deviceId); | |
| 131 | + } | |
| 132 | + } | |
| 133 | + | |
| 134 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpUpstreamHandler.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; | |
| 4 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusPointRespDTO; | |
| 5 | +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; | |
| 6 | +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; | |
| 7 | +import lombok.extern.slf4j.Slf4j; | |
| 8 | +import org.springframework.stereotype.Component; | |
| 9 | + | |
| 10 | +import java.util.HashMap; | |
| 11 | +import java.util.Map; | |
| 12 | + | |
| 13 | +/** | |
| 14 | + * IoT Modbus TCP 上行数据处理器 | |
| 15 | + * | |
| 16 | + * 负责: | |
| 17 | + * 1. 将 Modbus 读取的原始值转换为物模型属性值 | |
| 18 | + * 2. 构造属性上报消息 | |
| 19 | + * 3. 发送消息到消息总线 | |
| 20 | + * | |
| 21 | + * @author 芋道源码 | |
| 22 | + */ | |
| 23 | +// TODO @AI:希望它的初始化,在 configuration 里; | |
| 24 | +@Component | |
| 25 | +@Slf4j | |
| 26 | +public class IotModbusTcpUpstreamHandler { | |
| 27 | + | |
| 28 | + private final IotDeviceMessageService messageService; | |
| 29 | + private final IotModbusDataConverter dataConverter; | |
| 30 | + | |
| 31 | + private String serverId; | |
| 32 | + | |
| 33 | + public IotModbusTcpUpstreamHandler(IotDeviceMessageService messageService, | |
| 34 | + IotModbusDataConverter dataConverter) { | |
| 35 | + this.messageService = messageService; | |
| 36 | + this.dataConverter = dataConverter; | |
| 37 | + } | |
| 38 | + | |
| 39 | + // TODO @AI:lombok 简化 | |
| 40 | + /** | |
| 41 | + * 设置 serverId | |
| 42 | + * | |
| 43 | + * @param serverId 服务器 ID | |
| 44 | + */ | |
| 45 | + public void setServerId(String serverId) { | |
| 46 | + this.serverId = serverId; | |
| 47 | + } | |
| 48 | + | |
| 49 | + /** | |
| 50 | + * 处理 Modbus 读取结果 | |
| 51 | + * | |
| 52 | + * @param config 设备配置 | |
| 53 | + * @param point 点位配置 | |
| 54 | + * @param rawValue 原始值(int 数组) | |
| 55 | + */ | |
| 56 | + public void handleReadResult(IotModbusDeviceConfigRespDTO config, | |
| 57 | + IotModbusPointRespDTO point, | |
| 58 | + int[] rawValue) { | |
| 59 | + try { | |
| 60 | + // 1.1 转换原始值为物模型属性值 | |
| 61 | + Object convertedValue = dataConverter.convertToPropertyValue(rawValue, point); | |
| 62 | + log.debug("[handleReadResult][设备={}, 属性={}, 原始值={}, 转换值={}]", | |
| 63 | + config.getDeviceId(), point.getIdentifier(), rawValue, convertedValue); | |
| 64 | + // 1.2 构造属性上报消息 | |
| 65 | + Map<String, Object> params = new HashMap<>(); | |
| 66 | + params.put(point.getIdentifier(), convertedValue); | |
| 67 | + IotDeviceMessage message = IotDeviceMessage.requestOf("thing.event.property.post", params); | |
| 68 | + | |
| 69 | + // 2. 发送到消息总线 | |
| 70 | + messageService.sendDeviceMessage(message, config.getProductKey(), | |
| 71 | + config.getDeviceName(), serverId); | |
| 72 | + } catch (Exception e) { | |
| 73 | + log.error("[handleReadResult][处理读取结果失败, deviceId={}, identifier={}]", | |
| 74 | + config.getDeviceId(), point.getIdentifier(), e); | |
| 75 | + } | |
| 76 | + } | |
| 77 | + | |
| 78 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/modbustcp/IotModbusTcpUpstreamProtocol.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.gateway.protocol.modbustcp; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; | |
| 4 | +import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; | |
| 5 | +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; | |
| 6 | +import io.vertx.core.Vertx; | |
| 7 | +import jakarta.annotation.PostConstruct; | |
| 8 | +import jakarta.annotation.PreDestroy; | |
| 9 | +import lombok.RequiredArgsConstructor; | |
| 10 | +import lombok.extern.slf4j.Slf4j; | |
| 11 | + | |
| 12 | +import java.util.List; | |
| 13 | +import java.util.UUID; | |
| 14 | +import java.util.concurrent.TimeUnit; | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * IoT Modbus TCP 上行协议 | |
| 18 | + * | |
| 19 | + * 负责: | |
| 20 | + * 1. 定时从 biz 拉取 Modbus 设备配置 | |
| 21 | + * 2. 管理 Modbus TCP 连接 | |
| 22 | + * 3. 调度轮询任务 | |
| 23 | + * 4. 处理采集数据上报 | |
| 24 | + * | |
| 25 | + * @author 芋道源码 | |
| 26 | + */ | |
| 27 | +// TODO @AI:希望它的初始化,在 configuration 里; | |
| 28 | +@RequiredArgsConstructor | |
| 29 | +@Slf4j | |
| 30 | +public class IotModbusTcpUpstreamProtocol { | |
| 31 | + | |
| 32 | + private final IotGatewayProperties.ModbusTcpProperties modbusTcpProperties; | |
| 33 | + private final IotDeviceMessageService messageService; | |
| 34 | + private final IotModbusTcpConnectionManager connectionManager; | |
| 35 | + private final IotModbusTcpPollScheduler pollScheduler; | |
| 36 | + private final IotModbusTcpConfigCacheService configCacheService; | |
| 37 | + private final IotModbusTcpUpstreamHandler upstreamHandler; | |
| 38 | + private final Vertx vertx; | |
| 39 | + | |
| 40 | + // TODO @AI:按照别的模块,生成 serverId; | |
| 41 | + /** | |
| 42 | + * 服务器 ID,用于标识当前网关实例 | |
| 43 | + */ | |
| 44 | + private final String serverId = UUID.randomUUID().toString(); | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * 配置刷新定时器 ID | |
| 48 | + */ | |
| 49 | + private Long configRefreshTimerId; | |
| 50 | + | |
| 51 | + @PostConstruct | |
| 52 | + public void start() { | |
| 53 | + log.info("[start][Modbus TCP 协议启动, serverId={}]", serverId); | |
| 54 | + | |
| 55 | + // 0. 设置 serverId 到上行处理器 | |
| 56 | + upstreamHandler.setServerId(serverId); | |
| 57 | + | |
| 58 | + // 1. 首次加载配置 | |
| 59 | + refreshConfig(); | |
| 60 | + | |
| 61 | + // 2. 启动配置定时刷新 | |
| 62 | + int refreshInterval = modbusTcpProperties.getConfigRefreshInterval(); | |
| 63 | + configRefreshTimerId = vertx.setPeriodic( | |
| 64 | + TimeUnit.SECONDS.toMillis(refreshInterval), | |
| 65 | + id -> refreshConfig() | |
| 66 | + ); | |
| 67 | + log.info("[start][配置刷新定时器已启动, 间隔={}秒]", refreshInterval); | |
| 68 | + } | |
| 69 | + | |
| 70 | + @PreDestroy | |
| 71 | + public void stop() { | |
| 72 | + log.info("[stop][Modbus TCP 协议停止]"); | |
| 73 | + | |
| 74 | + // 1. 取消配置刷新定时器 | |
| 75 | + if (configRefreshTimerId != null) { | |
| 76 | + vertx.cancelTimer(configRefreshTimerId); | |
| 77 | + } | |
| 78 | + | |
| 79 | + // 2. 停止轮询调度器 | |
| 80 | + pollScheduler.stopAll(); | |
| 81 | + | |
| 82 | + // 3. 关闭所有连接 | |
| 83 | + connectionManager.closeAll(); | |
| 84 | + } | |
| 85 | + | |
| 86 | + /** | |
| 87 | + * 刷新配置 | |
| 88 | + */ | |
| 89 | + private void refreshConfig() { | |
| 90 | + try { | |
| 91 | + // 1. 从 biz 拉取最新配置 | |
| 92 | + List<IotModbusDeviceConfigRespDTO> configs = configCacheService.refreshConfig(); | |
| 93 | + log.debug("[refreshConfig][获取到 {} 个 Modbus 设备配置]", configs.size()); | |
| 94 | + | |
| 95 | + // 2. 更新连接和轮询任务 | |
| 96 | + for (IotModbusDeviceConfigRespDTO config : configs) { | |
| 97 | + try { | |
| 98 | + // 2.1 确保连接存在 | |
| 99 | + connectionManager.ensureConnection(config); | |
| 100 | + // 2.2 更新轮询任务 | |
| 101 | + // TODO @AI:【重要】如果点位配置没变化,是不是不用 update? | |
| 102 | + pollScheduler.updatePolling(config); | |
| 103 | + } catch (Exception e) { | |
| 104 | + log.error("[refreshConfig][处理设备配置失败, deviceId={}]", config.getDeviceId(), e); | |
| 105 | + } | |
| 106 | + } | |
| 107 | + | |
| 108 | + // 3. 清理已删除设备的资源 | |
| 109 | + configCacheService.cleanupRemovedDevices(configs, deviceId -> { | |
| 110 | + pollScheduler.stopPolling(deviceId); | |
| 111 | + connectionManager.removeDevice(deviceId); | |
| 112 | + }); | |
| 113 | + } catch (Exception e) { | |
| 114 | + log.error("[refreshConfig][刷新配置失败]", e); | |
| 115 | + } | |
| 116 | + } | |
| 117 | + | |
| 118 | + // TODO @AI:是不是 lombok 简化 | |
| 119 | + public String getServerId() { | |
| 120 | + return serverId; | |
| 121 | + } | |
| 122 | + | |
| 123 | + // TODO @AI:不需要;可以删除; | |
| 124 | + public IotDeviceMessageService getMessageService() { | |
| 125 | + return messageService; | |
| 126 | + } | |
| 127 | + | |
| 128 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/service/device/remote/IotDeviceApiImpl.java
| ... | ... | @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; |
| 6 | 6 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; |
| 7 | 7 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO; |
| 8 | 8 | import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO; |
| 9 | +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigRespDTO; | |
| 9 | 10 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; |
| 10 | 11 | import jakarta.annotation.PostConstruct; |
| 11 | 12 | import jakarta.annotation.Resource; |
| ... | ... | @@ -18,6 +19,8 @@ import org.springframework.http.ResponseEntity; |
| 18 | 19 | import org.springframework.stereotype.Service; |
| 19 | 20 | import org.springframework.web.client.RestTemplate; |
| 20 | 21 | |
| 22 | +import java.util.List; | |
| 23 | + | |
| 21 | 24 | import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; |
| 22 | 25 | |
| 23 | 26 | /** |
| ... | ... | @@ -33,6 +36,7 @@ public class IotDeviceApiImpl implements IotDeviceCommonApi { |
| 33 | 36 | private IotGatewayProperties gatewayProperties; |
| 34 | 37 | |
| 35 | 38 | private RestTemplate restTemplate; |
| 39 | + private RestTemplate modbusRestTemplate; | |
| 36 | 40 | |
| 37 | 41 | @PostConstruct |
| 38 | 42 | public void init() { |
| ... | ... | @@ -42,24 +46,35 @@ public class IotDeviceApiImpl implements IotDeviceCommonApi { |
| 42 | 46 | .readTimeout(rpc.getReadTimeout()) |
| 43 | 47 | .connectTimeout(rpc.getConnectTimeout()) |
| 44 | 48 | .build(); |
| 49 | + // TODO @AI:继续复用 rpc.getConnectTimeout();不需要独立 modbusRestTemplate | |
| 50 | + modbusRestTemplate = new RestTemplateBuilder() | |
| 51 | + .rootUri(rpc.getUrl() + "/rpc-api/iot/modbus") | |
| 52 | + .readTimeout(rpc.getReadTimeout()) | |
| 53 | + .connectTimeout(rpc.getConnectTimeout()) | |
| 54 | + .build(); | |
| 45 | 55 | } |
| 46 | 56 | |
| 47 | 57 | @Override |
| 48 | 58 | public CommonResult<Boolean> authDevice(IotDeviceAuthReqDTO authReqDTO) { |
| 49 | - return doPost("/auth", authReqDTO, new ParameterizedTypeReference<>() { }); | |
| 59 | + return doPost(restTemplate, "/auth", authReqDTO, new ParameterizedTypeReference<>() { }); | |
| 50 | 60 | } |
| 51 | 61 | |
| 52 | 62 | @Override |
| 53 | 63 | public CommonResult<IotDeviceRespDTO> getDevice(IotDeviceGetReqDTO getReqDTO) { |
| 54 | - return doPost("/get", getReqDTO, new ParameterizedTypeReference<>() { }); | |
| 64 | + return doPost(restTemplate, "/get", getReqDTO, new ParameterizedTypeReference<>() { }); | |
| 65 | + } | |
| 66 | + | |
| 67 | + @Override | |
| 68 | + public CommonResult<List<IotModbusDeviceConfigRespDTO>> getEnabledModbusDeviceConfigs() { | |
| 69 | + return doPost(modbusRestTemplate, "/enabled-configs", null, new ParameterizedTypeReference<>() { }); | |
| 55 | 70 | } |
| 56 | 71 | |
| 57 | - private <T, R> CommonResult<R> doPost(String url, T body, | |
| 72 | + private <T, R> CommonResult<R> doPost(RestTemplate template, String url, T body, | |
| 58 | 73 | ParameterizedTypeReference<CommonResult<R>> responseType) { |
| 59 | 74 | try { |
| 60 | 75 | // 请求 |
| 61 | 76 | HttpEntity<T> requestEntity = new HttpEntity<>(body); |
| 62 | - ResponseEntity<CommonResult<R>> response = restTemplate.exchange( | |
| 77 | + ResponseEntity<CommonResult<R>> response = template.exchange( | |
| 63 | 78 | url, HttpMethod.POST, requestEntity, responseType); |
| 64 | 79 | // 响应 |
| 65 | 80 | CommonResult<R> result = response.getBody(); | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
| ... | ... | @@ -117,6 +117,9 @@ yudao: |
| 117 | 117 | keep-alive-timeout-seconds: 300 # 保持连接超时时间(秒) |
| 118 | 118 | ssl-enabled: false # 是否启用 SSL(wss://) |
| 119 | 119 | sub-protocol: mqtt # WebSocket 子协议 |
| 120 | + # TODO @AI:modbus-tcp 组件配置待补充 | |
| 121 | + modbus-tcp: | |
| 122 | + enabled: true | |
| 120 | 123 | |
| 121 | 124 | --- #################### 日志相关配置 #################### |
| 122 | 125 | ... | ... |