node-red-zelecproto 0.0.8 → 0.1.0

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 (5) hide show
  1. package/645.js +50 -0
  2. package/698.js +33 -5
  3. package/AGENTS.md +133 -0
  4. package/package.json +1 -1
  5. package/test.js +26 -13
package/645.js CHANGED
@@ -28,6 +28,10 @@ function build645Frame(i, reverseAddr, csMode) {
28
28
  return buildEncrypted645Frame(i, addrBytes, reverseAddr, csMode);
29
29
  }
30
30
 
31
+ if (oad === '04040300') {
32
+ return buildSwitchScreen645Frame(i, addrBytes, csMode);
33
+ }
34
+
31
35
 
32
36
  // OAD 加密:倒序 + 每字节 +0x33
33
37
  const cmdEx = encryptOAD(oad); // "xx xx xx xx"
@@ -50,6 +54,38 @@ function build645Frame(i, reverseAddr, csMode) {
50
54
  });
51
55
  }
52
56
 
57
+ function buildSwitchScreen645Frame(i, addrBytes, csMode) {
58
+ const payload = i.payload || {};
59
+ const screenRaw = payload.screenContent || payload.switchScreenContent || payload.switchScreen || payload.itemContent || payload.dataHex;
60
+ let screenHex = String(screenRaw || '').replace(/\s+/g, '').toUpperCase();
61
+ if (!screenHex) throw new Error('04040300 屏显切换需要提供 screenContent/itemContent');
62
+ if (screenHex.length === 8) screenHex += '00';
63
+ if (!/^[0-9A-F]{10}$/.test(screenHex)) throw new Error('04040300 切屏内容必须是 10 位 HEX,例如 0001000000');
64
+
65
+ const screenBody = screenHex.slice(0, 8);
66
+ const optionByte = screenHex.slice(8, 10);
67
+ const oadBytes = hexToBytes(encryptOAD('04040300').replace(/\s+/g, ''));
68
+ const dataBytes = [
69
+ ...oadBytes,
70
+ ...hexToBytes(reverseHexBytes(screenBody)).map(b => (b + 0x33) & 0xFF),
71
+ ...hexToBytes(optionByte).map(b => (b + 0x33) & 0xFF)
72
+ ];
73
+
74
+ const C = 0x11;
75
+ const L = dataBytes.length;
76
+ const frameNoCS = [0x68, ...addrBytes, 0x68, C, L, ...dataBytes];
77
+ const cs = calc645CSForBytes(frameNoCS, csMode);
78
+ const csHex = cs.toString(16).toUpperCase().padStart(2, '0');
79
+ const fullHex = (`FE FE FE FE ${bytesToHex(frameNoCS)} ${csHex} 16`).replace(/\s+/g, '');
80
+ let _payload = i.payload || {};
81
+ return Object.assign(i, _payload, {
82
+ com_exec_addr: bytesToHex(addrBytes).replace(/\s+/g, ''),
83
+ cmdEx: bytesToHex(oadBytes).replace(/\s+/g, ''),
84
+ switchScreenContent: screenHex,
85
+ payload: fullHex
86
+ });
87
+ }
88
+
53
89
  /**
54
90
  * 读取地址
55
91
  */
@@ -672,6 +708,20 @@ function decode645(_msg) {
672
708
  '广播认证密钥有效': !!(v & (1 << 6)), '主控密钥不可恢复': !!(v & (1 << 7))
673
709
  }
674
710
  };
