diff --git a/S7_PLC使用说明.md b/S7_PLC使用说明.md
new file mode 100644
index 0000000..069081b
--- /dev/null
+++ b/S7_PLC使用说明.md
@@ -0,0 +1,260 @@
+# S7 PLC数据采集功能说明
+
+## 概述
+
+本功能实现了通过S7协议读取西门子PLC200 Smart设备的数据,支持定时自动采集和手动读写操作。
+
+## 功能特性
+
+### 1. 支持的地址类型
+- **M区**: M0.1, M0.2 (位地址), M10 (字节地址)
+- **I区**: I0.1, I0.2 (输入位), I0 (输入字节)
+- **Q区**: Q0.1, Q0.2 (输出位), Q0 (输出字节)
+- **VB区**: VB12 (字节)
+- **VW区**: VW314 (字,2字节)
+- **VD区**: VD11 (双字,4字节,浮点数)
+- **AIW区**: AIW0, AIW64 (模拟量输入字,2字节,只读)
+- **AQW区**: AQW0, AQW64 (模拟量输出字,2字节,可读写)
+
+### 2. 核心功能
+- ✅ 定时自动采集(每5分钟执行一次)
+- ✅ 手动优先逻辑(Constant.WEB_FLAG为true时跳过采集)
+- ✅ 根据gateway_manage中community_type='S7'的dataCom查询采集参数
+- ✅ 指令创建、下发、解析一体化
+- ✅ 数据倍率、初始值、小数点处理
+- ✅ 连接缓存管理,避免频繁创建连接
+
+## 文件结构
+
+```
+user-service/src/main/java/com/mh/user/
+├── s7/
+│ └── S7ConnectorUtil.java # S7通信工具类
+├── job/
+│ └── S7PlcCollectionJob.java # 定时采集任务
+├── controller/
+│ └── S7PlcController.java # 手动读写接口
+└── mapper/
+ ├── GatewayManageMapper.java # 新增queryS7Gateways()方法
+ └── CollectionParamsManageMapper.java # 新增selectCPMByDataCom()方法
+```
+
+## 数据库配置
+
+### 1. gateway_manage表配置
+```sql
+-- 添加S7类型的网关记录
+INSERT INTO gateway_manage (
+ gateway_name,
+ gateway_ip,
+ gateway_port, -- 格式: "rack,slot" 例如 "0,1"
+ data_com, -- 通讯口标识,例如 "S7_PLC_1"
+ community_type,-- 必须设置为 'S7'
+ grade -- 连接状态: 1=在线
+) VALUES (
+ 'PLC200Smart_1',
+ '192.168.1.100',
+ '0,1',
+ 'S7_PLC_1',
+ 'S7',
+ '1'
+);
+```
+
+### 2. device_install表配置
+```sql
+-- 添加设备安装记录,关联到S7网关
+INSERT INTO device_install (
+ device_name,
+ device_type,
+ data_com, -- 与gateway_manage中的data_com一致
+ building_id
+) VALUES (
+ 'S7设备1',
+ 'PLC',
+ 'S7_PLC_1',
+ '1'
+);
+```
+
+### 3. collection_params_manage表配置
+```sql
+-- 添加采集点位配置
+INSERT INTO collection_params_manage (
+ device_install_id,
+ register_addr, -- 寄存器地址,如 M0.1, VB12, VW314, VD11
+ func_code, -- 功能码(可选)
+ mt_ratio, -- 倍率
+ mt_init_value, -- 初始值
+ digits, -- 小数点位数
+ is_use, -- 是否启用: 1=启用
+ other_name, -- 点位别名(唯一)
+ building_id
+) VALUES (
+ 1, -- device_install_id
+ 'M0.1', -- 寄存器地址
+ NULL,
+ 1, -- 倍率
+ 0, -- 初始值
+ 2, -- 2位小数
+ 1, -- 启用
+ 'plc_status', -- 点位名称
+ '1' -- building_id
+);
+```
+
+## 使用方式
+
+### 1. 自动定时采集
+
+系统启动后会自动执行定时任务,每5分钟采集一次所有S7网关的数据。
+
+**定时任务配置:**
+- Cron表达式: `0 0/5 * * * ?` (每5分钟)
+- 可在`S7PlcCollectionJob.collectS7Data()`方法中修改
+
+**手动优先逻辑:**
+- 当`Constant.WEB_FLAG = true`时,跳过本次采集
+- 确保手动操作不被定时任务干扰
+
+### 2. 手动写入数据
+
+**API接口:**
+```
+POST /s7plc/write
+参数:
+ - cpmId: 采集参数ID (Long)
+ - value: 要写入的值 (Object)
+
+返回:
+{
+ "code": 200,
+ "msg": "写入成功"
+}
+```
+
+**示例:**
+```bash
+curl -X POST "http://localhost:8080/s7plc/write?cpmId=1&value=1"
+```
+
+### 3. 清理连接缓存
+
+**API接口:**
+```
+POST /s7plc/clearCache
+
+返回:
+{
+ "code": 200,
+ "msg": "缓存清理成功"
+}
+```
+
+## 代码示例
+
+### 在Service中调用手动写入
+```java
+@Autowired
+private S7PlcCollectionJob s7PlcCollectionJob;
+
+public void controlDevice(Long cpmId, Object value) {
+ // 设置手动操作标志
+ Constant.WEB_FLAG = true;
+ try {
+ boolean success = s7PlcCollectionJob.writeData(cpmId, value);
+ if (success) {
+ log.info("控制成功");
+ } else {
+ log.error("控制失败");
+ }
+ } finally {
+ Constant.WEB_FLAG = false;
+ }
+}
+```
+
+## 注意事项
+
+### 1. PLC连接配置
+- **IP地址**: 确保gateway_manage.gatewayIP正确配置
+- **Rack/Slot**: 默认为0,1,可通过gatewayPort字段配置(格式: "rack,slot")
+- **网络**: 确保服务器能访问PLC的IP地址和端口(默认102)
+
+### 2. 数据类型映射
+| 地址类型 | 数据类型 | DaveArea | 说明 |
+|---------|---------|----------|------|
+| M0.1 | Boolean | FLAGS | 位地址,返回0或1 |
+| M10 | Byte | FLAGS | 字节地址,返回0-255 |
+| I0.1 | Boolean | INPUTS | 输入位,返回0或1(只读) |
+| I0 | Byte | INPUTS | 输入字节,返回0-255(只读) |
+| Q0.1 | Boolean | OUTPUTS | 输出位,返回0或1 |
+| Q0 | Byte | OUTPUTS | 输出字节,返回0-255 |
+| VB12 | Byte | DB | V区字节,返回0-255 |
+| VW314 | Word | DB | V区字,返回0-65535 |
+| VD11 | Float | DB | V区双字,返回浮点数 |
+| AIW0 | Word | INPUTS | 模拟量输入字,返回0-65535(只读) |
+| AQW0 | Word | OUTPUTS | 模拟量输出字,返回0-65535 |
+
+### 3. 注意事项
+
+#### 读写权限说明
+- **只读区域**: I区(输入)、AIW(模拟量输入) - 这些区域通常由PLC硬件控制,不建议写入
+- **可写区域**: M区、Q区(输出)、V区、AQW(模拟量输出)
+- 尝试写入只读区域时会记录警告日志,但仍会执行写入操作
+
+### 4. 性能优化
+- 连接器采用缓存机制,避免频繁创建连接
+- 批量采集时使用同一个连接器实例
+- 可通过`clearCache()`方法清理缓存
+
+### 5. 异常处理
+- 连接失败会记录日志并跳过该网关
+- 单个点位采集失败不影响其他点位
+- 所有异常都会记录详细日志
+
+## 依赖库
+
+项目已添加s7connector依赖:
+```xml
+
+ com.github.s7connector
+ s7connector
+ 2.1
+
+```
+
+## 后续优化方向
+
+1. **连接池管理**: 实现更完善的连接池,支持断线重连
+2. **批量读写**: 支持一次性读取多个点位,提高效率
+3. **数据校验**: 增加数据范围校验和异常值过滤
+4. **监控告警**: 添加PLC连接状态监控和异常告警
+5. **配置化**: 将定时任务周期等参数配置化
+6. **测试用例**: 编写单元测试和集成测试
+
+## 常见问题
+
+### Q1: 连接失败怎么办?
+- 检查PLC IP地址是否正确
+- 检查网络连接是否正常
+- 检查PLC是否允许S7通信
+- 查看日志中的详细错误信息
+
+### Q2: 读取数据不正确?
+- 检查registerAddr格式是否正确
+- 确认数据类型是否匹配(M/VB/VW/VD)
+- 检查倍率和初始值配置
+- 对比PLC中的实际值
+
+### Q3: 如何调试?
+- 查看日志文件:`logs/user-service/info/`
+- 启用DEBUG日志级别
+- 使用PLC仿真软件进行测试
+
+## 联系方式
+
+如有问题,请联系开发团队。
+
+---
+**最后更新**: 2026-06-23
diff --git a/S7_PLC地址类型扩展说明.md b/S7_PLC地址类型扩展说明.md
new file mode 100644
index 0000000..2f01889
--- /dev/null
+++ b/S7_PLC地址类型扩展说明.md
@@ -0,0 +1,303 @@
+# S7 PLC 地址类型扩展说明
+
+## 更新概述
+
+本次更新为S7ConnectorUtil工具类新增了以下地址类型的支持:
+- **I区**: 数字量输入 (Input)
+- **Q区**: 数字量输出 (Output)
+- **AIW区**: 模拟量输入字 (Analog Input Word)
+- **AQW区**: 模拟量输出字 (Analog Output Word)
+
+## 支持的地址格式
+
+### 1. I区 - 数字量输入
+**地址格式:**
+- 位地址: `I0.1`, `I1.5`, `I2.3` 等
+- 字节地址: `I0`, `I1`, `I10` 等
+
+**特性:**
+- DaveArea: `PE` (Process Inputs)
+- 数据类型: Boolean (位) 或 Byte (字节)
+- **通常只读**,由外部传感器/开关控制
+- 写入时会记录警告日志
+
+**示例:**
+```java
+// 读取输入位 I0.1
+Object value = connector.readData("I0.1"); // 返回 0 或 1
+
+// 读取输入字节 I0
+Object value = connector.readData("I0"); // 返回 0-255
+```
+
+### 2. Q区 - 数字量输出
+**地址格式:**
+- 位地址: `Q0.1`, `Q1.5`, `Q2.3` 等
+- 字节地址: `Q0`, `Q1`, `Q10` 等
+
+**特性:**
+- DaveArea: `PA` (Process Outputs)
+- 数据类型: Boolean (位) 或 Byte (字节)
+- **可读写**,用于控制继电器/指示灯等
+
+**示例:**
+```java
+// 读取输出位 Q0.1
+Object value = connector.readData("Q0.1"); // 返回 0 或 1
+
+// 写入输出位 Q0.1
+connector.writeData("Q0.1", 1); // 置位
+
+// 写入输出字节 Q0
+connector.writeData("Q0", 255); // 所有位都置1
+```
+
+### 3. AIW区 - 模拟量输入字
+**地址格式:**
+- `AIW0`, `AIW64`, `AIW128` 等
+
+**特性:**
+- DaveArea: `PE` (Process Inputs)
+- 数据类型: Word (2字节, 0-65535)
+- **只读**,用于读取模拟量传感器(温度、压力等)
+- 通常对应PLC的模拟量输入模块
+- 写入时会记录警告日志
+
+**示例:**
+```java
+// 读取模拟量输入 AIW0
+Object value = connector.readData("AIW0"); // 返回 0-65535
+
+// 转换为实际值(例如温度传感器 0-10V 对应 0-100°C)
+int rawValue = (Integer) value;
+double temperature = rawValue * 100.0 / 65535.0;
+```
+
+### 4. AQW区 - 模拟量输出字
+**地址格式:**
+- `AQW0`, `AQW64`, `AQW128` 等
+
+**特性:**
+- DaveArea: `PA` (Process Outputs)
+- 数据类型: Word (2字节, 0-65535)
+- **可读写**,用于控制模拟量执行器(变频器、调节阀等)
+- 通常对应PLC的模拟量输出模块
+
+**示例:**
+```java
+// 读取模拟量输出 AQW0
+Object value = connector.readData("AQW0"); // 返回 0-65535
+
+// 写入模拟量输出(例如设置变频器频率 0-50Hz)
+double frequency = 25.0; // 25Hz
+int outputValue = (int)(frequency * 65535.0 / 50.0);
+connector.writeData("AQW0", outputValue);
+```
+
+## DaveArea映射表
+
+| PLC区域 | DaveArea枚举 | 说明 |
+|---------|-------------|------|
+| M区 | FLAGS | 位存储区/Merkers |
+| I区 | INPUTS | 数字量输入/Inputs |
+| Q区 | OUTPUTS | 数字量输出/Outputs |
+| V区(DB) | DB | 数据块/Data Block |
+| AIW | INPUTS | 模拟量输入(映射到输入区) |
+| AQW | OUTPUTS | 模拟量输出(映射到输出区) |
+
+## 数据库配置示例
+
+### 配置I区点位
+```sql
+-- 数字量输入点位
+INSERT INTO collection_params_manage (
+ device_install_id,
+ register_addr,
+ is_use,
+ other_name,
+ building_id
+) VALUES (
+ 1,
+ 'I0.1', -- 急停按钮状态
+ 1,
+ 'emergency_stop',
+ '1'
+);
+```
+
+### 配置Q区点位
+```sql
+-- 数字量输出点位
+INSERT INTO collection_params_manage (
+ device_install_id,
+ register_addr,
+ is_use,
+ other_name,
+ building_id
+) VALUES (
+ 1,
+ 'Q0.1', -- 启动指示灯
+ 1,
+ 'start_indicator',
+ '1'
+);
+```
+
+### 配置AIW区点位
+```sql
+-- 模拟量输入点位(温度传感器)
+INSERT INTO collection_params_manage (
+ device_install_id,
+ register_addr,
+ mt_ratio, -- 倍率
+ digits, -- 小数点
+ is_use,
+ other_name,
+ building_id
+) VALUES (
+ 1,
+ 'AIW0', -- 温度传感器
+ 0.0015259, -- 倍率: 100/65535 ≈ 0.0015259
+ 2, -- 2位小数
+ 1,
+ 'temperature_sensor',
+ '1'
+);
+```
+
+### 配置AQW区点位
+```sql
+-- 模拟量输出点位(变频器控制)
+INSERT INTO collection_params_manage (
+ device_install_id,
+ register_addr,
+ is_use,
+ other_name,
+ building_id
+) VALUES (
+ 1,
+ 'AQW0', -- 变频器频率设定
+ 1,
+ 'vfd_frequency',
+ '1'
+);
+```
+
+## 实际应用案例
+
+### 案例1: 读取多个传感器数据
+```java
+// 在S7PlcCollectionJob中自动采集
+// 配置以下点位:
+// - I0.1: 急停按钮
+// - I0.2: 启动按钮
+// - AIW0: 温度传感器
+// - AIW64: 压力传感器
+
+// 系统会每5分钟自动读取并保存到数据库
+```
+
+### 案例2: 手动控制输出
+```java
+@Autowired
+private S7PlcCollectionJob s7PlcCollectionJob;
+
+// 通过API控制Q区输出
+@PostMapping("/control/light")
+public HttpResult controlLight(@RequestParam boolean on) {
+ Constant.WEB_PLC_FLAG = true;
+ try {
+ // 获取Q0.1对应的cpmId
+ Long cpmId = getCpmIdByOtherName("start_indicator");
+ s7PlcCollectionJob.writeData(cpmId, on ? 1 : 0);
+ return HttpResult.ok();
+ } finally {
+ Constant.WEB_PLC_FLAG = false;
+ }
+}
+```
+
+### 案例3: 模拟量转换
+```java
+// 温度传感器: 0-10V 对应 0-100°C
+// AIW0 原始值: 0-65535
+
+// 在Service中进行转换
+public double getTemperature() {
+ CollectionParamsManageEntity param =
+ collectionParamsManageMapper.selectDeviceInstallByOtherName(
+ "temperature_sensor", "1"
+ );
+
+ // 原始值已经应用了倍率 0.0015259
+ BigDecimal rawValue = param.getCurValue();
+
+ // 直接得到摄氏度
+ return rawValue.doubleValue();
+}
+```
+
+## 注意事项
+
+### 1. I区和AIW区的只读特性
+- 这些区域由PLC硬件或外部设备控制
+- 尝试写入会导致警告日志
+- 在实际应用中应避免写入操作
+
+### 2. Q区和AQW区的写入权限
+- 确保PLC程序允许外部写入
+- 注意PLC程序中是否有互锁逻辑
+- 写入前确认设备状态安全
+
+### 3. 模拟量量程匹配
+- AIW/AQW的0-65535对应实际物理量
+- 需要在PLC中配置正确的量程
+- 或在Java代码中进行转换计算
+
+### 4. 地址偏移
+- AIW和AQW的地址通常是2的倍数(AIW0, AIW64, AIW128...)
+- 具体取决于PLC硬件配置
+- 请参考PLC硬件手册
+
+## 测试建议
+
+### 1. 使用PLC仿真软件
+- S7-PLCSIM Advanced
+- 可以模拟各种地址类型的读写
+- 验证地址解析是否正确
+
+### 2. 逐步测试
+1. 先测试M区和V区(最常用)
+2. 再测试I区和Q区(数字量)
+3. 最后测试AIW和AQW(模拟量)
+
+### 3. 监控日志
+- 查看读写操作的日志
+- 确认数据类型转换正确
+- 检查是否有警告或错误
+
+## 常见问题
+
+### Q1: 为什么I区写入会有警告?
+A: I区是输入区,通常由外部硬件控制。写入I区在技术上可行,但不符合PLC编程规范,可能导致意外行为。
+
+### Q2: AIW和AQW的地址为什么是64、128这样的数字?
+A: 这取决于PLC的硬件配置。每个模拟量通道占用2个字节,地址间隔由模块插槽位置决定。
+
+### Q3: 如何确定模拟量的量程?
+A:
+1. 查看PLC硬件模块手册
+2. 检查PLC程序中的量程配置
+3. 使用万用表测量实际电压/电流
+4. 对比PLC显示值和实际值进行校准
+
+### Q4: Q区写入后PLC没有响应?
+A:
+1. 检查PLC程序是否覆盖了Q区
+2. 确认PLC处于RUN模式
+3. 检查是否有硬件故障
+4. 验证网络连接正常
+
+---
+**更新日期**: 2026-06-23
+**版本**: v1.1
diff --git a/S7_PLC数据解析问题排查.md b/S7_PLC数据解析问题排查.md
new file mode 100644
index 0000000..c563a43
--- /dev/null
+++ b/S7_PLC数据解析问题排查.md
@@ -0,0 +1,301 @@
+# S7 PLC 数据解析问题排查指南
+
+## 问题现象
+虽然有数据返回,但解析出来的数值与实际PLC中的值不一致。
+
+## 常见原因及解决方案
+
+### 1. 字节序问题 (最常见)
+
+**问题描述:**
+- 西门子S7协议使用**大端序**(Big-Endian)
+- 如果库返回的是小端序,会导致数值错误
+
+**示例:**
+```
+PLC中VW100 = 256 (0x0100)
+大端序: [0x01, 0x00] → 计算: (0x01 << 8) | 0x00 = 256 ✓
+小端序: [0x00, 0x01] → 计算: (0x00 << 8) | 0x01 = 1 ✗
+```
+
+**当前实现:**
+代码已使用大端序转换:
+```java
+private int bytesToWord(byte[] data) {
+ return ((data[0] & 0xFF) << 8) | (data[1] & 0xFF); // 大端序
+}
+```
+
+**排查方法:**
+查看日志中的原始数据和计算结果:
+```
+读取VW区: addr=VW100, data=[1, 0], hex=01 00, value=256
+```
+
+---
+
+### 2. DB块号问题
+
+**问题描述:**
+V区(VB/VW/VD)对应PLC中的数据块(DB),不同项目可能使用不同的DB块号。
+
+**当前实现:**
+```java
+// 固定使用DB1
+data = connector.read(DaveArea.DB, 1, addressInfo.getByteOffset(), 2);
+```
+
+**解决方案:**
+如果你的PLC使用其他DB块(如DB2、DB100等),需要修改代码或配置。
+
+**排查步骤:**
+1. 在PLC程序中确认V区对应的DB块号
+2. 查看TIA Portal或STEP 7中的符号表
+3. 修改代码中的DB块号参数
+
+---
+
+### 3. AIW/AQW地址偏移问题
+
+**问题描述:**
+模拟量模块的地址可能不是从0开始,而是从64、128等开始。
+
+**示例:**
+```
+第一个模拟量通道: AIW0
+第二个模拟量通道: AIW2 或 AIW64 (取决于硬件配置)
+```
+
+**排查方法:**
+1. 查看PLC硬件组态
+2. 确认模拟量模块的起始地址
+3. 在PLC监控表中验证实际地址
+
+---
+
+### 4. 数据类型不匹配
+
+**问题描述:**
+使用了错误的读取方法,导致数据类型解析错误。
+
+**对照表:**
+| PLC地址 | 正确方法 | 错误示例 |
+|---------|---------|---------|
+| VW100 (字) | bytesToWord() - 2字节 | bytesToDWord() - 4字节 |
+| VD200 (双字) | bytesToDWord() - 4字节 | bytesToWord() - 2字节 |
+| M0.1 (位) | getBit() | 直接读取字节 |
+
+**排查方法:**
+查看日志中的字节长度:
+```
+读取VW区: data=[1, 0] ← 应该是2字节
+读取VD区: data=[64, 66, 0, 0] ← 应该是4字节
+```
+
+---
+
+### 5. 浮点数精度问题
+
+**问题描述:**
+VD区存储的是IEEE 754浮点数,转换可能有精度损失。
+
+**示例:**
+```
+PLC中: 25.5
+读取后: 25.500001 或 25.499999
+```
+
+**解决方案:**
+在应用层进行四舍五入:
+```java
+BigDecimal value = new BigDecimal(result);
+value = value.setScale(2, BigDecimal.ROUND_HALF_UP);
+```
+
+---
+
+## 调试步骤
+
+### 第1步: 启用DEBUG日志
+
+在`application.yml`或`application.properties`中设置:
+```yaml
+logging:
+ level:
+ com.mh.user.s7.S7ConnectorUtil: DEBUG
+```
+
+### 第2步: 查看原始数据日志
+
+运行程序后,查看日志输出:
+```
+2026-06-23 10:30:15 DEBUG - 读取VW区: addr=VW100, data=[1, 0], hex=01 00, value=256
+2026-06-23 10:30:15 INFO - 读取成功: addr=VW100, value=256
+```
+
+### 第3步: 对比PLC实际值
+
+1. 打开TIA Portal或STEP 7
+2. 进入在线监控模式
+3. 查看对应地址的实际值
+4. 对比日志中的value值
+
+### 第4步: 分析差异
+
+**情况A: 字节顺序相反**
+```
+PLC显示: 256 (0x0100)
+日志显示: data=[0, 1], value=1
+```
+→ 需要交换字节顺序
+
+**情况B: 数值完全不对**
+```
+PLC显示: 100
+日志显示: value=16777216
+```
+→ 可能是DB块号错误或地址偏移错误
+
+**情况C: 浮点数异常**
+```
+PLC显示: 25.5
+日志显示: value=1.23E-40
+```
+→ 字节序错误或数据类型错误
+
+---
+
+## 常见问题案例
+
+### 案例1: VW区读数是小端序
+
+**现象:**
+```
+PLC: VW100 = 256
+日志: data=[0, 1], value=1
+```
+
+**解决:**
+检查`bytesToWord()`方法,确保是大端序:
+```java
+// 正确 - 大端序
+return ((data[0] & 0xFF) << 8) | (data[1] & 0xFF);
+
+// 错误 - 小端序
+return (data[0] & 0xFF) | ((data[1] & 0xFF) << 8);
+```
+
+---
+
+### 案例2: VD区浮点数解析错误
+
+**现象:**
+```
+PLC: VD200 = 25.5
+日志: data=[65, 76, 0, 0], value=25.5 ← 正确
+或
+日志: data=[0, 0, 76, 65], value=异常值 ← 错误
+```
+
+**解决:**
+确认`bytesToDWord()`使用大端序:
+```java
+int intBits = ((data[0] & 0xFF) << 24) |
+ ((data[1] & 0xFF) << 16) |
+ ((data[2] & 0xFF) << 8) |
+ (data[3] & 0xFF);
+return Float.intBitsToFloat(intBits);
+```
+
+---
+
+### 案例3: DB块号错误
+
+**现象:**
+```
+PLC: DB100.DBW0 = 1000
+日志: value=0 或随机值
+```
+
+**解决:**
+修改读取时的DB块号:
+```java
+// 如果V区对应DB100
+data = connector.read(DaveArea.DB, 100, addressInfo.getByteOffset(), 2);
+```
+
+---
+
+## 快速验证方法
+
+### 方法1: 使用已知值测试
+
+在PLC程序中设置一个已知值:
+```
+MB10 = 123 (0x7B)
+VW100 = 256 (0x0100)
+VD200 = 10.5 (浮点数)
+```
+
+然后读取并对比:
+```
+预期日志:
+读取M区: addr=M10, data=[123], hex=7B, value=123
+读取VW区: addr=VW100, data=[1, 0], hex=01 00, value=256
+读取VD区: addr=VD200, data=[65, 40, 0, 0], hex=41 28 00 00, value=10.5
+```
+
+### 方法2: 使用PLC仿真软件
+
+1. 安装S7-PLCSIM Advanced
+2. 创建测试程序,设置已知值
+3. 运行Java程序读取
+4. 对比结果
+
+### 方法3: 使用其他S7客户端工具
+
+推荐使用:
+- **Modbus Poll** (支持S7)
+- **CAS Modbus Scanner**
+- **QModMaster**
+
+用这些工具读取相同的地址,对比结果。
+
+---
+
+## 性能优化建议
+
+### 1. 批量读取
+
+如果需要读取多个连续地址,建议使用批量读取:
+```java
+// 一次性读取10个字
+byte[] data = connector.read(DaveArea.DB, 1, 0, 20); // 20字节 = 10个字
+```
+
+### 2. 减少日志输出
+
+生产环境关闭DEBUG日志:
+```yaml
+logging:
+ level:
+ com.mh.user.s7.S7ConnectorUtil: INFO
+```
+
+### 3. 连接复用
+
+当前已实现连接缓存,确保不要频繁创建新连接。
+
+---
+
+## 联系支持
+
+如果以上方法都无法解决问题,请提供:
+
+1. **日志输出**: 包含完整的DEBUG日志
+2. **PLC截图**: TIA Portal中对应地址的监控值
+3. **地址信息**: 具体的registerAddr配置
+4. **期望值vs实际值**: 对比表格
+
+---
+**更新日期**: 2026-06-23
diff --git a/user-service/pom.xml b/user-service/pom.xml
index 9aa85c2..2c8a093 100644
--- a/user-service/pom.xml
+++ b/user-service/pom.xml
@@ -179,6 +179,13 @@
4.1.86.Final
+
+
+ com.github.s7connector
+ s7connector
+ 2.1
+
+
diff --git a/user-service/src/main/java/com/mh/user/constants/Constant.java b/user-service/src/main/java/com/mh/user/constants/Constant.java
index 3c441da..620c590 100644
--- a/user-service/src/main/java/com/mh/user/constants/Constant.java
+++ b/user-service/src/main/java/com/mh/user/constants/Constant.java
@@ -17,12 +17,17 @@ public class Constant {
public static final String WEATHER_DATA = "weather_data";
public static final String COMMUNITY_TYPE_REAL_COM = "realCom";
public static final String COMMUNITY_TYPE_TCP = "tcp";
+ public static final String COMMUNITY_TYPE_S7 = "S7";
public static boolean CONTROL_WEB_FLAG = false;
public static boolean SEND_STATUS = false; // 指令发送状态
public static volatile boolean FLAG = false;
public static volatile boolean WEB_FLAG = false; // 判断是否有前端指令下发
+ public static volatile boolean PLC_FLAG = false;
+
+ public static volatile boolean WEB_PLC_FLAG = false; // 判断是否有前端指令下发
+
public static final String FAIL = "fail";
public static final String SUCCESS = "success";
diff --git a/user-service/src/main/java/com/mh/user/controller/DeviceOperateController.java b/user-service/src/main/java/com/mh/user/controller/DeviceOperateController.java
index b0ffc7b..177251e 100644
--- a/user-service/src/main/java/com/mh/user/controller/DeviceOperateController.java
+++ b/user-service/src/main/java/com/mh/user/controller/DeviceOperateController.java
@@ -10,6 +10,7 @@ import com.mh.user.entity.ControlSetEntity;
import com.mh.user.entity.DeviceCodeParamEntity;
import com.mh.user.entity.DeviceInstallEntity;
import com.mh.user.entity.PumpSetEntity;
+import com.mh.user.job.S7PlcCollectionJob;
import com.mh.user.model.DeviceModel;
import com.mh.user.model.SerialPortModel;
import com.mh.user.serialport.SerialPortSingle2;
@@ -54,6 +55,10 @@ public class DeviceOperateController {
@Autowired
private IMqttGatewayService iMqttGatewayService;
+ @Autowired
+ private S7PlcCollectionJob s7PlcCollectionJob;
+
+
//操作设备
@SysLogger(title = "控制管理", optDesc = "设置设备参数值")
@PostMapping(value = "/operate")
@@ -92,6 +97,14 @@ public class DeviceOperateController {
iMqttGatewayService.publish(sendTopic, sendOrder, 0);
}
}
+ } else if (deviceControlService.isS7Control(params)) {
+ for (SerialPortModel serialPortModel :
+ params) {
+ boolean s7Write = s7PlcCollectionJob.writeData(Long.valueOf(serialPortModel.getCpmId()), serialPortModel.getDataValue());
+ if (!s7Write) {
+ return HttpResult.error(500, "fail");
+ }
+ }
} else {
String returnStr = deviceControlService.readOrWriteDevice(params, Constant.WRITE);
if (!StringUtils.isBlank(returnStr) && "fail".equals(returnStr)) {
diff --git a/user-service/src/main/java/com/mh/user/controller/HotWaterMonitorController.java b/user-service/src/main/java/com/mh/user/controller/HotWaterMonitorController.java
index 092e20f..e7068e2 100644
--- a/user-service/src/main/java/com/mh/user/controller/HotWaterMonitorController.java
+++ b/user-service/src/main/java/com/mh/user/controller/HotWaterMonitorController.java
@@ -43,7 +43,7 @@ public class HotWaterMonitorController {
* @return
*/
@GetMapping("/operateList")
- public HttpResult operateList(@RequestParam("buildingId") String buildingId, @RequestParam("deviceInstallId") Integer deviceInstallId) {
+ public HttpResult operateList(@RequestParam("buildingId") String buildingId, @RequestParam(value = "deviceInstallId", required = false) Integer deviceInstallId) {
if (deviceInstallId == null) {
List list = collectionParamsManageService.operateList(buildingId);
return HttpResult.ok(list);
diff --git a/user-service/src/main/java/com/mh/user/controller/S7PlcController.java b/user-service/src/main/java/com/mh/user/controller/S7PlcController.java
new file mode 100644
index 0000000..8ea43cd
--- /dev/null
+++ b/user-service/src/main/java/com/mh/user/controller/S7PlcController.java
@@ -0,0 +1,68 @@
+package com.mh.user.controller;
+
+import com.mh.common.http.HttpResult;
+import com.mh.user.job.S7PlcCollectionJob;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * S7 PLC控制接口
+ * 提供手动读写PLC点位的功能
+ *
+ * @author System
+ * @date 2026-06-23
+ */
+@Slf4j
+@RestController
+@RequestMapping("/s7plc")
+public class S7PlcController {
+
+ private final S7PlcCollectionJob s7PlcCollectionJob;
+
+ public S7PlcController(S7PlcCollectionJob s7PlcCollectionJob) {
+ this.s7PlcCollectionJob = s7PlcCollectionJob;
+ }
+
+ /**
+ * 手动写入数据到PLC
+ *
+ * @param cpmId 采集参数ID
+ * @param value 要写入的值
+ * @return 操作结果
+ */
+ @PostMapping("/write")
+ public HttpResult writeData(@RequestParam Long cpmId, @RequestParam Object value) {
+ try {
+ log.info("收到手动写入请求: cpmId={}, value={}", cpmId, value);
+
+ boolean success = s7PlcCollectionJob.writeData(cpmId, value);
+
+ if (success) {
+ return HttpResult.ok("写入成功");
+ } else {
+ return HttpResult.error(500, "写入失败");
+ }
+ } catch (Exception e) {
+ log.error("手动写入异常: cpmId={}", cpmId, e);
+ return HttpResult.error(500, "写入异常: " + e.getMessage());
+ }
+ }
+
+ /**
+ * 清理S7连接器缓存
+ * 用于重启连接或维护
+ *
+ * @return 操作结果
+ */
+ @PostMapping("/clearCache")
+ public HttpResult clearCache() {
+ try {
+ log.info("收到清理S7连接器缓存请求");
+ s7PlcCollectionJob.clearConnectorCache();
+ return HttpResult.ok("缓存清理成功");
+ } catch (Exception e) {
+ log.error("清理缓存异常", e);
+ return HttpResult.error(500, "清理缓存异常: " + e.getMessage());
+ }
+ }
+}
diff --git a/user-service/src/main/java/com/mh/user/dto/HotWaterSupplyPumpControlVO.java b/user-service/src/main/java/com/mh/user/dto/HotWaterSupplyPumpControlVO.java
new file mode 100644
index 0000000..a874a50
--- /dev/null
+++ b/user-service/src/main/java/com/mh/user/dto/HotWaterSupplyPumpControlVO.java
@@ -0,0 +1,179 @@
+package com.mh.user.dto;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Getter;
+import lombok.Setter;
+
+import java.math.BigDecimal;
+import java.util.StringJoiner;
+
+/**
+ * @author LJF
+ * @version 1.0
+ * @project EEMCS
+ * @description 回水泵热泵控制界面VO
+ * @date 2025-03-14 09:00:37
+ */
+@Setter
+@Getter
+public class HotWaterSupplyPumpControlVO {
+
+ private String id;
+
+ private String name;
+
+ private int orderNum;
+
+ // 定时_时开1
+ private int oneHourTimeOpenSetOne;
+ private String oneHourTimeOpenSetOneId;
+
+ // 定时_分开1
+ private int oneMinTimeOpenSetOne;
+ private String oneMinTimeOpenSetOneId;
+
+ // 定时_时关1
+ private int oneHourTimeCloseSetOne;
+ private String oneHourTimeCloseSetOneId;
+
+ // 定时_分关1
+ private int oneMinTimeCloseSetOne;
+ private String oneMinTimeCloseSetOneId;
+
+ // 定时_时分开1
+ private String oneHourMinTimeOpenSetOneStr;
+ // 定时_时分关1
+ private String oneHourMinTimeCloseSetOneStr;
+
+ // 定时_时开2
+ private int oneHourTimeOpenSetTwo;
+ private String oneHourTimeOpenSetTwoId;
+
+ // 定时_分开2
+ private int oneMinTimeOpenSetTwo;
+ private String oneMinTimeOpenSetTwoId;
+
+ // 定时_时关2
+ private int oneHourTimeCloseSetTwo;
+ private String oneHourTimeCloseSetTwoId;
+
+ // 定时_分关2
+ private int oneMinTimeCloseSetTwo;
+ private String oneMinTimeCloseSetTwoId;
+
+ // 定时_时分开2
+ private String oneHourMinTimeOpenSetTwoStr;
+ // 定时_时分关2
+ private String oneHourMinTimeCloseSetTwoStr;
+
+ // 定时_时开3
+ private int oneHourTimeOpenSetThree;
+ private String oneHourTimeOpenSetThreeId;
+
+ // 定时_分开3
+ private int oneMinTimeOpenSetThree;
+ private String oneMinTimeOpenSetThreeId;
+
+ // 定时_时关3
+ private int oneHourTimeCloseSetThree;
+ private String oneHourTimeCloseSetThreeId;
+
+ // 定时_分关3
+ private int oneMinTimeCloseSetThree;
+ private String oneMinTimeCloseSetThreeId;
+
+ // 定时_时分开3
+ private String oneHourMinTimeOpenSetThreeStr;
+ // 定时_时分关3
+ private String oneHourMinTimeCloseSetThreeStr;
+
+ // 开延时
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "0")
+ private int openDelayTime;
+ private String openDelayTimeId;
+
+ // 温度设置
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "0")
+ private BigDecimal tempSet;
+ private String tempSetId;
+
+ // 累计运行时间
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "0")
+ private BigDecimal runTime;
+ private String runTimeId;
+
+ // 运行状态
+ private int runState;
+ private String runStateId;
+
+ // 启停控制
+ private int startStopControl;
+ private String startStopControlId;
+
+ // 故障
+ private int fault;
+ private String faultId;
+
+ // 一键启动
+ private int startOneKey;
+ private String startOneKeyId;
+
+ // 手自动切换
+ private int manualAutoSwitch;
+ private String manualAutoSwitchId;
+
+ // 选择两台回水泵启动
+ private int twoPumpStart;
+ private String twoPumpStartId;
+
+ // 温度设置上限
+ private BigDecimal tempSetUpperLimit;
+ private String tempSetUpperLimitId;
+
+ // 温度设置下限
+ private BigDecimal tempSetLowerLimit;
+ private String tempSetLowerLimitId;
+
+ @Override
+ public String toString() {
+ return new StringJoiner(", ", HotWaterSupplyPumpControlVO.class.getSimpleName() + "[", "]")
+ .add("id='" + id + "'")
+ .add("name='" + name + "'")
+ .add("orderNum=" + orderNum)
+ .add("oneHourTimeOpenSetOne=" + oneHourTimeOpenSetOne)
+ .add("oneHourTimeOpenSetOneId='" + oneHourTimeOpenSetOneId + "'")
+ .add("oneMinTimeOpenSetOne=" + oneMinTimeOpenSetOne)
+ .add("oneMinTimeOpenSetOneId='" + oneMinTimeOpenSetOneId + "'")
+ .add("oneHourTimeCloseSetOne=" + oneHourTimeCloseSetOne)
+ .add("oneHourTimeCloseSetOneId='" + oneHourTimeCloseSetOneId + "'")
+ .add("oneMinTimeCloseSetOne=" + oneMinTimeCloseSetOne)
+ .add("oneMinTimeCloseSetOneId='" + oneMinTimeCloseSetOneId + "'")
+ .add("oneHourTimeOpenSetTwo=" + oneHourTimeOpenSetTwo)
+ .add("oneHourTimeOpenSetTwoId='" + oneHourTimeOpenSetTwoId + "'")
+ .add("oneMinTimeOpenSetTwo=" + oneMinTimeOpenSetTwo)
+ .add("oneMinTimeOpenSetTwoId='" + oneMinTimeOpenSetTwoId + "'")
+ .add("oneHourTimeCloseSetTwo=" + oneHourTimeCloseSetTwo)
+ .add("oneHourTimeCloseSetTwoId='" + oneHourTimeCloseSetTwoId + "'")
+ .add("oneMinTimeCloseSetTwo=" + oneMinTimeCloseSetTwo)
+ .add("oneMinTimeCloseSetTwoId='" + oneMinTimeCloseSetTwoId + "'")
+ .add("oneHourTimeOpenSetThree=" + oneHourTimeOpenSetThree)
+ .add("oneHourTimeOpenSetThreeId='" + oneHourTimeOpenSetThreeId + "'")
+ .add("oneMinTimeOpenSetThree=" + oneMinTimeOpenSetThree)
+ .add("oneMinTimeOpenSetThreeId='" + oneMinTimeOpenSetThreeId + "'")
+ .add("oneHourTimeCloseSetThree=" + oneHourTimeCloseSetThree)
+ .add("oneHourTimeCloseSetThreeId='" + oneHourTimeCloseSetThreeId + "'")
+ .add("openDelayTime=" + openDelayTime)
+ .add("openDelayTimeId='" + openDelayTimeId + "'")
+ .add("tempSet=" + tempSet)
+ .add("tempSetId='" + tempSetId + "'")
+ .add("runTime=" + runTime)
+ .add("runTimeId='" + runTimeId + "'")
+ .add("runState=" + runState)
+ .add("runStateId='" + runStateId + "'")
+ .add("startStopControl=" + startStopControl)
+ .add("startStopControlId='" + startStopControlId + "'")
+ .add("fault=" + fault)
+ .add("faultId='" + faultId + "'")
+ .toString();
+ }
+}
diff --git a/user-service/src/main/java/com/mh/user/dto/HotWaterSystemControlVO.java b/user-service/src/main/java/com/mh/user/dto/HotWaterSystemControlVO.java
index 4a74df6..05f03a2 100644
--- a/user-service/src/main/java/com/mh/user/dto/HotWaterSystemControlVO.java
+++ b/user-service/src/main/java/com/mh/user/dto/HotWaterSystemControlVO.java
@@ -27,6 +27,12 @@ public class HotWaterSystemControlVO {
private int timeSet;
private String timeSetId;
+ /**
+ * 校时手自动切换:22
+ */
+ private int timeSetAuto;
+ private String timeSetAutoId;
+
// 水箱温度 2
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "0")
private BigDecimal tankTemp;
@@ -97,6 +103,26 @@ public class HotWaterSystemControlVO {
private int yearTimeSet;
private String yearTimeSetId;
+ // 自动_分_写 8
+ private int minTimeSetAuto;
+ private String minTimeSetAutoId;
+
+ // 自动_时_写 9
+ private int hourTimeSetAuto;
+ private String hourTimeSetAutoId;
+
+ // 自动_日_写 9
+ private int dayTimeSetAuto;
+ private String dayTimeSetAutoId;
+
+ // 自动_月_写 10
+ private int monthTimeSetAuto;
+ private String monthTimeSetAutoId;
+
+ // 自动_年_写 11
+ private int yearTimeSetAuto;
+ private String yearTimeSetAutoId;
+
private int orderNum;
/**
@@ -105,6 +131,34 @@ public class HotWaterSystemControlVO {
private int reset;
private String resetId;
+ /**
+ * pressureSet 压力设置
+ * @return
+ */
+ private int pressureSet;
+ private String pressureSetId;
+
+ /**
+ * 单箱液位
+ * @return
+ */
+ private int singleBoxLevel;
+ private String singleBoxLevelId;
+
+ /**
+ * 多箱液位
+ * @return
+ */
+ private int multiBoxLevel;
+ private String multiBoxLevelId;
+
+ /**
+ * 水箱高度 26
+ * @return
+ */
+ private int tankHeight;
+ private String tankHeightId;
+
@Override
public String toString() {
return new StringJoiner(", ", HotWaterSystemControlVO.class.getSimpleName() + "[", "]")
diff --git a/user-service/src/main/java/com/mh/user/job/S7PlcCollectionJob.java b/user-service/src/main/java/com/mh/user/job/S7PlcCollectionJob.java
new file mode 100644
index 0000000..75abfb7
--- /dev/null
+++ b/user-service/src/main/java/com/mh/user/job/S7PlcCollectionJob.java
@@ -0,0 +1,337 @@
+package com.mh.user.job;
+
+import com.mh.user.constants.Constant;
+import com.mh.user.entity.CollectionParamsManageEntity;
+import com.mh.user.entity.GatewayManageEntity;
+import com.mh.user.mapper.CollectionParamsManageMapper;
+import com.mh.user.mapper.GatewayManageMapper;
+import com.mh.user.s7.S7ConnectorUtil;
+import com.mh.user.utils.DateUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * S7 PLC定时采集任务
+ * 支持M、VB、VW、VD等地址类型的读写操作
+ *
+ * @author System
+ * @date 2026-06-23
+ */
+@Slf4j
+@Component
+public class S7PlcCollectionJob {
+
+ private final GatewayManageMapper gatewayManageMapper;
+ private final CollectionParamsManageMapper collectionParamsManageMapper;
+
+ // 缓存S7连接器,避免频繁创建连接
+ private static final Map connectorCache = new ConcurrentHashMap<>();
+
+ public S7PlcCollectionJob(GatewayManageMapper gatewayManageMapper,
+ CollectionParamsManageMapper collectionParamsManageMapper) {
+ this.gatewayManageMapper = gatewayManageMapper;
+ this.collectionParamsManageMapper = collectionParamsManageMapper;
+ }
+
+ /**
+ * 定时采集S7 PLC数据
+ * 每5分钟执行一次,可根据实际需求调整
+ * 优先处理手动操作:如果Constant.WEB_PLC_FLAG为true,则跳过本次采集
+ */
+ @Scheduled(cron = "0 0/5 * * * ?")
+ public void collectS7Data() {
+ log.info("------S7 PLC定时采集开始>>>>Constant.FLAG=={}------", Constant.PLC_FLAG);
+
+ try {
+ // 检查是否有手动操作正在进行
+ if (Constant.PLC_FLAG || Constant.WEB_PLC_FLAG) {
+ log.info("存在手动操作,跳过本次S7 PLC采集");
+ return;
+ }
+
+ Constant.PLC_FLAG = true;
+
+ // 查询所有在线的S7网关
+ List s7Gateways = gatewayManageMapper.queryS7Gateways();
+ if (s7Gateways == null || s7Gateways.isEmpty()) {
+ log.info("未找到在线的S7网关");
+ return;
+ }
+
+ log.info("找到{}个在线S7网关", s7Gateways.size());
+
+ // 遍历每个S7网关
+ for (GatewayManageEntity gateway : s7Gateways) {
+ try {
+ processGateway(gateway);
+ } catch (Exception e) {
+ log.error("处理S7网关异常: gatewayName={}, dataCom={}",
+ gateway.getGatewayName(), gateway.getDataCom(), e);
+ }
+ }
+
+ } catch (Exception e) {
+ log.error("S7 PLC定时采集异常", e);
+ } finally {
+ Constant.PLC_FLAG = false;
+ log.info("------S7 PLC定时采集结束>>>>Constant.FLAG=={}------", Constant.PLC_FLAG);
+ }
+ }
+
+ /**
+ * 处理单个S7网关的数据采集
+ */
+ private void processGateway(GatewayManageEntity gateway) {
+ String dataCom = gateway.getDataCom();
+ if (dataCom == null || dataCom.isEmpty()) {
+ log.warn("网关dataCom为空: {}", gateway.getGatewayName());
+ return;
+ }
+
+ // 获取或创建S7连接器
+ S7ConnectorUtil connector = getOrCreateConnector(gateway);
+ if (connector == null) {
+ log.error("无法创建S7连接器: {}", gateway.getGatewayName());
+ return;
+ }
+
+ // 查询该网关对应的采集参数
+ List params = collectionParamsManageMapper.selectCPMByDataCom(dataCom);
+ if (params == null || params.isEmpty()) {
+ log.info("网关{}没有配置采集参数", gateway.getGatewayName());
+ return;
+ }
+
+ log.info("开始采集网关{}的{}个点位", gateway.getGatewayName(), params.size());
+
+ // 过滤掉重复的采集参数
+ params = params.stream().distinct().collect(Collectors.toList());
+ String dateStr = DateUtil.dateToString(new Date(), "yyyy-MM-dd HH:mm:ss");
+
+ // 遍历采集参数并读取数据
+ for (CollectionParamsManageEntity param : params) {
+ try {
+ // 再次检查是否有手动操作
+ if (Constant.WEB_PLC_FLAG) {
+ log.info("检测到手动操作,中断采集");
+ break;
+ }
+
+ readAndSaveData(connector, param, dateStr);
+
+ } catch (Exception e) {
+ log.error("采集点位异常: registerAddr={}, otherName={}",
+ param.getRegisterAddr(), param.getOtherName(), e);
+ }
+ }
+ }
+
+ /**
+ * 读取并保存数据
+ */
+ private void readAndSaveData(S7ConnectorUtil connector,
+ CollectionParamsManageEntity param,
+ String dateStr) {
+ String registerAddr = param.getRegisterAddr();
+ if (registerAddr == null || registerAddr.isEmpty()) {
+ log.warn("点位寄存器地址为空: id={}", param.getId());
+ return;
+ }
+
+ // 读取数据
+ Object value = connector.readData(registerAddr);
+ if (value == null) {
+ log.warn("读取数据为空: registerAddr={}", registerAddr);
+ return;
+ }
+
+ // 转换值为BigDecimal
+ BigDecimal curValue;
+ try {
+ // BigDecimal 构造函数可以直接处理 String 和 Number.toString()
+ curValue = new BigDecimal(value.toString());
+ } catch (NumberFormatException e) {
+ log.warn("无法转换为数字: registerAddr={}, value={}", "", value);
+ return;
+ }
+
+ // 应用倍率和初始值
+ if (param.getMtRatio() != null && param.getMtRatio() != 1) {
+ curValue = curValue.multiply(new BigDecimal(param.getMtRatio()));
+ }
+ if (param.getMtInitValue() != null) {
+ curValue = curValue.add(param.getMtInitValue());
+ }
+
+ // 应用小数点位数
+ if (param.getDigits() != null && param.getDigits() > 0) {
+ curValue = curValue.setScale(param.getDigits(), BigDecimal.ROUND_HALF_UP);
+ }
+
+ // 更新数据库
+ collectionParamsManageMapper.updateCollectionParamsManage(
+ param.getDeviceInstallId().intValue(),
+ registerAddr,
+ curValue.toString(),
+ dateStr,
+ param.getBuildingId() != null ? param.getBuildingId().toString() : null
+ );
+
+ log.debug("采集成功: registerAddr={}, value={}, otherName={}",
+ registerAddr, curValue, param.getOtherName());
+ }
+
+ /**
+ * 获取或创建S7连接器
+ */
+ private S7ConnectorUtil getOrCreateConnector(GatewayManageEntity gateway) {
+ String cacheKey = gateway.getDataCom();
+
+ // 从缓存中获取
+ S7ConnectorUtil connector = connectorCache.get(cacheKey);
+ if (connector != null) {
+ return connector;
+ }
+
+ // 创建新连接器
+ try {
+ String ipAddress = gateway.getGatewayIP();
+ if (ipAddress == null || ipAddress.isEmpty()) {
+ log.error("网关IP地址为空: {}", gateway.getGatewayName());
+ return null;
+ }
+
+ // 解析rack和slot,默认为0和1
+ int rack = 0;
+ int slot = 1;
+ if (gateway.getGatewayPort() != null && !gateway.getGatewayPort().isEmpty()) {
+ try {
+ String[] parts = gateway.getGatewayPort().split(",");
+ if (parts.length >= 2) {
+ rack = Integer.parseInt(parts[0].trim());
+ slot = Integer.parseInt(parts[1].trim());
+ }
+ } catch (NumberFormatException e) {
+ log.warn("解析rack/slot失败,使用默认值: {}", gateway.getGatewayPort());
+ }
+ }
+
+ connector = new S7ConnectorUtil(ipAddress, rack, slot);
+ connectorCache.put(cacheKey, connector);
+ log.info("创建S7连接器成功: IP={}, rack={}, slot={}", ipAddress, rack, slot);
+ return connector;
+
+ } catch (Exception e) {
+ log.error("创建S7连接器失败: {}", gateway.getGatewayName(), e);
+ return null;
+ }
+ }
+
+ /**
+ * 手动写入数据到PLC(供Controller调用)
+ *
+ * @param cpmId 采集参数ID
+ * @param value 要写入的值
+ * @return 是否成功
+ */
+ public boolean writeData(Long cpmId, Object value) {
+ try {
+ // 查询采集参数
+ CollectionParamsManageEntity param = collectionParamsManageMapper.selectById(cpmId.toString());
+ if (param == null) {
+ log.error("采集参数不存在: cpmId={}", cpmId);
+ return false;
+ }
+
+ // 查询网关信息
+ String dataCom = getDataComByDeviceId(param.getDeviceInstallId());
+ if (dataCom == null) {
+ log.error("无法获取设备对应的dataCom: deviceInstallId={}", param.getDeviceInstallId());
+ return false;
+ }
+
+ GatewayManageEntity gateway = getGatewayByDataCom(dataCom);
+ if (gateway == null) {
+ log.error("无法获取网关信息: dataCom={}", dataCom);
+ return false;
+ }
+
+ // 获取连接器
+ S7ConnectorUtil connector = getOrCreateConnector(gateway);
+ if (connector == null) {
+ log.error("无法获取S7连接器");
+ return false;
+ }
+
+ // 写入数据
+ connector.writeData(param.getRegisterAddr(), value);
+
+ // 更新数据库
+ String dateStr = DateUtil.dateToString(new Date(), "yyyy-MM-dd HH:mm:ss");
+// BigDecimal curValue;
+// if (value instanceof Number) {
+// curValue = new BigDecimal(value.toString());
+// } else {
+// curValue = new BigDecimal(value.toString());
+// }
+
+ collectionParamsManageMapper.updateCollectionParamsManageById(
+ cpmId,
+ value.toString(),
+ dateStr
+ );
+
+ log.info("手动写入成功: cpmId={}, registerAddr={}, value={}", cpmId, param.getRegisterAddr(), value);
+ return true;
+
+ } catch (Exception e) {
+ log.error("手动写入失败: cpmId={}", cpmId, e);
+ return false;
+ }
+ }
+
+ /**
+ * 根据deviceInstallId获取dataCom
+ */
+ private String getDataComByDeviceId(Long deviceInstallId) {
+ return gatewayManageMapper.getDataComByDeviceInstallId(deviceInstallId);
+ }
+
+ /**
+ * 根据dataCom获取网关信息
+ */
+ private GatewayManageEntity getGatewayByDataCom(String dataCom) {
+ List gateways = gatewayManageMapper.queryS7Gateways();
+ if (gateways != null) {
+ for (GatewayManageEntity gw : gateways) {
+ if (dataCom.equals(gw.getDataCom())) {
+ return gw;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 清理连接器缓存(可选,用于重启或维护)
+ */
+ public void clearConnectorCache() {
+ for (Map.Entry entry : connectorCache.entrySet()) {
+ try {
+ entry.getValue().disconnect();
+ } catch (Exception e) {
+ log.error("断开S7连接异常: {}", entry.getKey(), e);
+ }
+ }
+ connectorCache.clear();
+ log.info("S7连接器缓存已清理");
+ }
+}
diff --git a/user-service/src/main/java/com/mh/user/job/StartOrStopHotpumpJob.java b/user-service/src/main/java/com/mh/user/job/StartOrStopHotpumpJob.java
index 5a61a09..6d6f2a6 100644
--- a/user-service/src/main/java/com/mh/user/job/StartOrStopHotpumpJob.java
+++ b/user-service/src/main/java/com/mh/user/job/StartOrStopHotpumpJob.java
@@ -40,7 +40,7 @@ public class StartOrStopHotpumpJob {
this.deviceControlService = deviceControlService;
}
- @Scheduled(cron = "0 0/1 * * * ?")
+// @Scheduled(cron = "0 0/1 * * * ?")
public void startOrStopHotpump() {
log.info("定时开关机热泵开始");
// 查询定时时间
diff --git a/user-service/src/main/java/com/mh/user/mapper/CollectionParamsManageMapper.java b/user-service/src/main/java/com/mh/user/mapper/CollectionParamsManageMapper.java
index d7648d5..fe4cd77 100644
--- a/user-service/src/main/java/com/mh/user/mapper/CollectionParamsManageMapper.java
+++ b/user-service/src/main/java/com/mh/user/mapper/CollectionParamsManageMapper.java
@@ -380,4 +380,48 @@ public interface CollectionParamsManageMapper extends BaseMapper selectHotWaterByDeviceInstallId(String buildingId, Integer deviceInstallId);
+
+ /**
+ * 根据dataCom查询对应的采集参数列表(S7协议使用)
+ * @param dataCom 通讯口
+ * @return 采集参数列表
+ */
+ @Select("SELECT cpm.* FROM collection_params_manage cpm " +
+ "JOIN device_install di ON cpm.device_install_id = di.id " +
+ "WHERE di.data_com = #{dataCom} AND cpm.is_use = 1 " +
+ "ORDER BY cpm.device_install_id, cpm.order_num")
+ @Results({
+ @Result(column = "id", property = "id"),
+ @Result(column = "create_time", property = "createTime"),
+ @Result(column = "update_time", property = "updateTime"),
+ @Result(column = "device_install_id", property = "deviceInstallId"),
+ @Result(column = "register_addr", property = "registerAddr"),
+ @Result(column = "func_code", property = "funcCode"),
+ @Result(column = "mt_ratio", property = "mtRatio"),
+ @Result(column = "mt_init_value", property = "mtInitValue"),
+ @Result(column = "digits", property = "digits"),
+ @Result(column = "data_type", property = "dataType"),
+ @Result(column = "cur_value", property = "curValue"),
+ @Result(column = "cur_time", property = "curTime"),
+ @Result(column = "mt_is_sum", property = "mtIsSum"),
+ @Result(column = "unit", property = "unit"),
+ @Result(column = "order_num", property = "orderNum"),
+ @Result(column = "remark", property = "remark"),
+ @Result(column = "register_size", property = "registerSize"),
+ @Result(column = "is_use", property = "isUse"),
+ @Result(column = "other_name", property = "otherName"),
+ @Result(column = "grade", property = "grade"),
+ @Result(column = "param_type_id", property = "paramTypeId"),
+ @Result(column = "param_type_group_id", property = "paramTypeGroupId"),
+ @Result(column = "collection_type", property = "collectionType"),
+ @Result(column = "quality", property = "quality"),
+ @Result(column = "building_id", property = "buildingId")
+ })
+ List selectCPMByDataCom(String dataCom);
+
+ @Update("update collection_params_manage set cur_value = #{value}, cur_time = #{dateStr} where id = #{cpmId} ")
+ void updateCollectionParamsManageById(@Param("cpmId") Long cpmId,
+ @Param("value") String value,
+ @Param("dateStr") String dateStr);
+
}
diff --git a/user-service/src/main/java/com/mh/user/mapper/GatewayManageMapper.java b/user-service/src/main/java/com/mh/user/mapper/GatewayManageMapper.java
index 508f1e2..4593ab1 100644
--- a/user-service/src/main/java/com/mh/user/mapper/GatewayManageMapper.java
+++ b/user-service/src/main/java/com/mh/user/mapper/GatewayManageMapper.java
@@ -121,4 +121,38 @@ public interface GatewayManageMapper {
@Select("select top 1 building_id from device_install di join gateway_manage gm on di.data_com = gm.data_com and gm.sn = #{sn}")
String queryBuildingIdBySn(String sn);
+
+ /**
+ * 根据deviceInstallId查询对应的dataCom
+ * @param deviceInstallId 设备安装ID
+ * @return dataCom
+ */
+ @Select("select top 1 di.data_com from device_install di where di.id = #{deviceInstallId}")
+ String getDataComByDeviceInstallId(@Param("deviceInstallId") Long deviceInstallId);
+
+ /**
+ * 查询所有S7类型的网关
+ * @return S7网关列表
+ */
+ @Select("select * from gateway_manage where community_type = 'S7' and grade = '1'")
+ @Results(id="s7Rs",value = {
+ @Result(column = "id", property = "id"),
+ @Result(column = "gateway_name", property = "gatewayName"),
+ @Result(column = "gateway_ip", property = "gatewayIP"),
+ @Result(column = "gateway_address", property = "gatewayAddress"),
+ @Result(column = "gateway_port", property = "gatewayPort"),
+ @Result(column = "grade", property = "grade"),
+ @Result(column = "internet_card", property = "internetCard"),
+ @Result(column = "operator", property = "operator"),
+ @Result(column = "create_date", property = "createDate"),
+ @Result(column = "connect_date", property = "connectDate"),
+ @Result(column = "remarks", property = "remarks"),
+ @Result(column = "heart_beat", property = "heartBeat"),
+ @Result(column = "imei", property = "imei"),
+ @Result(column = "sn", property = "sn"),
+ @Result(column = "data_com", property = "dataCom"),
+ @Result(column = "thread", property = "thread"),
+ @Result(column = "community_type", property = "communityType")
+ })
+ List queryS7Gateways();
}
diff --git a/user-service/src/main/java/com/mh/user/s7/S7ConnectorUtil.java b/user-service/src/main/java/com/mh/user/s7/S7ConnectorUtil.java
new file mode 100644
index 0000000..e74082d
--- /dev/null
+++ b/user-service/src/main/java/com/mh/user/s7/S7ConnectorUtil.java
@@ -0,0 +1,543 @@
+package com.mh.user.s7;
+
+import com.github.s7connector.api.DaveArea;
+import com.github.s7connector.api.S7Connector;
+import com.github.s7connector.api.factory.S7ConnectorFactory;
+import com.mh.user.utils.ExchangeStringUtil;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+
+import java.math.BigDecimal;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * S7 PLC通信工具类
+ * 支持M、VB、VW、VD等地址类型的读写操作
+ */
+@Slf4j
+public class S7ConnectorUtil {
+
+ private S7Connector connector;
+ private String ipAddress;
+ private int rack;
+ private int slot;
+
+ /**
+ * 构造函数 - 初始化S7连接
+ *
+ * @param ipAddress PLC IP地址
+ * @param rack 机架号(通常为0)
+ * @param slot 插槽号(通常为1)
+ */
+ public S7ConnectorUtil(String ipAddress, int rack, int slot) {
+ this.ipAddress = ipAddress;
+ this.rack = rack;
+ this.slot = slot;
+ connect();
+ }
+
+ /**
+ * 建立S7连接
+ */
+ public void connect() {
+ try {
+ if (connector != null) {
+ disconnect();
+ }
+ connector = S7ConnectorFactory
+ .buildTCPConnector()
+ .withHost(ipAddress)
+ .withRack(rack)
+ .withSlot(slot)
+ .build();
+ log.info("S7 PLC连接成功: {} (rack={}, slot={})", ipAddress, rack, slot);
+ } catch (Exception e) {
+ log.error("S7 PLC连接失败: {}", ipAddress, e);
+ throw new RuntimeException("S7 PLC连接失败", e);
+ }
+ }
+
+ /**
+ * 断开S7连接
+ */
+ public void disconnect() {
+ try {
+ if (connector != null) {
+ connector.close();
+ log.info("S7 PLC连接已关闭: {}", ipAddress);
+ }
+ } catch (Exception e) {
+ log.error("S7 PLC断开连接异常: {}", ipAddress, e);
+ }
+ }
+
+ /**
+ * 根据寄存器地址读取数据
+ *
+ * @param registerAddr 寄存器地址,如 M0.1, VB12, VW314, VD11
+ * @return 读取的值
+ */
+ public Object readData(String registerAddr) {
+ try {
+ AddressInfo addressInfo = parseAddress(registerAddr);
+ if (addressInfo == null) {
+ log.error("无法解析寄存器地址: {}", registerAddr);
+ return null;
+ }
+
+ byte[] data = null;
+ Object result = null;
+
+ switch (addressInfo.getArea()) {
+ case "M":
+ // M区是位存储区(Merkers/Flags),按字节读取
+ data = connector.read(DaveArea.FLAGS, 0, 1, addressInfo.getByteOffset());
+ if (data == null || data.length == 0) {
+ log.warn("读取M区数据为空: addr={}", registerAddr);
+ return null;
+ }
+ log.info("读取M区: addr={}, data=[{}], hex={}",
+ registerAddr, data[0] & 0xFF, bytesToHex(data));
+ if (addressInfo.isBit()) {
+ // 如果是位地址,提取指定位
+ boolean bitValue = getBit(data[0], addressInfo.getBitOffset());
+ result = bitValue ? 1 : 0;
+ } else {
+ result = data[0] & 0xFF;
+ }
+ break;
+
+ case "I":
+ // I区是输入区(Inputs),使用INPUTS
+ data = connector.read(DaveArea.INPUTS, 0, 1, addressInfo.getByteOffset());
+ if (data == null || data.length == 0) {
+ log.warn("读取I区数据为空: addr={}, 可能PLC未配置该输入点", registerAddr);
+ return 0; // 输入区无数据时返回0
+ }
+ log.info("读取I区: addr={}, data=[{}], hex={}",
+ registerAddr, data[0] & 0xFF, bytesToHex(data));
+ if (addressInfo.isBit()) {
+ boolean bitValue = getBit(data[0], addressInfo.getBitOffset());
+ result = bitValue ? 1 : 0;
+ } else {
+ result = data[0] & 0xFF;
+ }
+ break;
+
+ case "Q":
+ // Q区是输出区(Outputs),使用OUTPUTS
+ data = connector.read(DaveArea.OUTPUTS, 0, 1, addressInfo.getByteOffset());
+ if (data == null || data.length == 0) {
+ log.warn("读取Q区数据为空: addr={}, 可能PLC未配置该输出点", registerAddr);
+ return 0; // 输出区无数据时返回0
+ }
+ log.info("读取Q区: addr={}, data=[{}], hex={}",
+ registerAddr, data[0] & 0xFF, bytesToHex(data));
+ if (addressInfo.isBit()) {
+ boolean bitValue = getBit(data[0], addressInfo.getBitOffset());
+ result = bitValue ? 1 : 0;
+ } else {
+ result = data[0] & 0xFF;
+ }
+ break;
+
+ case "VB":
+ // V区字节 (DB块)
+ data = connector.read(DaveArea.DB, 1, 1, addressInfo.getByteOffset());
+ if (data == null || data.length == 0) {
+ log.warn("读取VB区数据为空: addr={}", registerAddr);
+ return null;
+ }
+ String string = bytesToHex(data).replace(" ", "");
+ result = ExchangeStringUtil.hexToDec(string.substring(string.length() - 2));
+ log.info("读取VB区: addr={}, data=[{}], hex={}",
+ registerAddr, data[0] & 0xFF, string);
+ break;
+
+ case "VW":
+ // V区字(2字节) - 大端序
+ data = connector.read(DaveArea.DB, 1, 2, addressInfo.getByteOffset());
+ if (data == null || data.length < 2) {
+ log.warn("读取VW区数据为空或长度不足: addr={}", registerAddr);
+ return null;
+ }
+ String s = bytesToHex(data).replace(" ", "");
+ // bytesToHex(data)获取最右边的2个字节
+ result = ExchangeStringUtil.hexToDec(s.substring(s.length() - 4));
+ log.info("读取VW区: addr={}, data=[{}, {}], hex={}, value={}",
+ registerAddr, data[0] & 0xFF, data[1] & 0xFF, s, result);
+ break;
+
+ case "VD":
+ // V区双字(4字节) - 大端序浮点数
+ data = connector.read(DaveArea.DB, 1, 4, addressInfo.getByteOffset());
+ if (data == null || data.length < 4) {
+ log.warn("读取VD区数据为空或长度不足: addr={}", registerAddr);
+ return null;
+ }
+ String hex = bytesToHex(data).replace(" ", "");
+ result = ExchangeStringUtil.hexToSingle(hex.substring(hex.length() - 8));
+ log.info("读取VD区: addr={}, data=[{}, {}, {}, {}], hex={}, value={}",
+ registerAddr, data[0] & 0xFF, data[1] & 0xFF, data[2] & 0xFF, data[3] & 0xFF,
+ hex, result);
+ break;
+
+ case "AIW":
+ // 模拟量输入字(2字节),只读,使用INPUTS - 大端序
+ data = connector.read(DaveArea.INPUTS, 0, 2, addressInfo.getByteOffset());
+ if (data == null || data.length < 2) {
+ log.warn("读取AIW区数据为空或长度不足: addr={}", registerAddr);
+ return 0;
+ }
+ result = bytesToWord(data);
+ log.info("读取AIW区: addr={}, data=[{}, {}], hex={}, value={}",
+ registerAddr, data[0] & 0xFF, data[1] & 0xFF, bytesToHex(data), result);
+ break;
+
+ case "AQW":
+ // 模拟量输出字(2字节),可读写,使用OUTPUTS - 大端序
+ data = connector.read(DaveArea.OUTPUTS, 0, 2, addressInfo.getByteOffset());
+ if (data == null || data.length < 2) {
+ log.warn("读取AQW区数据为空或长度不足: addr={}", registerAddr);
+ return 0;
+ }
+ String aqwStr = bytesToHex(data).replace(" ","");
+ result = ExchangeStringUtil.hexToDec(aqwStr.substring(aqwStr.length() - 4));
+ log.info("读取AQW区: addr={}, data=[{}, {}], hex={}, value={}",
+ registerAddr, data[0] & 0xFF, data[1] & 0xFF, aqwStr, result);
+ break;
+
+ default:
+ log.error("不支持的地址类型: {}", addressInfo.getArea());
+ return null;
+ }
+
+ log.info("读取成功: addr={}, value={}", registerAddr, result);
+ return result;
+
+ } catch (Exception e) {
+ log.error("读取S7数据失败: {}", registerAddr, e);
+ return null;
+ }
+ }
+
+
+ /**
+ * 根据寄存器地址写入数据
+ *
+ * @param registerAddr 寄存器地址,如 M0.1, VB12, VW314, VD11
+ * @param value 要写入的值
+ */
+ public void writeData(String registerAddr, Object value) {
+ try {
+ AddressInfo addressInfo = parseAddress(registerAddr);
+ if (addressInfo == null) {
+ log.error("无法解析寄存器地址: {}", registerAddr);
+ return;
+ }
+
+ switch (addressInfo.getArea()) {
+ case "M":
+ if (addressInfo.isBit()) {
+ // 写入位
+ int intValue = value instanceof Number ? ((Number) value).intValue() : Integer.parseInt(value.toString());
+ writeBit(DaveArea.FLAGS, 0, addressInfo.getByteOffset(), addressInfo.getBitOffset(), intValue != 0);
+ } else {
+ // 写入字节
+ int intValue = value instanceof Number ? ((Number) value).intValue() : Integer.parseInt(value.toString());
+ byte[] data = {(byte) (intValue & 0xFF)};
+ connector.write(DaveArea.FLAGS, 0, addressInfo.getByteOffset(), data);
+ }
+ break;
+ case "I":
+ // I区是输入区,通常只读,不建议写入
+ log.warn("I区是输入区,通常只读,不建议写入: {}", registerAddr);
+ if (addressInfo.isBit()) {
+ int intValue = value instanceof Number ? ((Number) value).intValue() : Integer.parseInt(value.toString());
+ writeBit(DaveArea.INPUTS, 0, addressInfo.getByteOffset(), addressInfo.getBitOffset(), intValue != 0);
+ } else {
+ int intValue = value instanceof Number ? ((Number) value).intValue() : Integer.parseInt(value.toString());
+ byte[] data = {(byte) (intValue & 0xFF)};
+ connector.write(DaveArea.INPUTS, 0, addressInfo.getByteOffset(), data);
+ }
+ break;
+ case "Q":
+ // Q区是输出区,可读写
+ if (addressInfo.isBit()) {
+ int intValue = value instanceof Number ? ((Number) value).intValue() : Integer.parseInt(value.toString());
+ writeBit(DaveArea.OUTPUTS, 0, addressInfo.getByteOffset(), addressInfo.getBitOffset(), intValue != 0);
+ } else {
+ int intValue = value instanceof Number ? ((Number) value).intValue() : Integer.parseInt(value.toString());
+ byte[] data = {(byte) (intValue & 0xFF)};
+ connector.write(DaveArea.OUTPUTS, 0, addressInfo.getByteOffset(), data);
+ }
+ break;
+ case "VB":
+ // 写入字节
+ int vbValue = value instanceof Number ? ((Number) value).intValue() : Integer.parseInt(value.toString());
+ byte[] vbData = {(byte) (vbValue & 0xFF)};
+ connector.write(DaveArea.DB, 1, addressInfo.getByteOffset(), vbData);
+ break;
+ case "VW":
+ // 写入字(2字节)
+ int vwValue = value instanceof Number ? ((Number) value).intValue() : Integer.parseInt(value.toString());
+ byte[] vwData = wordToBytes(vwValue);
+ connector.write(DaveArea.DB, 1, addressInfo.getByteOffset(), vwData);
+ break;
+ case "VD":
+ // 写入双字(4字节)
+ float vdValue;
+ if (value instanceof Number) {
+ vdValue = ((Number) value).floatValue();
+ } else {
+ vdValue = Float.parseFloat(value.toString());
+ }
+ byte[] vdData = dwordToBytes(vdValue);
+ connector.write(DaveArea.DB, 1, addressInfo.getByteOffset(), vdData);
+ break;
+ case "AIW":
+ // AIW是模拟量输入,通常只读,不建议写入
+ log.warn("AIW是模拟量输入,通常只读,不建议写入: {}", registerAddr);
+ int aiwValue = value instanceof Number ? ((Number) value).intValue() : Integer.parseInt(value.toString());
+ byte[] aiwData = wordToBytes(aiwValue);
+ connector.write(DaveArea.INPUTS, 0, addressInfo.getByteOffset(), aiwData);
+ break;
+ case "AQW":
+ // AQW是模拟量输出,可读写
+ int aqwValue = value instanceof Number ? ((Number) value).intValue() : Integer.parseInt(value.toString());
+ byte[] aqwData = wordToBytes(aqwValue);
+ connector.write(DaveArea.OUTPUTS, 0, addressInfo.getByteOffset(), aqwData);
+ break;
+ default:
+ log.error("不支持的地址类型: {}", addressInfo.getArea());
+ }
+ log.info("写入S7数据成功: {} = {}", registerAddr, value);
+ } catch (Exception e) {
+ log.error("写入S7数据失败: {}", registerAddr, e);
+ throw new RuntimeException("写入S7数据失败", e);
+ }
+ }
+
+ /**
+ * 解析寄存器地址
+ *
+ * @param registerAddr 寄存器地址字符串
+ * @return 地址信息对象
+ */
+ private AddressInfo parseAddress(String registerAddr) {
+ if (registerAddr == null || registerAddr.isEmpty()) {
+ return null;
+ }
+
+ AddressInfo info = new AddressInfo();
+
+ // 匹配纯M地址 (如 M0, M10)
+ Pattern mBytePattern = Pattern.compile("^M(\\d+)$");
+ Matcher mByteMatcher = mBytePattern.matcher(registerAddr.toUpperCase());
+ if (mByteMatcher.find()) {
+ info.setArea("M");
+ info.setByteOffset(Integer.parseInt(mByteMatcher.group(1)));
+ info.setBit(false);
+ return info;
+ }
+
+ // 匹配M位地址 (如 M0.1, M10.5)
+ Pattern mBitPattern = Pattern.compile("^M(\\d+)\\.(\\d+)$");
+ Matcher mBitMatcher = mBitPattern.matcher(registerAddr.toUpperCase());
+ if (mBitMatcher.find()) {
+ info.setArea("M");
+ info.setByteOffset(Integer.parseInt(mBitMatcher.group(1)));
+ info.setBitOffset(Integer.parseInt(mBitMatcher.group(2)));
+ info.setBit(true);
+ return info;
+ }
+
+ // 匹配I字节地址 (如 I0, I1)
+ Pattern iBytePattern = Pattern.compile("^I(\\d+)$");
+ Matcher iByteMatcher = iBytePattern.matcher(registerAddr.toUpperCase());
+ if (iByteMatcher.find()) {
+ info.setArea("I");
+ info.setByteOffset(Integer.parseInt(iByteMatcher.group(1)));
+ info.setBit(false);
+ return info;
+ }
+
+ // 匹配I位地址 (如 I0.1, I1.5)
+ Pattern iBitPattern = Pattern.compile("^I(\\d+)\\.(\\d+)$");
+ Matcher iBitMatcher = iBitPattern.matcher(registerAddr.toUpperCase());
+ if (iBitMatcher.find()) {
+ info.setArea("I");
+ info.setByteOffset(Integer.parseInt(iBitMatcher.group(1)));
+ info.setBitOffset(Integer.parseInt(iBitMatcher.group(2)));
+ info.setBit(true);
+ return info;
+ }
+
+ // 匹配Q字节地址 (如 Q0, Q1)
+ Pattern qBytePattern = Pattern.compile("^Q(\\d+)$");
+ Matcher qByteMatcher = qBytePattern.matcher(registerAddr.toUpperCase());
+ if (qByteMatcher.find()) {
+ info.setArea("Q");
+ info.setByteOffset(Integer.parseInt(qByteMatcher.group(1)));
+ info.setBit(false);
+ return info;
+ }
+
+ // 匹配Q位地址 (如 Q0.1, Q1.5)
+ Pattern qBitPattern = Pattern.compile("^Q(\\d+)\\.(\\d+)$");
+ Matcher qBitMatcher = qBitPattern.matcher(registerAddr.toUpperCase());
+ if (qBitMatcher.find()) {
+ info.setArea("Q");
+ info.setByteOffset(Integer.parseInt(qBitMatcher.group(1)));
+ info.setBitOffset(Integer.parseInt(qBitMatcher.group(2)));
+ info.setBit(true);
+ return info;
+ }
+
+ // 匹配VB地址 (如 VB12)
+ Pattern vbPattern = Pattern.compile("^VB(\\d+)$");
+ Matcher vbMatcher = vbPattern.matcher(registerAddr.toUpperCase());
+ if (vbMatcher.find()) {
+ info.setArea("VB");
+ info.setByteOffset(Integer.parseInt(vbMatcher.group(1)));
+ info.setBit(false);
+ return info;
+ }
+
+ // 匹配VW地址 (如 VW314)
+ Pattern vwPattern = Pattern.compile("^VW(\\d+)$");
+ Matcher vwMatcher = vwPattern.matcher(registerAddr.toUpperCase());
+ if (vwMatcher.find()) {
+ info.setArea("VW");
+ info.setByteOffset(Integer.parseInt(vwMatcher.group(1)));
+ info.setBit(false);
+ return info;
+ }
+
+ // 匹配VD地址 (如 VD11)
+ Pattern vdPattern = Pattern.compile("^VD(\\d+)$");
+ Matcher vdMatcher = vdPattern.matcher(registerAddr.toUpperCase());
+ if (vdMatcher.find()) {
+ info.setArea("VD");
+ info.setByteOffset(Integer.parseInt(vdMatcher.group(1)));
+ info.setBit(false);
+ return info;
+ }
+
+ // 匹配AIW地址 (模拟量输入字,如 AIW0, AIW64)
+ Pattern aiwPattern = Pattern.compile("^AIW(\\d+)$");
+ Matcher aiwMatcher = aiwPattern.matcher(registerAddr.toUpperCase());
+ if (aiwMatcher.find()) {
+ info.setArea("AIW");
+ info.setByteOffset(Integer.parseInt(aiwMatcher.group(1)));
+ info.setBit(false);
+ return info;
+ }
+
+ // 匹配AQW地址 (模拟量输出字,如 AQW0, AQW64)
+ Pattern aqwPattern = Pattern.compile("^AQW(\\d+)$");
+ Matcher aqwMatcher = aqwPattern.matcher(registerAddr.toUpperCase());
+ if (aqwMatcher.find()) {
+ info.setArea("AQW");
+ info.setByteOffset(Integer.parseInt(aqwMatcher.group(1)));
+ info.setBit(false);
+ return info;
+ }
+
+ log.error("无法识别的寄存器地址格式: {}", registerAddr);
+ return null;
+ }
+
+ /**
+ * 从字节中获取指定位的值
+ */
+ private boolean getBit(byte data, int bitPosition) {
+ return ((data >> bitPosition) & 0x01) == 1;
+ }
+
+ /**
+ * 字节数组转十六进制字符串(用于调试)
+ */
+ private String bytesToHex(byte[] data) {
+ if (data == null || data.length == 0) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder();
+ for (byte b : data) {
+ sb.append(String.format("%02X ", b & 0xFF));
+ }
+ return sb.toString().trim();
+ }
+
+ /**
+ * 写入指定位的值
+ */
+ private void writeBit(DaveArea area, int dbNumber, int byteOffset, int bitPosition, boolean value) throws Exception {
+ // 先读取当前字节
+ byte[] currentData = connector.read(area, dbNumber, 1, byteOffset);
+ byte currentByte = currentData[0];
+
+ // 修改指定位
+ if (value) {
+ currentByte |= (1 << bitPosition); // 置位
+ } else {
+ currentByte &= ~(1 << bitPosition); // 复位
+ }
+
+ // 写回
+ connector.write(area, dbNumber, byteOffset, new byte[]{currentByte});
+ }
+
+ /**
+ * 字节数组转字(2字节,无符号)
+ */
+ private int bytesToWord(byte[] data) {
+ return ((data[0] & 0xFF) << 8) | (data[1] & 0xFF);
+ }
+
+ /**
+ * 字转字节数组(2字节)
+ */
+ private byte[] wordToBytes(int value) {
+ return new byte[]{
+ (byte) ((value >> 8) & 0xFF),
+ (byte) (value & 0xFF)
+ };
+ }
+
+ /**
+ * 字节数组转双字(4字节,浮点数)
+ */
+ private float bytesToDWord(byte[] data) {
+ int intBits = ((data[0] & 0xFF) << 24) |
+ ((data[1] & 0xFF) << 16) |
+ ((data[2] & 0xFF) << 8) |
+ (data[3] & 0xFF);
+ return Float.intBitsToFloat(intBits);
+ }
+
+ /**
+ * 双字(浮点数)转字节数组(4字节)
+ */
+ private byte[] dwordToBytes(float value) {
+ int intBits = Float.floatToIntBits(value);
+ return new byte[]{
+ (byte) ((intBits >> 24) & 0xFF),
+ (byte) ((intBits >> 16) & 0xFF),
+ (byte) ((intBits >> 8) & 0xFF),
+ (byte) (intBits & 0xFF)
+ };
+ }
+
+ /**
+ * 地址信息内部类
+ */
+ @Data
+ private static class AddressInfo {
+ private String area; // 区域类型: M, VB, VW, VD
+ private int byteOffset; // 字节偏移
+ private int bitOffset; // 位偏移(仅M区位地址使用)
+ private boolean isBit; // 是否为位地址
+ }
+}
diff --git a/user-service/src/main/java/com/mh/user/serialport/SendAndReceiveByCom.java b/user-service/src/main/java/com/mh/user/serialport/SendAndReceiveByCom.java
index 1d0c805..ce1e5f0 100644
--- a/user-service/src/main/java/com/mh/user/serialport/SendAndReceiveByCom.java
+++ b/user-service/src/main/java/com/mh/user/serialport/SendAndReceiveByCom.java
@@ -117,13 +117,13 @@ public class SendAndReceiveByCom {
byte[] bytes = SerialTool.readFromPort(serialPort);
Date date1 = new Date();
String dateStr = DateUtil.dateToString(date1, "yyyy-MM-dd HH:mm:ss");
- if (bytes == null) {
+ if (bytes == null || bytes.length == 0) {
SerialTool.closePort(serialPort);
log.info("串口{}没有数据返回!{}", serialPort.getName(), i);
SendAndReceiveByTcp.printLog(deviceAddr, deviceType, buildingId, buildingName, dateStr, log, deviceInstallService, nowDataService, comName);
}
// 处理返回来的数据报文
- dealReceiveData(dateStr, serialPort, i, deviceAddr, deviceType, registerAddr, brand, buildingId, buildingName, bytes, device, null);
+ dealReceiveData(dateStr, serialPort, i, deviceAddr, deviceType, registerAddr, brand, buildingId, buildingName, bytes, device, deviceManageEntityList.get(i));
} catch (Exception e) {
if (null != serialPort) {
diff --git a/user-service/src/main/java/com/mh/user/service/DeviceControlService.java b/user-service/src/main/java/com/mh/user/service/DeviceControlService.java
index 839ab06..92d3bfc 100644
--- a/user-service/src/main/java/com/mh/user/service/DeviceControlService.java
+++ b/user-service/src/main/java/com/mh/user/service/DeviceControlService.java
@@ -25,4 +25,6 @@ public interface DeviceControlService {
String operationDevice(SerialPortModel params);
String getSn(SerialPortModel serialPortModel);
+
+ boolean isS7Control(List params);
}
diff --git a/user-service/src/main/java/com/mh/user/service/impl/CollectionParamsManageServiceImpl.java b/user-service/src/main/java/com/mh/user/service/impl/CollectionParamsManageServiceImpl.java
index 3fbb721..05f8879 100644
--- a/user-service/src/main/java/com/mh/user/service/impl/CollectionParamsManageServiceImpl.java
+++ b/user-service/src/main/java/com/mh/user/service/impl/CollectionParamsManageServiceImpl.java
@@ -344,6 +344,8 @@ public class CollectionParamsManageServiceImpl implements CollectionParamsManage
return createHotPumpControlVO(dlEntry, dlItems, parentDto);
case "循环泵":
return createCircuitPumpControlVO(dlEntry, dlItems, parentDto);
+ case "供水泵":
+ return createSupplyPumpControlVO(dlEntry, dlItems, parentDto);
case "回水泵":
return createBackPumpControlVO(dlEntry, dlItems, parentDto);
case "设备校准":
@@ -356,6 +358,152 @@ public class CollectionParamsManageServiceImpl implements CollectionParamsManage
}
}
+ private HotWaterSupplyPumpControlVO createSupplyPumpControlVO(
+ Map.Entry> dlEntry,
+ List dlItems,
+ HotWaterControlDTO parentDto) {
+
+ HotWaterSupplyPumpControlVO supplyPumpVo = new HotWaterSupplyPumpControlVO();
+ setupBasicDeviceInfo(supplyPumpVo, dlEntry, dlItems, parentDto);
+
+ dlItems.forEach(item -> {
+ switch (item.getParamTypeId()) {
+ case "1":
+ // 启停控制
+ supplyPumpVo.setStartStopControl(item.getCurValue().intValue());
+ supplyPumpVo.setStartStopControlId(item.getCpmId());
+ break;
+ case "2":
+ // 运行状态
+ supplyPumpVo.setRunState(item.getCurValue().intValue());
+ supplyPumpVo.setRunStateId(item.getCpmId());
+ break;
+ case "3":
+ // 故障状态
+ supplyPumpVo.setFault(item.getCurValue().intValue());
+ supplyPumpVo.setFaultId(item.getCpmId());
+ break;
+ case "4":
+ // 时控
+ handleSupplyPumpTimeParameters(supplyPumpVo, item);
+ break;
+ case "7":
+ // 温度设置
+ supplyPumpVo.setTempSet(item.getCurValue());
+ supplyPumpVo.setTempSetId(item.getCpmId());
+ break;
+ case "15":
+ // 时间
+ supplyPumpVo.setOpenDelayTime(item.getCurValue().intValue());
+ supplyPumpVo.setOpenDelayTimeId(item.getCpmId());
+ break;
+ case "16":
+ // 累积运行时间
+ if (item.getOtherName().contains("小时")) {
+ supplyPumpVo.setRunTime(item.getCurValue());
+ supplyPumpVo.setRunTimeId(item.getCpmId());
+ }
+ break;
+ case "21":
+ // 一键启动
+ supplyPumpVo.setStartOneKey(item.getCurValue().intValue());
+ supplyPumpVo.setStartOneKeyId(item.getCpmId());
+ break;
+ case "22":
+ // 手动自动切换
+ supplyPumpVo.setManualAutoSwitch(item.getCurValue().intValue());
+ supplyPumpVo.setManualAutoSwitchId(item.getCpmId());
+ break;
+ case "25":
+ // 两台回水泵启动
+ supplyPumpVo.setTwoPumpStart(item.getCurValue().intValue());
+ supplyPumpVo.setTwoPumpStartId(item.getCpmId());
+ break;
+ case "26":
+ // 温度上限
+ supplyPumpVo.setTempSetUpperLimit(item.getCurValue());
+ supplyPumpVo.setTempSetUpperLimitId(item.getCpmId());
+ break;
+ case "27":
+ // 温度下限
+ supplyPumpVo.setTempSetLowerLimit(item.getCurValue());
+ supplyPumpVo.setTempSetLowerLimitId(item.getCpmId());
+ break;
+ default:
+ break;
+ }
+ });
+ if (supplyPumpVo.getOneHourTimeOpenSetOneId() != null
+ && supplyPumpVo.getOneHourTimeCloseSetOneId() != null
+ && supplyPumpVo.getOneMinTimeOpenSetOneId() != null
+ && supplyPumpVo.getOneMinTimeCloseSetOneId() != null) {
+ // 设置时分开写入oneHourMinTimeOpenSetOneStr,oneHourMinTimeCloseSetOneStr
+ supplyPumpVo.setOneHourMinTimeOpenSetOneStr(String.format("%02d:%02d", supplyPumpVo.getOneHourTimeOpenSetOne(), supplyPumpVo.getOneMinTimeOpenSetOne()));
+ supplyPumpVo.setOneHourMinTimeCloseSetOneStr(String.format("%02d:%02d", supplyPumpVo.getOneHourTimeCloseSetOne(), supplyPumpVo.getOneMinTimeCloseSetOne()));
+ }
+ if (supplyPumpVo.getOneHourTimeOpenSetTwoId() != null
+ && supplyPumpVo.getOneHourTimeCloseSetTwoId() != null
+ && supplyPumpVo.getOneMinTimeOpenSetTwoId() != null
+ && supplyPumpVo.getOneMinTimeCloseSetTwoId() != null) {
+ // 获取时分开写入oneHourMinTimeOpenSetTwoStr,oneHourMinTimeCloseSetTwoStr
+ supplyPumpVo.setOneHourMinTimeOpenSetTwoStr(String.format("%02d:%02d", supplyPumpVo.getOneHourTimeOpenSetTwo(), supplyPumpVo.getOneMinTimeOpenSetTwo()));
+ supplyPumpVo.setOneHourMinTimeCloseSetTwoStr(String.format("%02d:%02d", supplyPumpVo.getOneHourTimeCloseSetTwo(), supplyPumpVo.getOneMinTimeCloseSetTwo()));
+ }
+ if (supplyPumpVo.getOneHourTimeOpenSetThreeId() != null
+ && supplyPumpVo.getOneHourTimeCloseSetThreeId() != null
+ && supplyPumpVo.getOneMinTimeOpenSetThreeId() != null
+ && supplyPumpVo.getOneMinTimeCloseSetThreeId() != null) {
+ // 获取时分开写入oneHourMinTimeOpenSetThreeStr,oneHourMinTimeCloseSetThreeStr
+ supplyPumpVo.setOneHourMinTimeOpenSetThreeStr(String.format("%02d:%02d", supplyPumpVo.getOneHourTimeOpenSetThree(), supplyPumpVo.getOneMinTimeOpenSetThree()));
+ supplyPumpVo.setOneHourMinTimeCloseSetThreeStr(String.format("%02d:%02d", supplyPumpVo.getOneHourTimeCloseSetThree(), supplyPumpVo.getOneMinTimeCloseSetThree()));
+ }
+ return supplyPumpVo;
+ }
+
+ private void handleSupplyPumpTimeParameters(HotWaterSupplyPumpControlVO backPumpVo, HotWaterControlListVO item) {
+ String otherName = item.getOtherName();
+ int value = item.getCurValue().intValue();
+ String cpmId = item.getCpmId();
+
+ if (otherName.contains("定时_时开1")) {
+ backPumpVo.setOneHourTimeOpenSetOne(value);
+ backPumpVo.setOneHourTimeOpenSetOneId(cpmId);
+ } else if (otherName.contains("定时_时关1")) {
+ backPumpVo.setOneHourTimeCloseSetOne(value);
+ backPumpVo.setOneHourTimeCloseSetOneId(cpmId);
+ } else if (otherName.contains("定时_分开1")) {
+ backPumpVo.setOneMinTimeOpenSetOne(value);
+ backPumpVo.setOneMinTimeOpenSetOneId(cpmId);
+ } else if (otherName.contains("定时_分关1")) {
+ backPumpVo.setOneMinTimeCloseSetOne(value);
+ backPumpVo.setOneMinTimeCloseSetOneId(cpmId);
+ } else if (otherName.contains("定时_时开2")) {
+ backPumpVo.setOneHourTimeOpenSetTwo(value);
+ backPumpVo.setOneHourTimeOpenSetTwoId(cpmId);
+ } else if (otherName.contains("定时_时关2")) {
+ backPumpVo.setOneHourTimeCloseSetTwo(value);
+ backPumpVo.setOneHourTimeCloseSetTwoId(cpmId);
+ } else if (otherName.contains("定时_分开2")) {
+ backPumpVo.setOneMinTimeOpenSetTwo(value);
+ backPumpVo.setOneMinTimeOpenSetTwoId(cpmId);
+ } else if (otherName.contains("定时_分关2")) {
+ backPumpVo.setOneMinTimeCloseSetTwo(value);
+ backPumpVo.setOneMinTimeCloseSetTwoId(cpmId);
+ } else if (otherName.contains("定时_时开3")) {
+ backPumpVo.setOneHourTimeOpenSetThree(value);
+ backPumpVo.setOneHourTimeOpenSetThreeId(cpmId);
+ } else if (otherName.contains("定时_时关3")) {
+ backPumpVo.setOneHourTimeCloseSetThree(value);
+ backPumpVo.setOneHourTimeCloseSetThreeId(cpmId);
+ } else if (otherName.contains("定时_分开3")) {
+ backPumpVo.setOneMinTimeOpenSetThree(value);
+ backPumpVo.setOneMinTimeOpenSetThreeId(cpmId);
+ } else if (otherName.contains("定时_分关3")) {
+ backPumpVo.setOneMinTimeCloseSetThree(value);
+ backPumpVo.setOneMinTimeCloseSetThreeId(cpmId);
+ }
+ }
+
private HotWaterSystemControlVO createSystemControlVO(
Map.Entry> dlEntry,
List dlItems,
@@ -367,15 +515,28 @@ public class CollectionParamsManageServiceImpl implements CollectionParamsManage
dlItems.forEach(item -> {
switch (item.getParamTypeId()) {
case "1":
+ if (item.getOtherName().contains("自动")) {
+ break;
+ }
// 时间写入控制
vo.setTimeSet(item.getCurValue().intValue());
vo.setTimeSetId(item.getCpmId());
break;
+ case "22":
+ // 校时手自动切换
+ vo.setTimeSetAuto(item.getCurValue().intValue());
+ vo.setTimeSetAutoId(item.getCpmId());
+ break;
case "5":
// 压力
vo.setPressure(item.getCurValue());
vo.setPressureId(item.getCpmId());
break;
+ case "27":
+ // 压力设置
+ vo.setPressureSet(item.getCurValue().intValue());
+ vo.setPressureSetId(item.getCpmId());
+ break;
case "6":
case "12":
// 回水温度
@@ -396,6 +557,19 @@ public class CollectionParamsManageServiceImpl implements CollectionParamsManage
// 时间
handleSystemTimeParameters(vo, item);
break;
+ case "26":
+ // 水箱高度和液位
+ if (item.getOtherName().contains("单箱液位")) {
+ vo.setSingleBoxLevel(item.getCurValue().intValue());
+ vo.setSingleBoxLevelId(item.getCpmId());
+ } else if (item.getOtherName().contains("多箱液位")) {
+ vo.setMultiBoxLevel(item.getCurValue().intValue());
+ vo.setMultiBoxLevelId(item.getCpmId());
+ } else if (item.getOtherName().contains("高度")) {
+ vo.setTankHeight(item.getCurValue().intValue());
+ vo.setTankHeightId(item.getCpmId());
+ }
+ break;
case "28":
// modbus 重置复位
vo.setReset(item.getCurValue().intValue());
@@ -470,6 +644,26 @@ public class CollectionParamsManageServiceImpl implements CollectionParamsManage
vo.setMinTimeSet(value);
vo.setMinTimeSetId(cpmId);
break;
+ case "自动_年_写":
+ vo.setYearTimeSetAuto(Integer.parseInt("20" + value));
+ vo.setYearTimeSetAutoId(cpmId);
+ break;
+ case "自动_月_写":
+ vo.setMonthTimeSetAuto(value);
+ vo.setMonthTimeSetAutoId(cpmId);
+ break;
+ case "自动_日_写":
+ vo.setDayTimeSetAuto(value);
+ vo.setDayTimeSetAutoId(cpmId);
+ break;
+ case "自动_时_写":
+ vo.setHourTimeSetAuto(value);
+ vo.setHourTimeSetAutoId(cpmId);
+ break;
+ case "自动_分_写":
+ vo.setMinTimeSetAuto(value);
+ vo.setMinTimeSetAutoId(cpmId);
+ break;
default:
break;
}
diff --git a/user-service/src/main/java/com/mh/user/service/impl/DeviceControlServiceImpl.java b/user-service/src/main/java/com/mh/user/service/impl/DeviceControlServiceImpl.java
index e0a10ab..0fd2eb6 100644
--- a/user-service/src/main/java/com/mh/user/service/impl/DeviceControlServiceImpl.java
+++ b/user-service/src/main/java/com/mh/user/service/impl/DeviceControlServiceImpl.java
@@ -70,6 +70,20 @@ public class DeviceControlServiceImpl implements DeviceControlService {
return false;
}
+ @Override
+ public boolean isS7Control(List params) {
+ if (!params.isEmpty()) {
+ for (SerialPortModel serialPortModel : params) {
+ // 判断是否是mqtt通讯
+ String communicationType = collectionParamsManageMapper.selectCommunicationType(serialPortModel.getCpmId());
+ if (Constant.COMMUNITY_TYPE_S7.equals(communicationType)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
@Override
public String operationDevice(SerialPortModel params) {
// 拼接发送的报文
diff --git a/user-service/src/main/java/com/mh/user/utils/Test.java b/user-service/src/main/java/com/mh/user/utils/Test.java
index 603bcdc..560c033 100644
--- a/user-service/src/main/java/com/mh/user/utils/Test.java
+++ b/user-service/src/main/java/com/mh/user/utils/Test.java
@@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import java.io.BufferedWriter;
import java.io.IOException;
+import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -18,6 +19,8 @@ import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* @author ljf
@@ -113,7 +116,28 @@ public class Test {
}
public static void main(String[] args) throws ParseException, IOException {
-
+ String aqwStr = "8A 00 00 3F 93 33 33 41 50 00 00 3E 5D DD DE 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 42 8A 00 00 3F 93 33 33 41 50 00 00 3E 5D DD DE 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 6C 00 15 9A 00 00 00 40 80 00 00 00 00 26 06 25 11 07 07 00 05 00 00 1A";
+ aqwStr = aqwStr.replace(" ","");
+ aqwStr = aqwStr.substring(aqwStr.length() - 4);
+ System.out.println(aqwStr);
+ String result = ExchangeStringUtil.hexToDec(aqwStr);
+ System.out.println(result);
+ // 匹配AIW地址 (模拟量输入字,如 AIW0, AIW64)
+ Pattern aiwPattern = Pattern.compile("^VD(\\d+)$");
+ Matcher aiwMatcher = aiwPattern.matcher("VD0".toUpperCase());
+ if (aiwMatcher.find()) {
+ System.out.println("VD");
+ System.out.println(Integer.parseInt(aiwMatcher.group(1)));
+ System.out.println (false);
+ }
+ Object value = "89";
+ BigDecimal curValue;
+ if (value instanceof Number) {
+ curValue = new BigDecimal(value.toString());
+ } else {
+ log.warn("不支持的数据类型: registerAddr={}, value={}", "", value);
+ return;
+ }
}
diff --git a/user-service/src/main/resources/application-dev.yml b/user-service/src/main/resources/application-dev.yml
index 6a0b034..82a03ab 100644
--- a/user-service/src/main/resources/application-dev.yml
+++ b/user-service/src/main/resources/application-dev.yml
@@ -22,7 +22,7 @@ spring:
type: com.alibaba.druid.pool.DruidDataSource
druid:
#添加allowMultiQueries=true 在批量更新时才不会出错
- url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=chws_bsdz;allowMultiQueries=true;encrypt=false
+ url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=chws_gr;allowMultiQueries=true;encrypt=false
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
username: sa
password: mh@803
diff --git a/user-service/src/main/resources/application-prod.yml b/user-service/src/main/resources/application-prod.yml
index 84a6005..1d9f994 100644
--- a/user-service/src/main/resources/application-prod.yml
+++ b/user-service/src/main/resources/application-prod.yml
@@ -75,16 +75,16 @@ spring:
# password: chws_gw@803
# # 华软江门
-# url: jdbc:sqlserver://127.0.0.1:57238;DatabaseName=chws_jm;allowMultiQueries=true;encrypt=false
-# driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
-# username: chws_jm
-# password: Mhtech@803
+ url: jdbc:sqlserver://127.0.0.1:57238;DatabaseName=chws_jm;allowMultiQueries=true;encrypt=false
+ driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
+ username: chws_jm
+ password: Mhtech@803
# 珠海北师大
- url: jdbc:sqlserver://127.0.0.1:8033;DatabaseName=chws_bsdz;allowMultiQueries=true;encrypt=false
- driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
- username: chws_bsdz
- password: Mhtech@803803
+# url: jdbc:sqlserver://127.0.0.1:8033;DatabaseName=chws_bsdz;allowMultiQueries=true;encrypt=false
+# driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
+# username: chws_bsdz
+# password: Mhtech@803803
# #南方学院
# url: jdbc:sqlserver://127.0.0.1:8033;DatabaseName=chws_nfxy;allowMultiQueries=true;encrypt=false
diff --git a/user-service/src/main/resources/application.yml b/user-service/src/main/resources/application.yml
index 081dc84..5f109b8 100644
--- a/user-service/src/main/resources/application.yml
+++ b/user-service/src/main/resources/application.yml
@@ -1,6 +1,6 @@
spring:
profiles:
- active: prod
+ active: dev
mvc:
pathmatch:
matching-strategy: ant_path_matcher