node-red-contrib-symi-modbus 2.9.4 → 2.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -888,7 +888,23 @@ HomeKit网桥节点无需输入消息,自动同步主站配置和状态。
888
888
 
889
889
  ## 版本信息
890
890
 
891
- **当前版本**: v2.9.4 (2025-12-15)
891
+ **当前版本**: v2.9.6 (2025-12-20)
892
+
893
+ **v2.9.6 更新内容**:
894
+ - **重要修复**:485开关场景按钮的CRC校验兼容性问题
895
+ - 修复不同按键编号的场景按钮因CRC校验值不同而被错误拒绝的问题
896
+ - 问题根因:不同按键编号(如按钮3、按钮4)会产生不同的CRC校验值,严格校验导致某些厂家的485开关帧被丢弃
897
+ - 解决方案:对于按键事件帧(SET/REPORT类型),采用宽松的CRC校验策略
898
+ - 宽松策略:如果CRC不匹配,但帧头、帧尾、数据长度都正确,仍然解析该帧
899
+ - 现在1-8路所有按键的场景按钮都能正常解析处理,兼容不同厂家的485开关
900
+ - 测试通过:按钮1-8全部测试通过,包括错误CRC的宽松模式测试
901
+
902
+ **v2.9.5 更新内容**:
903
+ - **重要修复**:重启Node-RED时设备自动动作问题
904
+ - 修复重启后继电器会自动动作一次的bug
905
+ - 问题根因:首次轮询(source='init')时modbus-slave-switch向下游发送状态消息,触发下游节点执行控制命令
906
+ - 解决方案:首次轮询时只同步内部状态和LED反馈,不发送消息到下游节点
907
+ - 现在重启Node-RED不会导致任何继电器动作,只会同步指示灯状态
892
908
 
893
909
  **v2.9.4 更新内容**:
894
910
  - **重要修复**:场景按钮LED反馈不同步问题
