node-red-zelecproto 0.1.1 → 0.1.2

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.
Files changed (4) hide show
  1. package/645.js +26 -28
  2. package/698.js +88 -99
  3. package/package.json +1 -1
  4. package/AGENTS.md +0 -133
package/645.js CHANGED
@@ -433,6 +433,20 @@ function decode645(_msg) {
433
433
  return arr.includes(di)
434
434
  }
435
435
 
436
+ function expandBits16(v) {
437
+ const bits = {};
438
+ for (let i = 0; i < 16; i++) {
439
+ bits[`bit${i}`] = (v >> i) & 0x1;
440
+ }
441
+ return bits;
442
+ }
443
+ function expandBits32(v) {
444
+ const bits = {};
445
+ for (let i = 0; i < 32; i++) {
446
+ bits[`bit${i}`] = (v >>> i) & 0x1;
447
+ }
448
+ return bits;
449
+ }
436
450
 
437
451
  // —— 常用分支(保留你的原分支,增加越界保护)——
438
452
  function buildDays(prefix) {
@@ -520,31 +534,22 @@ function decode645(_msg) {
520
534
  rawValue: w1,
521
535
  rawBlockHex: statusBlockHex,
522
536
  binary: bin16(w1),
523
- keys: {
524
- '停电抄表电池欠压': !!bit(w1, 3),
525
- '时钟电池欠压': !!bit(w1, 2),
526
- '有功功率方向反向': !!bit(w1, 4),
527
- '无功功率方向反向': !!bit(w1, 5),
528
- '控制回路错误': !!bit(w1, 8),
529
- 'ESAM错误': !!bit(w1, 9),
530
- '内部程序错误': !!bit(w1, 12),
531
- '存储器故障或损坏': !!bit(w1, 13),
532
- '透支状态': !!bit(w1, 14),
533
- '时钟故障': !!bit(w1, 15)
534
- }
537
+ bits: expandBits16(w1)
535
538
  };
536
539
 
537
540
  // 状态字2/3只保留原始数值和binary,具体位义由上层业务或文档表解释。
538
541
  const w2 = readU16LE();
539
542
  const word2 = (w2 === null) ? null : {
540
543
  rawValue: w2,
541
- binary: bin16(w2)
544
+ binary: bin16(w2),
545
+ bits: expandBits16(w2)
542
546
  };
543
547
 
544
548
  const w3 = readU16LE();
545
549
  const word3 = (w3 === null) ? null : {
546
550
  rawValue: w3,
547
- binary: bin16(w3)
551
+ binary: bin16(w3),
552
+ bits: expandBits16(w3)
548
553
  };
549
554
 
550
555
  // —— 密钥状态字(与你现有的 04000508 保持一致,32位)——
@@ -553,6 +558,7 @@ function decode645(_msg) {
553
558
  rawValue: w8,
554
559
  hexValue: w8.toString(16).toUpperCase().padStart(8, '0'),
555
560
  binary: bin32(w8),
561
+ bits: expandBits32(w8),
556
562
  keys: {
557
563
  '主控密钥有效': !!bit(w8, 0),
558
564
  '身份认证密钥有效': !!bit(w8, 1),
@@ -581,18 +587,7 @@ function decode645(_msg) {
581
587
 
582
588
  value = {
583
589
  rawValue: v, binary: bin,
584
- keys: {
585
- '停电抄表电池欠压': !!(v & (1 << 3)), // bit3: 0=正常, 1=欠压
586
- '时钟电池欠压': !!(v & (1 << 2)), // bit2: 0=正常, 1=欠压
587
- '有功功率方向反向': !!(v & (1 << 4)), // bit4: 0=正向, 1=反向
588
- '无功功率方向反向': !!(v & (1 << 5)), // bit5: 0=正向, 1=反向
589
- '控制回路错误': !!(v & (1 << 8)), // bit8: 0=正常, 1=错误
590
- 'ESAM错误': !!(v & (1 << 9)), // bit9: 0=正常, 1=错误
591
- '内部程序错误': !!(v & (1 << 12)), // bit12: 0=正常, 1=错误
592
- '存储器故障或损坏': !!(v & (1 << 13)), // bit13: 0=正常, 1=故障
593
- '透支状态': !!(v & (1 << 14)), // bit14: 0=正常, 1=透支
594
- '时钟故障': !!(v & (1 << 15)) // bit15: 0=正常, 1=故障
595
- }
590
+ bits: expandBits16(v)
596
591
  };
597
592
  } else if (di === '04000502' && arrPush.length >= 6) {
598
593
  // 状态字2只返回原始数值,binary为bit15..bit0。
@@ -603,7 +598,8 @@ function decode645(_msg) {
603
598
 
604
599
  value = {
605
600
  rawValue: v,
606
- binary: bin
601
+ binary: bin,
602
+ bits: expandBits16(v)
607
603
  };
608
604
  } else if (di === '04000503' && arrPush.length >= 6) {
609
605
  // 状态字3只返回原始数值,binary为bit15..bit0。
@@ -614,7 +610,8 @@ function decode645(_msg) {
614
610
 
615
611
  value = {
616
612
  rawValue: v,
617
- binary: bin
613
+ binary: bin,
614
+ bits: expandBits16(v)
618
615
  };
619
616
  }
620
617
  // else if (['0000FF00', '0001FF00', '0002FF00'].includes(di) && arrPush.length > 4) {
@@ -629,6 +626,7 @@ function decode645(_msg) {
629
626
  rawValue: v,
630
627
  hexValue: v.toString(16).toUpperCase().padStart(8, '0'),
631
628
  binary: v.toString(2).padStart(32, '0'),
629
+ bits: expandBits32(v),
632
630
  keys: {
633
631
  '主控密钥有效': !!(v & (1 << 0)), '身份认证密钥有效': !!(v & (1 << 1)), '密钥协商密钥有效': !!(v & (1 << 2)),
634
632
  '密钥更新密钥有效': !!(v & (1 << 3)), '传输密钥有效': !!(v & (1 << 4)), '保护密钥有效': !!(v & (1 << 5)),
package/698.js CHANGED
@@ -870,15 +870,11 @@ function parseStatusWordFromAxdrBitString(buffer) {
870
870
  const bytes = buffer.slice(start, end);
871
871
  if (bytes.length < 2) return null;
872
872
  const bits = [];
873
- const lowToHighBits = [];
874
- // A-XDR bit-string按每字节高位到低位展开;另保留低位到高位展示用于和现场口径对照。
873
+ // A-XDR bit-string按每字节高位到低位展开。
875
874
  for (const byte of bytes.slice(0, 2)) {
876
875
  for (let bit = 7; bit >= 0; bit--) {
877
876
  bits.push((byte >> bit) & 0x01);
878
877
  }
879
- for (let bit = 0; bit <= 7; bit++) {
880
- lowToHighBits.push((byte >> bit) & 0x01);
881
- }
882
878
  }
883
879
  let statusWord = 0;
884
880
  // 文档表F.x中的bit编号按展开后的bit-string下标映射,后续binary再按645一致的数值形式输出。
@@ -887,136 +883,129 @@ function parseStatusWordFromAxdrBitString(buffer) {
887
883
  }
888
884
  return {
889
885
  statusWord,
890
- binaryLowToHigh: lowToHighBits.slice(0, 16).join(''),
891
- binaryHighToLow: bits.slice(0, 16).join('')
886
+ binary: bits.slice(0, 16).join(''),
887
+ consumed: end
892
888
  };
893
889
  }
894
890
 
895
- /**
896
- * 解析电表运行状态字2 - 与645协议格式保持一致
897
- * @param {Buffer} dataBuffer - 数据缓冲区
898
- * @returns {Object} 解析结果
899
- */
900
-
901
- function parseMeterStatus(dataBuffer, oad = '20140201') {
902
- const result = createStandardResult("电表状态", oad, dataBuffer);
903
- try {
904
- // 1. 读取原始状态字(小端)
905
- const statusWordLe = parseMeterStatusOptimized(dataBuffer);
906
-
907
- // 2. 转换为大端,用于大端逻辑判断
908
- const statusWordBe = toBigEndian16(statusWordLe);
909
-
910
- if (statusWordBe !== null) {
911
- const valTmp = statusWordBe & 0xFFFF;
912
- const bin = valTmp.toString(2).padStart(16, '0'); // 最高位bit15在最左
891
+ function expandBits16(v) {
892
+ const bits = {};
893
+ for (let i = 0; i < 16; i++) {
894
+ bits[`bit${i}`] = (v >> i) & 0x1;
895
+ }
896
+ return bits;
897
+ }
913
898
 
914
- // 3. 大端判断:bit0 是最左边的最高位
915
- result.value = {
916
- rawValue: valTmp,
917
- binary: bin,
918
- keys: {
919
- 'bit15 时钟故障': bin[0] === '1',
920
- 'bit14 透支状态': bin[1] === '1',
921
- 'bit13 存储器故障或损坏': bin[2] === '1',
922
- 'bit12 内部程序错误': bin[3] === '1',
923
- 'bit11 保留': bin[4] === '1',
924
- 'bit10 保留': bin[5] === '1',
925
- 'bit9 ESAM错误': bin[6] === '1',
926
- 'bit8 控制回路错误': bin[7] === '1',
927
- 'bit7 保留': bin[8] === '1',
928
- 'bit6 保留': bin[9] === '1',
929
- 'bit5 无功功率方向反向': bin[10] === '1',
930
- 'bit4 有功功率方向反向': bin[11] === '1',
931
- 'bit3 停电抄表电池欠压': bin[12] === '1',
932
- 'bit2 时钟电池欠压': bin[13] === '1',
933
- 'bit1 需量积算方式': bin[14] === '1',
934
- 'bit0 保留': bin[15] === '1'
935
- }
936
- };
899
+ function format698StatusWord(statusWord, bitStringStatus = null, index = null) {
900
+ const val16 = statusWord & 0xFFFF;
901
+ return {
902
+ ...(index == null ? {} : { index }),
903
+ rawValue: val16,
904
+ statusWordHex: val16.toString(16).padStart(4, '0').toUpperCase(),
905
+ // binary按原始bit-string显示,便于与645页面展示口径保持一致。
906
+ binary: bitStringStatus?.binary || val16.toString(2).padStart(16, '0'),
907
+ bits: expandBits16(val16)
908
+ };
909
+ }
937
910
 
938
- setSuccessResult(result, {
939
- timestamp: null,
940
- statusWord: valTmp,
941
- statusWordHex: valTmp.toString(16).padStart(4, '0').toUpperCase(),
942
- statusBits: decodeMeterStatusBits(valTmp)
943
- }, { generic: { dataType: 'bit-string' } });
911
+ function parseStatusWordsFromAxdrArray(buffer) {
912
+ if (!Buffer.isBuffer(buffer) || buffer.length < 2 || buffer[0] !== 0x01) return [];
913
+ const L = readAxdrLength(buffer, 1);
914
+ const count = L.len;
915
+ let offset = 1 + L.size;
916
+ const words = [];
917
+
918
+ for (let i = 0; i < count && offset < buffer.length; i++) {
919
+ if (buffer[offset] === 0x04) {
920
+ const word = parseStatusWordFromAxdrBitString(buffer.slice(offset));
921
+ if (!word) break;
922
+ words.push(word);
923
+ offset += word.consumed;
944
924
  } else {
945
- throw new Error("无法解析状态字");
925
+ const { consumed } = enhancedParseData(buffer.slice(offset), '2014', '02');
926
+ if (!consumed) break;
927
+ offset += consumed;
946
928
  }
947
-
948
- } catch (e) {
949
- console.error('parseMeterStatus错误:', e.message);
950
- setErrorResult(result, e.message);
951
929
  }
952
930
 
953
- return result;
931
+ return words;
954
932
  }
955
933
 
956
-
957
- /**
958
- * 解析电表运行状态字3(20140203)- 按操作类位义输出字段
959
- */
960
- function parseMeterStatusWord3(dataBuffer) {
961
- const oad = '20140203';
962
- const result = createStandardResult("电表运行状态字3", oad, dataBuffer);
934
+ function parseMeterStatusWord698(dataBuffer, oad, dataType) {
935
+ const result = createStandardResult(dataType, oad, dataBuffer);
963
936
  try {
964
937
  const bitStringStatus = parseStatusWordFromAxdrBitString(dataBuffer);
965
938
  const statusWord = bitStringStatus?.statusWord ?? parseMeterStatusOptimized(dataBuffer);
966
939
  if (statusWord === null) throw new Error('无法解析状态字');
967
940
 
968
- const val16 = statusWord & 0xFFFF; // 低16位
969
- // binary按最终状态字数值输出(bit15..bit0),与645状态字binary保持一致。
970
- const bin = val16.toString(2).padStart(16, '0');
971
-
972
- result.value = {
973
- rawValue: val16,
974
- binary: bin,
975
- ...(bitStringStatus?.binaryLowToHigh ? { binaryLowToHigh: bitStringStatus.binaryLowToHigh } : {}),
976
- ...(bitStringStatus?.binaryHighToLow ? { binaryHighToLow: bitStringStatus.binaryHighToLow } : {})
977
- };
941
+ const value = format698StatusWord(statusWord, bitStringStatus);
942
+ result.value = value;
978
943
 
979
944
  setSuccessResult(result, {
980
- statusWord: val16,
981
- statusWordHex: val16.toString(16).padStart(4, '0').toUpperCase(),
982
- statusBits: decodeMeterStatusBits(val16)
945
+ statusWord: value.rawValue,
946
+ statusWordHex: value.statusWordHex,
947
+ statusBits: decodeMeterStatusBits(value.rawValue)
983
948
  }, { generic: { dataType: 'bit-string' } });
984
949
  } catch (e) {
985
950
  setErrorResult(result, e.message);
986
951
  }
952
+
987
953
  return result;
988
954
  }
989
955
 
990
- /**
991
- * 解析电表运行状态字2(20140202)- 返回原始位数组,不强加语义
992
- */
993
- function parseMeterStatusWord2(dataBuffer) {
994
- const oad = '20140202';
995
- const result = createStandardResult("电表运行状态字2", oad, dataBuffer);
956
+ function parseMeterStatusArray698(dataBuffer) {
957
+ const oad = '20140200';
958
+ const result = createStandardResult("电表状态", oad, dataBuffer);
996
959
  try {
997
- const bitStringStatus = parseStatusWordFromAxdrBitString(dataBuffer);
998
- const statusWord = bitStringStatus?.statusWord ?? parseMeterStatusOptimized(dataBuffer);
999
- if (statusWord === null) throw new Error('无法解析状态字');
1000
-
1001
- const val16 = statusWord & 0xFFFF;
1002
- // binary按最终状态字数值输出(bit15..bit0),与645状态字binary保持一致。
1003
- const bin = val16.toString(2).padStart(16, '0');
960
+ const parsedWords = parseStatusWordsFromAxdrArray(dataBuffer);
961
+ if (!parsedWords.length) throw new Error('无法解析状态字数组');
1004
962
 
963
+ const words = parsedWords.map((word, index) => format698StatusWord(word.statusWord, word, index + 1));
1005
964
  result.value = {
1006
- rawValue: val16,
1007
- binary: bin,
1008
- ...(bitStringStatus?.binaryLowToHigh ? { binaryLowToHigh: bitStringStatus.binaryLowToHigh } : {}),
1009
- ...(bitStringStatus?.binaryHighToLow ? { binaryHighToLow: bitStringStatus.binaryHighToLow } : {}),
1010
- bits: decodeMeterStatusBits(val16)
965
+ word1: words[0] || null,
966
+ word2: words[1] || null,
967
+ word3: words[2] || null,
968
+ words
1011
969
  };
1012
970
 
1013
- setSuccessResult(result, { statusWord: val16, statusWordHex: val16.toString(16).padStart(4, '0').toUpperCase(), statusBits: decodeMeterStatusBits(val16) }, { generic: { dataType: 'bit-string' } });
971
+ setSuccessResult(result, {
972
+ statusWords: words,
973
+ statusWord: words[0]?.rawValue ?? null,
974
+ statusWordHex: words[0]?.statusWordHex ?? null,
975
+ statusBits: words[0] ? decodeMeterStatusBits(words[0].rawValue) : []
976
+ }, { generic: { dataType: 'array' } });
1014
977
  } catch (e) {
1015
978
  setErrorResult(result, e.message);
1016
979
  }
980
+
1017
981
  return result;
1018
982
  }
1019
983
 
984
+ /**
985
+ * 解析电表运行状态字1或状态字数组
986
+ * @param {Buffer} dataBuffer - 数据缓冲区
987
+ * @returns {Object} 解析结果
988
+ */
989
+
990
+ function parseMeterStatus(dataBuffer, oad = '20140201') {
991
+ if (oad === '20140200') return parseMeterStatusArray698(dataBuffer);
992
+ return parseMeterStatusWord698(dataBuffer, oad, "电表状态");
993
+ }
994
+
995
+ /**
996
+ * 解析电表运行状态字3(20140203)
997
+ */
998
+ function parseMeterStatusWord3(dataBuffer) {
999
+ return parseMeterStatusWord698(dataBuffer, '20140203', "电表运行状态字3");
1000
+ }
1001
+
1002
+ /**
1003
+ * 解析电表运行状态字2(20140202)
1004
+ */
1005
+ function parseMeterStatusWord2(dataBuffer) {
1006
+ return parseMeterStatusWord698(dataBuffer, '20140202', "电表运行状态字2");
1007
+ }
1008
+
1020
1009
  /**
1021
1010
  * 优化的状态字解析策略
1022
1011
  * @param {Buffer} dataBuffer - 数据缓冲区
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-zelecproto",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "node-red zelecproto node",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/AGENTS.md DELETED
@@ -1,133 +0,0 @@
1
- # AGENTS.md
2
-
3
- This file contains guidelines and commands for agentic coding agents working in the node-red-zelecproto repository.
4
-
5
- ## Global Settings
6
- - **Proxy**: Execute `proxy` command before running any network-related commands to enable proxy connection
7
-
8
- ## Build/Test/Lint Commands
9
-
10
- ### Current Commands
11
- - `npm test` - Currently shows "Error: no test specified" (placeholder)
12
- - No build, lint, or typecheck commands are currently configured
13
-
14
- ### Testing
15
- - Use `node test.js` to run the manual test file
16
- - Test file located at: `test.js`
17
- - For single test execution, run `node -e "require('./test.js')"`
18
-
19
- ### Development
20
- - No build process required (Node-RED nodes load directly)
21
- - Manual testing via Node-RED flow editor recommended
22
-
23
- ## Code Style Guidelines
24
-
25
- ### File Structure and Naming
26
- - Main node files: `zelecproto.js`, `zbatchproto.js`, `zeleble.js`
27
- - Protocol implementations: `645.js`, `698.js`, `ble.js`
28
- - HTML definitions: `zelecproto.html`, `zbatchproto.html`, `zeleble.html`
29
- - Icons in: `icons/` directory (SVG format)
30
- - Use kebab-case for file names
31
-
32
- ### Module Pattern
33
- All Node-RED nodes must follow this pattern:
34
- ```javascript
35
- module.exports = function (RED) {
36
- "use strict";
37
-
38
- // Require dependencies
39
- var proto645 = require("./645");
40
-
41
- function NodeName(n) {
42
- RED.nodes.createNode(this, n);
43
- var node = this;
44
-
45
- this.on("input", function (msg, send, done) {
46
- // Process message
47
- send(msg);
48
- done();
49
- });
50
-
51
- this.on('close', () => {
52
- // Cleanup if needed
53
- });
54
- }
55
-
56
- RED.nodes.registerType("nodename", NodeName);
57
- }
58
- ```
59
-
60
- ### Import/Require Style
61
- - Use `var` for requires (consistent with existing codebase)
62
- - Local dependencies first: `var proto645 = require("./645");`
63
- - Group requires at top of module function
64
- - Use relative paths for local files
65
-
66
- ### Code Formatting
67
- - Use strict mode: `"use strict";` at top of module function
68
- - Indentation: 4 spaces (consistent with existing files)
69
- - Semicolons required
70
- - String quotes: single quotes preferred
71
- - Object property access: dot notation for known properties, bracket notation for dynamic
72
-
73
- ### Naming Conventions
74
- - Node functions: PascalCase (e.g., `zelecproto`, `zbatchproto`)
75
- - Variables: camelCase (e.g., `msg`, `node`, `addrBytes`)
76
- - Constants: UPPER_SNAKE_CASE (e.g., `START`, `END`, `OAD`)
77
- - File names: kebab-case (e.g., `zelecproto.js`, `645.js`)
78
-
79
- ### Error Handling
80
- - Use `throw new Error()` for validation errors
81
- - Include descriptive error messages
82
- - Validate input parameters before processing
83
- - Example: `if (addrRaw.length !== 12) throw new Error('com_exec_addr 必须是 6 字节(12个HEX字符)');`
84
-
85
- ### Message Processing
86
- - Always use the three-parameter input handler: `function (msg, send, done)`
87
- - Call `send(msg)` before `done()`
88
- - Clean up temporary properties: `delete msg._proto`
89
- - Preserve original message structure when possible
90
-
91
- ### Protocol Implementation
92
- - 645 Protocol: Focus on DL/T 645 frame building/parsing
93
- - 698 Protocol: DL/T 698.45 encode/decode with CRC-16/X-25
94
- - BLE Protocol: 国网多芯物联表蓝牙通信协议
95
- - Each protocol file should export a function that takes `msg` and returns modified `msg`
96
-
97
- ### HTML Node Definitions
98
- - Use data-template-name attribute matching node type
99
- - Include name field in all nodes
100
- - Set appropriate category, color, and icon
101
- - Category: 'zutils' for all nodes
102
- - Icons: reference SVG files in icons/ directory
103
-
104
- ### Comments and Documentation
105
- - Use Chinese comments for protocol-specific explanations (consistent with existing code)
106
- - Use JSDoc-style comments for functions
107
- - Include protocol references and frame format descriptions
108
- - Example: `// 国网多芯物联表《蓝牙通信及脉冲检定说明手册(0224)》协议`
109
-
110
- ### Buffer/Hex Handling
111
- - Use `Buffer.from()` for creating buffers
112
- - Hex strings should be uppercase without spaces for final output
113
- - Use helper functions for hex/bytes conversion
114
- - Maintain consistent hex formatting across protocols
115
-
116
- ### Node-RED Integration
117
- - Register nodes in package.json under `node-red.nodes`
118
- - Minimum Node.js version: >=15
119
- - Minimum Node-RED version: >=1.3
120
- - No external dependencies currently required
121
-
122
- ## Development Workflow
123
- 1. Modify protocol implementation files as needed
124
- 2. Test with `node test.js` or via Node-RED flow editor
125
- 3. Ensure all nodes follow the standard module pattern
126
- 4. Verify HTML definitions match JavaScript node registrations
127
- 5. Test message processing with sample data
128
-
129
- ## Protocol-Specific Notes
130
- - 645: Handle address reversal and OAD encryption
131
- - 698: Implement CRC-16/X-25 checksum validation
132
- - BLE: Follow frame format with Start/End markers
133
- - All protocols should support both encode and decode operations