711
+ } else if (di === '04040300' && arrPush.length >= 5) {
712
+ const body = arrPush.slice(4);
713
+ const option = body.length > 0 ? body[body.length - 1] : null;
714
+ const screenContent = body.length > 1
715
+ ? Buffer.from(body.slice(0, -1).reverse()).toString('hex').toUpperCase()
716
+ : '';
717
+ value = {
718
+ type: 'switch_screen',
719
+ description: '屏显切换',
720
+ screenContent,
721
+ option: option == null ? null : option.toString(16).toUpperCase().padStart(2, '0'),
722
+ itemContent: screenContent + (option == null ? '' : option.toString(16).toUpperCase().padStart(2, '0')),
723
+ rawMinus33: bytesToHex(arrPush).replace(/\s+/g, '')
724
+ };
675
725
  } else if (di === '04000102' && arrPush.length >= 3) {
676
726
  value = Buffer.from(arrPush.slice(-3).reverse()).toString('hex').toUpperCase(); // "HHMMSS"
677
727
  } else if (di === '04000101' && arrPush.length >= 8) {
package/698.js CHANGED
@@ -859,6 +859,32 @@ function toBigEndian16(value) {
859
859
  // 将小端(低字节在前)转换为大端(高字节在前)
860
860
  return (lower << 8) | upper;
861
861
  }
862
+
863
+ function parseStatusWordFromAxdrBitString(buffer) {
864
+ if (!Buffer.isBuffer(buffer) || buffer.length < 3 || buffer[0] !== 0x04) return null;
865
+ const L = readAxdrLength(buffer, 1);
866
+ const byteLen = Math.ceil(L.len / 8);
867
+ const start = 1 + L.size;
868
+ const end = start + byteLen;
869
+ if (end > buffer.length) return null;
870
+ const bytes = buffer.slice(start, end);
871
+ if (bytes.length < 2) return null;
872
+ const bits = [];
873
+ for (const byte of bytes.slice(0, 2)) {
874
+ for (let bit = 7; bit >= 0; bit--) {
875
+ bits.push((byte >> bit) & 0x01);
876
+ }
877
+ }
878
+ let statusWord = 0;
879
+ for (let i = 0; i < 16; i++) {
880
+ statusWord |= bits[i] << i;
881
+ }
882
+ return {
883
+ statusWord,
884
+ binary: bits.slice(0, 16).join('')
885
+ };
886
+ }
887
+
862
888
  /**
863
889
  * 解析电表运行状态字2 - 与645协议格式保持一致
864
890
  * @param {Buffer} dataBuffer - 数据缓冲区
@@ -928,11 +954,12 @@ function parseMeterStatusWord3(dataBuffer) {
928
954
  const oad = '20140203';
929
955
  const result = createStandardResult("电表运行状态字3", oad, dataBuffer);
930
956
  try {
931
- const statusWord = parseMeterStatusOptimized(dataBuffer);
957
+ const bitStringStatus = parseStatusWordFromAxdrBitString(dataBuffer);
958
+ const statusWord = bitStringStatus?.statusWord ?? parseMeterStatusOptimized(dataBuffer);
932
959
  if (statusWord === null) throw new Error('无法解析状态字');
933
960
 
934
961
  const val16 = statusWord & 0xFFFF; // 低16位
935
- const bin = val16.toString(2).padStart(16, '0');
962
+ const bin = bitStringStatus?.binary ?? val16.toString(2).padStart(16, '0');
936
963
 
937
964
  const supplyBits = (val16 >> 1) & 0b11; // bit2-bit1
938
965
  const supplyMode = (
@@ -984,11 +1011,12 @@ function parseMeterStatusWord2(dataBuffer) {
984
1011
  const oad = '20140202';
985
1012
  const result = createStandardResult("电表运行状态字2", oad, dataBuffer);
986
1013
  try {
987
- const statusWord = parseMeterStatusOptimized(dataBuffer);
1014
+ const bitStringStatus = parseStatusWordFromAxdrBitString(dataBuffer);
1015
+ const statusWord = bitStringStatus?.statusWord ?? parseMeterStatusOptimized(dataBuffer);
988
1016
  if (statusWord === null) throw new Error('无法解析状态字');
989
1017
 
990
1018
  const val16 = statusWord & 0xFFFF;
991
- const bin = val16.toString(2).padStart(16, '0');
1019
+ const bin = bitStringStatus?.binary ?? val16.toString(2).padStart(16, '0');
992
1020
 
993
1021
  const bit = (n) => ((val16 >> n) & 0x1);
994
1022
  const dir = (b) => (b ? '反向' : '正向'); // 0=正向, 1=反向
@@ -3005,4 +3033,4 @@ function batchMsg698(msg) {
3005
3033
  }
3006
3034
 
3007
3035
  module.exports = batchMsg698;
3008
- module.exports.batchMsg698 = batchMsg698;
3036
+ module.exports.batchMsg698 = batchMsg698;
package/AGENTS.md ADDED
@@ -0,0 +1,133 @@
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-zelecproto",
3
- "version": "0.0.8",
3
+ "version": "0.1.0",
4
4
  "description": "node-red zelecproto node",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/test.js CHANGED
@@ -7,9 +7,9 @@ let msg = {};
7
7
  // msg.payload = 'FE FE FE FE 68 30 00 C3 05 77 59 63 00 15 19 11 59 0F 90 00 17 85 01 01 30 1B 07 00 01 01 01 02 02 00 02 02 06 00 00 00 00 00 00 00 01 00 04 B2 F3 8B 23 F2 7C 16'
8
8
  // msg.payload = 'FE FE FE FE 68 33 00 C3 05 60 80 63 00 15 19 11 8B E3 90 00 1A 85 01 01 30 13 0A 00 01 01 01 02 02 00 02 02 1C 07 E3 07 17 0B 06 1B 00 00 00 01 00 04 2C 96 C6 41 EF 15 16'
9
9
  // msg.payload = 'FE FE FE FE 68 2C 00 C3 05 16 01 00 00 00 00 11 54 84 90 00 13 85 01 01 30 13 0A 00 01 01 01 02 02 00 02 02 00 00 00 00 01 00 04 EE E6 E8 86 13 64 16'
10
- msg.payload = '685134234200006893068467567533336F16'
11
- msg.proto = 645
12
- msg.mode = 'decode'
10
+ // msg.payload = '685134234200006893068467567533336F16'
11
+ // msg.proto = 645
12
+ // msg.mode = 'decode'
13
13
 
14
14
 
15
15
 
@@ -23,14 +23,27 @@ msg.mode = 'decode'
23
23
 
24
24
 
25
25
 
26
- msg.payload=[
27
- {
28
- barcode:'1',
29
- payload : '685134234200006893068467567533336F16',
30
- proto : 645
31
- }
32
- ]
26
+ // msg.payload=[
27
+ // {
28
+ // barcode:'1',
29
+ // payload : '685134234200006893068467567533336F16',
30
+ // proto : 645
31
+ // }
32
+ // ]
33
33
 
34
- let nmsg = proto645(msg);
35
- console.log(nmsg);
36
- // console.log(JSON.stringify(nmsg));
34
+ // let nmsg = proto645(msg);
35
+ // console.log(nmsg);
36
+ // console.log(JSON.stringify(nmsg));
37
+
38
+
39
+
40
+ // msg.payload=[
41
+ // {
42
+ // barcode:'1',
43
+ // payload : 'FE FE FE FE 68 52 00 C3 05 60 50 30 00 49 25 11 BA 8D 90 00 39 85 01 01 00 00 04 00 01 01 05 14 00 00 00 00 00 C2 91 74 14 00 00 00 00 00 10 CE 12 14 00 00 00 00 00 0A 24 C2 14 00 00 00 00 00 66 C8 DA 14 00 00 00 00 00 3B FC 54 00 00 01 00 04 8D 62 D9 49 53 D1 16',
44
+ // proto : 698
45
+ // }
46
+ // ]
47
+
48
+ // console.log(JSON.stringify(proto698(msg)));
49
+ // // console.log(proto698(msg));