device-communication-node 1.0.0-beta.1

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.
@@ -0,0 +1,155 @@
1
+ const InteractionPattern = require('./InteractionPattern')
2
+
3
+ class Task {
4
+
5
+ /**
6
+ * 全局序列生成器
7
+ */
8
+ static seqGenerator = 0
9
+
10
+ /**
11
+ * 写入数据 Buffer
12
+ * @type {Buffer}
13
+ */
14
+ writeBytes
15
+
16
+ /**
17
+ * 回调函数 (readBytes, writeBytes)
18
+ * @type {Function}
19
+ */
20
+ dataReceived
21
+
22
+ /**
23
+ * 响应超时时间
24
+ * @type {number}
25
+ */
26
+ timeout = 500
27
+
28
+ /**
29
+ * 重试次数
30
+ * @type {number}
31
+ */
32
+ retryCount = 0
33
+
34
+ /**
35
+ * 优先级(越小越优先)
36
+ * @type {number}
37
+ */
38
+ priority = 0
39
+
40
+ /**
41
+ * 通信模式
42
+ */
43
+ interactionPattern = InteractionPattern.FIRE_AND_FORGET
44
+
45
+ /**
46
+ * 序列号
47
+ */
48
+ sequence
49
+
50
+ /**
51
+ * 构造函数
52
+ * @param {*} writeBytes 写入的字节数组
53
+ * @param {*} priority 优先级
54
+ * @param {*} retryCount 重试次数
55
+ * @param {*} dataReceived 回调函数
56
+ * @param {*} timeout 超时时间
57
+ * @param {*} interactionPattern 任务交互模式
58
+ */
59
+ constructor(
60
+ writeBytes,
61
+ priority,
62
+ retryCount,
63
+ dataReceived,
64
+ timeout,
65
+ interactionPattern
66
+ ) {
67
+ this.writeBytes = writeBytes
68
+ this.priority = priority
69
+ this.retryCount = retryCount
70
+ this.dataReceived = dataReceived
71
+ this.interactionPattern = interactionPattern
72
+
73
+ if (typeof timeout === 'number') {
74
+ this.timeout = timeout
75
+ }
76
+ this.sequence = Task.seqGenerator++
77
+ }
78
+ compareTo(other) {
79
+
80
+ // 优先级(越小越优先)
81
+ let res = this.priority - other.priority
82
+
83
+ // 序列号(先进先出)
84
+ if (res === 0) {
85
+ res = this.sequence - other.sequence
86
+ }
87
+
88
+ return res
89
+ }
90
+
91
+ /**
92
+ * getter: writeBytes
93
+ */
94
+ getWriteBytes() {
95
+ return this.writeBytes
96
+ }
97
+
98
+ /**
99
+ * getter: callback
100
+ */
101
+ getDataReceived() {
102
+ return this.dataReceived
103
+ }
104
+
105
+ /**
106
+ * timeout
107
+ */
108
+ getTimeout() {
109
+ return this.timeout
110
+ }
111
+
112
+ /**
113
+ * retry
114
+ */
115
+ getRetryCount() {
116
+ return this.retryCount
117
+ }
118
+
119
+ /**
120
+ * priority
121
+ */
122
+ getPriority() {
123
+ return this.priority
124
+ }
125
+
126
+ /**
127
+ * sequence
128
+ */
129
+ getSequence() {
130
+ return this.sequence
131
+ }
132
+
133
+ /**
134
+ * set retry
135
+ */
136
+ setRetryCount(retryCount) {
137
+ this.retryCount = retryCount
138
+ }
139
+
140
+ /**
141
+ * getter interactionPattern
142
+ */
143
+ getSchedulingStrategy() {
144
+ return this.interactionPattern
145
+ }
146
+
147
+ /**
148
+ * setter interactionPattern
149
+ */
150
+ setInteractionPattern(interactionPattern) {
151
+ this.interactionPattern = interactionPattern
152
+ }
153
+ }
154
+
155
+ module.exports = Task
@@ -0,0 +1,62 @@
1
+
2
+ /**
3
+ * 截取 Buffer
4
+ *
5
+ * @param {Buffer|Uint8Array} buffer 原始数据
6
+ * @param {number} start 起始索引
7
+ * @param {number} end 结束索引 (包含此位)
8
+ * @returns {Buffer} 新的子数组
9
+ */
10
+ const slice = (buffer, start, end) => {
11
+ if (!buffer || !(buffer instanceof Uint8Array)) {
12
+ throw new Error("Input must be a Buffer or Uint8Array");
13
+ }
14
+
15
+ if (start < 0 || end >= buffer.length || start > end) {
16
+ throw new Error(`Invalid start or end index: start=${start}, end=${end}, length=${buffer.length}`);
17
+ }
18
+ return Buffer.from(buffer.subarray(start, end + 1));
19
+ }
20
+
21
+ /**
22
+ * 合并多个 Buffer
23
+ *
24
+ * @param {...(Buffer|Uint8Array)} buffers 多个数组参数
25
+ * @returns {Buffer} 合并后的新 Buffer
26
+ */
27
+ const merge = (...buffers) => {
28
+ if (!buffers || buffers.length === 0) {
29
+ return Buffer.alloc(0);
30
+ }
31
+
32
+ // 过滤掉 null 或 undefined 的部分
33
+ const validBuffers = buffers.filter(b => b !== null && b !== undefined);
34
+
35
+ if (validBuffers.length === 0) {
36
+ return Buffer.alloc(0);
37
+ }
38
+ return Buffer.concat(validBuffers);
39
+ }
40
+ /**
41
+ * CIDR 转子网掩码 byte 数组
42
+ * 例如:24 => 255.255.255.0 => [0xFF, 0xFF, 0xFF, 0x00]
43
+ */
44
+ const cidrToSubnetBytes = (cidr) => {
45
+ const mask = [];
46
+
47
+ for (let i = 0; i < 4; i++) {
48
+ if (cidr >= 8) {
49
+ mask.push(255);
50
+ cidr -= 8;
51
+ } else if (cidr > 0) {
52
+ const value = (0xFF << (8 - cidr)) & 0xFF;
53
+ mask.push(value);
54
+ cidr = 0;
55
+ } else {
56
+ mask.push(0);
57
+ }
58
+ }
59
+
60
+ return mask;
61
+ }
62
+ module.exports = { slice, merge, cidrToSubnetBytes }
@@ -0,0 +1,57 @@
1
+
2
+ /**
3
+ * 针对分子筛硬件的CRC8计算
4
+ * @param {Buffer|Uint8Array} data
5
+ * @param {number} offset
6
+ * @param {number} length
7
+ * @returns {number}
8
+ */
9
+ const calculateCrc8 = (data, offset, length) => {
10
+ let crc = 0xFF
11
+
12
+ for (let idx = 0; idx < length; idx++) {
13
+ let ch1 = data[idx + offset] & 0xFF
14
+
15
+ for (let i = 0; i < 8; i++) {
16
+ if (((crc ^ ch1) & 0x80) > 0) {
17
+ crc = ((crc << 1) ^ 0x1D) & 0xFF
18
+ } else {
19
+ crc = (crc << 1) & 0xFF
20
+ }
21
+
22
+ ch1 = (ch1 << 1) & 0xFF
23
+ }
24
+ }
25
+
26
+ return (crc ^ 0xFF) & 0xFF
27
+ }
28
+
29
+ /**
30
+ * 计算 Modbus CRC16
31
+ * 遵循低位在前(Little-endian)的标准输出
32
+ *
33
+ * @param {Uint8Array|number[]} data 数据字节数组
34
+ * @returns {number[]} [CRC低字节, CRC高字节]
35
+ */
36
+ const getModbusCRC16 = (data) => {
37
+ let crc = 0xFFFF;
38
+
39
+ for (const b of data) {
40
+ crc ^= (b & 0xFF);
41
+
42
+ for (let i = 0; i < 8; i++) {
43
+ if ((crc & 0x0001) !== 0) {
44
+ crc = (crc >>> 1) ^ 0xA001;
45
+ } else {
46
+ crc >>>= 1;
47
+ }
48
+ }
49
+ }
50
+
51
+ const low = crc & 0xFF;
52
+ const high = (crc >>> 8) & 0xFF;
53
+
54
+ return [low, high];
55
+ }
56
+
57
+ module.exports = { calculateCrc8, getModbusCRC16 }
@@ -0,0 +1,100 @@
1
+
2
+ /**
3
+ * 将 16 进制字符串转回 Buffer
4
+ */
5
+ const hexToBytes = (hexString) => {
6
+ if (!hexString || typeof hexString !== 'string') {
7
+ return Buffer.alloc(0)
8
+ }
9
+
10
+ try {
11
+ const cleanHex = hexString.replace(/\s+/g, '')
12
+ return Buffer.from(cleanHex, 'hex')
13
+ } catch (e) {
14
+ console.error('非法 16 进制字符串:', e.message)
15
+ return Buffer.alloc(0)
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Buffer 转 HEX字符串
21
+ */
22
+ const bytesToHexString = (bytes) => {
23
+ if (!bytes || bytes.length === 0) {
24
+ return ''
25
+ }
26
+
27
+ const hex = Buffer.from(bytes).toString('hex')
28
+
29
+ return hex
30
+ .replace(/.{2}/g, '$& ')
31
+ .trim()
32
+ .toUpperCase()
33
+ }
34
+
35
+ /**
36
+ * 单个字节转 HEX 字符串
37
+ * @param {number} byte - 0-255 的整数
38
+ */
39
+ const byteToHexString = (byte) => {
40
+ // 确保是 0xFF 范围内的单字节,并转为16进制字符串
41
+ // padStart(2, '0') 确保结果始终是两位数,例如 9 变成 09
42
+ return (byte & 0xFF)
43
+ .toString(16)
44
+ .padStart(2, '0')
45
+ .toUpperCase();
46
+ }
47
+
48
+ /**
49
+ * 10进制转2位HEX
50
+ */
51
+ const toHexByteFast = (value) => {
52
+ return (value & 0xFF)
53
+ .toString(16)
54
+ .toUpperCase()
55
+ .padStart(2, '0')
56
+ }
57
+
58
+ /**
59
+ * 单字节转HEX
60
+ */
61
+ const byteToHex = (b) => {
62
+ return this.toHexByteFast(b)
63
+ }
64
+
65
+ /**
66
+ * 10进制转2字节数组(高字节在前)
67
+ * 例如:
68
+ * 258 => [0x01, 0x02]
69
+ */
70
+ const intTo2Bytes = (value) => {
71
+ return Buffer.from([
72
+ (value >> 8) & 0xFF,
73
+ value & 0xFF
74
+ ])
75
+ }
76
+
77
+ /**
78
+ * 10进制转2字节数组(低字节在前)
79
+ * @param {*} value
80
+ * @returns
81
+ */
82
+ const intTo2BytesLE = (value) => {
83
+ return Buffer.from([
84
+ value & 0xFF, // 先放低位
85
+ (value >> 8) & 0xFF // 后放高位
86
+ ])
87
+ }
88
+
89
+ /**
90
+ * 10进制转1字节数组
91
+ * @param {*} value
92
+ * @returns
93
+ */
94
+ const intTo1Byte = (value) => {
95
+ return Buffer.from([
96
+ value & 0xFF
97
+ ])
98
+ }
99
+
100
+ module.exports = { hexToBytes, bytesToHexString, byteToHexString, toHexByteFast, byteToHex, intTo2Bytes, intTo2BytesLE, intTo1Byte }
@@ -0,0 +1,50 @@
1
+ const os = require('os');
2
+
3
+ /**
4
+ * 获取真实局域网 IPv4
5
+ * 自动过滤:
6
+ * - 127.0.0.1
7
+ * - VMware
8
+ * - VirtualBox
9
+ * - Hyper-V
10
+ * - Docker
11
+ * - WSL
12
+ * - vEthernet
13
+ */
14
+ const getLocalIP = () => {
15
+ const interfaces = os.networkInterfaces();
16
+
17
+ for (const name of Object.keys(interfaces)) {
18
+
19
+ // 过滤虚拟网卡
20
+ const lowerName = name.toLowerCase();
21
+
22
+ if (
23
+ lowerName.includes('vmware') ||
24
+ lowerName.includes('virtual') ||
25
+ lowerName.includes('hyper-v') ||
26
+ lowerName.includes('vethernet') ||
27
+ lowerName.includes('docker') ||
28
+ lowerName.includes('wsl')
29
+ ) {
30
+ continue;
31
+ }
32
+
33
+ for (const net of interfaces[name]) {
34
+
35
+ // 只要 IPv4 且不是回环地址
36
+ if (
37
+ net.family === 'IPv4' &&
38
+ !net.internal
39
+ ) {
40
+ return net.address;
41
+ }
42
+ }
43
+ }
44
+
45
+ return null;
46
+ }
47
+
48
+ module.exports = {
49
+ getLocalIP
50
+ };
@@ -0,0 +1,49 @@
1
+ const DeviceCore = require('../../device/core/DeviceCore');
2
+ const checkUtils = require('../../device/utils/checkUtils');
3
+ const hexUtils = require('../../device/utils/hexUtils');
4
+ /**
5
+ * 测试设备
6
+ */
7
+ class DeviceTest extends DeviceCore {
8
+ constructor() {
9
+ super();
10
+ }
11
+ /**
12
+ * 发送
13
+ */
14
+ async tesetSend() {
15
+ // 构建的完整协议帧
16
+ const completeFrame = Buffer.from([0x02, 0x03, 0x00, 0x0A, 0x00, 0x03, 0x25, 0xFA]);
17
+
18
+ return await this.sendSync(completeFrame, {
19
+ retry: 0,
20
+ timeout: 500,
21
+ parser: (readyBytes, writeBytes) => {
22
+
23
+ // 数据长度校验,应为 6
24
+ if (readyBytes[2] !== 6) {
25
+ throw new Error(`返回数据长度异常:${readyBytes[2]}`);
26
+ }
27
+
28
+ if (readyBytes.length < 11) {
29
+ throw new Error(`返回数据长度不足:${readyBytes.length}`);
30
+ }
31
+
32
+ const readUInt16 = (offset) =>
33
+ ((readyBytes[offset] << 8) | readyBytes[offset + 1]) >>> 0;
34
+
35
+ const internalTemperature = readUInt16(3) / 10 - 25;
36
+ const externalTemperature = readUInt16(5) / 10 - 25;
37
+ const humidity = readUInt16(7) / 10;
38
+
39
+ return {
40
+ internalTemperature,
41
+ externalTemperature,
42
+ humidity
43
+ };
44
+ }
45
+ });
46
+ }
47
+ }
48
+
49
+ module.exports = DeviceTest;
package/src/index.js ADDED
@@ -0,0 +1,14 @@
1
+ const CommDispatcherManager = require('./device/core/commDispatcherManager')
2
+ const DeviceTest = require('./drivers/test/DeviceTest')
3
+
4
+ const main = async () => {
5
+
6
+ const dispatcher = CommDispatcherManager.create("tcp", "192.168.1.113:8234")
7
+ const device = new DeviceTest();
8
+ device.setCommDispatcher(dispatcher)
9
+ dispatcher.setDevice(device)
10
+ console.log(await device.tesetSend())
11
+
12
+ }
13
+
14
+ main();