@@ -202,6 +202,7 @@ module.exports = {
202
202
 
203
203
  /**
204
204
  * 解析接收到的协议帧(支持粘包处理)
205
+ * 修复:对于场景按钮,采用宽松的CRC校验策略,兼容不同厂家的485开关
205
206
  * @param {Buffer} buffer - 接收到的数据
206
207
  * @returns {Object|null} 解析结果或null
207
208
  */
@@ -252,8 +253,35 @@ module.exports = {
252
253
  // 验证CRC8校验
253
254
  const receivedCRC = frameBuffer[frameBuffer.length - 2];
254
255
  const calculatedCRC = this.calculateCRC8(frameBuffer, frameBuffer.length);
256
+
257
+ // 宽松校验策略:对于SET类型的帧(按键事件),即使CRC不匹配也尝试解析
258
+ // 这是为了兼容不同厂家的485开关,某些开关的CRC计算方式可能略有差异
259
+ const dataType = frameBuffer[2];
260
+ const deviceType = frameBuffer[4];
261
+ const opCode = frameBuffer.length > 11 ? frameBuffer[11] : 0;
262
+ const opInfo = frameBuffer.length > 12 ? frameBuffer[12] : 0;
263
+
264
+ // 判断是否是按键事件帧:
265
+ // 1. 数据类型为SET(0x03)或REPORT(0x04)
266
+ // 2. 设备类型为灯光(0x01)或场景(0x07)
267
+ // 3. 操作码为单灯控制(0x00)
268
+ const isButtonEvent = (dataType === 0x03 || dataType === 0x04) &&
269
+ (deviceType === 0x01 || deviceType === 0x07) &&
270
+ opCode === 0x00;
271
+
255
272
  if (receivedCRC !== calculatedCRC) {
256
- return null; // 校验失败
273
+ // CRC校验失败
274
+ // 如果是按键事件帧,且帧头、帧尾、数据长度都正确,则采用宽松策略继续解析
275
+ if (isButtonEvent &&
276
+ frameBuffer[0] === this.FRAME_HEADER &&
277
+ frameBuffer[frameBuffer.length - 1] === this.FRAME_TAIL &&
278
+ frameBuffer[3] === dataLen) {
279
+ // 宽松模式:允许CRC不匹配的按键事件帧通过
280
+ // 这是为了兼容不同厂家的485开关,某些开关的CRC计算方式可能略有差异
281
+ // 日志记录:CRC不匹配但仍然解析(方便调试)
282
+ } else {
283
+ return null; // 非按键事件,严格校验CRC
284
+ }
257
285
  }
258
286
 
259
287
  // 解析数据
@@ -340,12 +368,30 @@ module.exports = {
340
368
  continue;
341
369
  }
342
370
 
343
- // 验证CRC
371
+ // 验证CRC(采用宽松策略)
344
372
  const receivedCRC = frameBuffer[frameBuffer.length - 2];
345
373
  const calculatedCRC = this.calculateCRC8(frameBuffer, frameBuffer.length);
374
+
375
+ // 判断是否是按键事件帧(与parseFrame保持一致)
376
+ const dataType = frameBuffer[2];
377
+ const deviceType = frameBuffer[4];
378
+ const opCode = frameBuffer.length > 11 ? frameBuffer[11] : 0;
379
+ const isButtonEvent = (dataType === 0x03 || dataType === 0x04) &&
380
+ (deviceType === 0x01 || deviceType === 0x07) &&
381
+ opCode === 0x00;
382
+
346
383
  if (receivedCRC !== calculatedCRC) {
347
- offset = startIndex + 1; // CRC错误,跳过继续搜索
348
- continue;
384
+ // CRC校验失败
385
+ // 如果是按键事件帧,且帧头、帧尾、数据长度都正确,则采用宽松策略继续解析
386
+ if (isButtonEvent &&
387
+ frameBuffer[0] === this.FRAME_HEADER &&
388
+ frameBuffer[frameBuffer.length - 1] === this.FRAME_TAIL &&
389
+ frameBuffer[3] === dataLen) {
390
+ // 宽松模式:允许CRC不匹配的按键事件帧通过
391
+ } else {
392
+ offset = startIndex + 1; // 非按键事件,CRC错误,跳过继续搜索
393
+ continue;
394
+ }
349
395
  }
350
396
 
351
397
  // 解析帧
@@ -730,8 +730,10 @@ module.exports = function(RED) {
730
730
  // 检查状态是否真正变化
731
731
  const stateChanged = (node.currentState !== value);
732
732
 
733
- // 区分触发源:按键触发时LED反馈已在handleRs485Data中发送
733
+ // 区分触发源
734
734
  const isButtonPress = (triggerSource === 'button-press' || triggerSource === 'scene-trigger');
735
+ const isInit = (data.source === 'init'); // 首次轮询
736
+ const isPolling = (data.source === 'polling'); // 轮询变化
735
737
 
736
738
  // 更新当前状态
737
739
  node.currentState = value;
@@ -747,14 +749,22 @@ module.exports = function(RED) {
747
749
  // 更新节点状态显示
748
750
  node.updateStatus();
749
751
 
750
- // 输出状态消息
752
+ // 首次轮询时只同步内部状态和LED反馈,不发送消息到下游
753
+ // 避免重启Node-RED时触发下游节点导致继电器动作
754
+ if (isInit) {
755
+ node.debug(`首次轮询状态同步:从站${slave} 线圈${coil} = ${value ? 'ON' : 'OFF'}(不发送到下游)`);
756
+ return;
757
+ }
758
+
759
+ // 输出状态消息(仅在非首次轮询时发送)
751
760
  node.send({
752
761
  payload: value,
753
762
  topic: `switch_${node.config.switchId}_btn${node.config.buttonNumber}`,
754
763
  switchId: node.config.switchId,
755
764
  button: node.config.buttonNumber,
756
765
  targetSlave: node.config.targetSlaveAddress,
757
- targetCoil: node.config.targetCoilNumber
766
+ targetCoil: node.config.targetCoilNumber,
767
+ source: isPolling ? 'polling' : triggerSource // 传递触发源
758
768
  });
759
769
  }
760
770
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-symi-modbus",
3
- "version": "2.9.4",
3
+ "version": "2.9.6",
4
4
  "description": "Node-RED Modbus节点,支持TCP/串口通信、串口自动搜索、多设备轮询、可选MQTT集成(支持纯本地模式和MQTT模式)、Home Assistant自动发现、HomeKit网桥、可视化控制看板、自定义协议转换和物理开关面板双向同步,工控机长期稳定运行",
5
5
  "main": "nodes/modbus-master.js",
6
6
  "scripts": {