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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alice
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,173 @@
1
+ # device-communication-node
2
+
3
+ 🚀 轻量级统一物联网设备通信框架 (Node.js),基于适配器设计模式,支持 TCP/UDP/TCPSERVER、串口多通道自由切换与高度抽象。
4
+
5
+ > ⚠️ **当前版本为内测版本 (Beta),请勿直接用于生产环境。**
6
+
7
+ ## 📦 安装
8
+
9
+ 由于目前处于内测阶段,请通过指定 `@next` 标签或精确版本号进行安装:
10
+
11
+ ```bash
12
+ # 推荐方式
13
+ npm install device-communication-node@next
14
+
15
+ # 或者指定版本
16
+ npm install device-communication-node@1.0.0-beta.1
17
+ ```
18
+
19
+ ## ✨ 核心特性
20
+
21
+ - **多通道统一抽象**:一套底层逻辑,自由切换 TCP、UDP、SerialPort(串口)。
22
+ - **优秀的架构设计**:采用 Channel -> Dispatcher -> Factory 适配器架构,职责分明。
23
+ - **高稳定性**:针对底层网络(如 TCP Server)运行期异常进行了安全防护,防止进程崩溃。
24
+
25
+ ## 🛠️ 快速开始
26
+
27
+ ### 1. 具体设备类继承DeviceCore,并使用发送方法
28
+
29
+ ```
30
+ const DeviceCore = require('../../device/core/DeviceCore');
31
+ const checkUtils = require('../../device/utils/checkUtils');
32
+ const hexUtils = require('../../device/utils/hexUtils');
33
+ /**
34
+ * 测试设备
35
+ */
36
+ class DeviceTest extends DeviceCore {
37
+ constructor() {
38
+ super();
39
+ }
40
+ /**
41
+ * 发送
42
+ */
43
+ async tesetSend() {
44
+ // 构建的完整协议帧
45
+ const completeFrame = Buffer.from([0x02, 0x03, 0x00, 0x0A, 0x00, 0x03, 0x25, 0xFA]);
46
+
47
+ return await this.sendSync(completeFrame, {
48
+ retry: 0,
49
+ timeout: 500,
50
+ parser: (readyBytes, writeBytes) => {
51
+
52
+ // 数据长度校验,应为 6
53
+ if (readyBytes[2] !== 6) {
54
+ throw new Error(`返回数据长度异常:${readyBytes[2]}`);
55
+ }
56
+
57
+ if (readyBytes.length < 11) {
58
+ throw new Error(`返回数据长度不足:${readyBytes.length}`);
59
+ }
60
+
61
+ const readUInt16 = (offset) =>
62
+ ((readyBytes[offset] << 8) | readyBytes[offset + 1]) >>> 0;
63
+
64
+ const internalTemperature = readUInt16(3) / 10 - 25;
65
+ const externalTemperature = readUInt16(5) / 10 - 25;
66
+ const humidity = readUInt16(7) / 10;
67
+
68
+ return {
69
+ internalTemperature,
70
+ externalTemperature,
71
+ humidity
72
+ };
73
+ }
74
+ });
75
+ }
76
+ }
77
+
78
+ module.exports = DeviceTest;
79
+ ```
80
+
81
+ ### 2.建议针对具体设备协议,在具体设备类中重写DeviceCore中以下方法
82
+
83
+ ```
84
+ /**
85
+ * 基础校验
86
+ */
87
+ validate(readBytes) {
88
+ return readBytes && readBytes.length > 0;
89
+ }
90
+
91
+ /**
92
+ * 帧匹配校验
93
+ */
94
+ isMatch(writeBytes, readBytes) {
95
+ return this.validate(readBytes);
96
+ }
97
+
98
+ /**
99
+ * 帧解析逻辑 - 基类默认实现 (不解决拆包粘包)
100
+ * 子类需重写此方法以实现特定协议的拆包
101
+ */
102
+ parseFrame(onFrameReady) {
103
+ // 默认实现:直接把当前收到的全部内容当成一帧抛出
104
+ if (this.receiveBuffer.length > 0) {
105
+ const frame = Buffer.from(this.receiveBuffer);
106
+ this.receiveBuffer = Buffer.alloc(0); // 清空
107
+ if (this.validate(frame)) {
108
+ onFrameReady(frame);
109
+ }
110
+ }
111
+ }
112
+
113
+ /**
114
+ * 主动上报处理接口
115
+ */
116
+ onAutoReport(frame) {
117
+ const now = new Date().toISOString().replace('T', ' ').substring(0, 19);
118
+ console.log(`[${now}] 主动上报帧: ${HexUtils.bytesToHexString(frame)}`);
119
+ }
120
+
121
+ /**
122
+ * 默认回调
123
+ */
124
+ defaultCallback(readBytes, writeBytes) {
125
+ console.log("--- 默认回调 ---");
126
+ console.log("发送:", HexUtils.bytesToHexString(writeBytes));
127
+ console.log("接收:", HexUtils.bytesToHexString(readBytes));
128
+ }
129
+
130
+ /**
131
+ * 队列清空通知
132
+ */
133
+ onAllTasksCompleted() {
134
+ // 子类重写
135
+ }
136
+ ```
137
+
138
+ ### 3.外部使用
139
+
140
+ ```
141
+ const CommDispatcherManager = require('./device/core/commDispatcherManager')
142
+ const DeviceTest = require('./drivers/test/DeviceTest')
143
+
144
+ const main = async () => {
145
+
146
+ const dispatcher = CommDispatcherManager.create("tcp", "192.168.1.113:8234")
147
+ const device = new DeviceTest();
148
+ device.setCommDispatcher(dispatcher)
149
+ dispatcher.setDevice(device)
150
+ console.log(await device.tesetSend())
151
+
152
+ }
153
+
154
+ main();
155
+ ```
156
+
157
+ ### 4.结果
158
+
159
+ ```
160
+ PS D:\code_repository\private-project\device-communication-node> npm run dev
161
+
162
+ > device-communication-node@1.0.0-beta.1 dev
163
+ > node src/index.js
164
+
165
+ [Dispatcher] 执行任务: 02 03 00 0A 00 03 25 FA (尝试 1/1)
166
+ { internalTemperature: -25, externalTemperature: -25, humidity: 0 }
167
+ ```
168
+
169
+
170
+
171
+ ## 📄 开源许可证
172
+
173
+ [MIT License](https://www.google.com/search?q=LICENSE) © 2026 Alice
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "device-communication-node",
3
+ "version": "1.0.0-beta.1",
4
+ "description": "轻量级统一物联网设备通信框架,支持 TCP/UDP、串口等多通道自由切换与高度抽象。",
5
+ "main": "src/index.js",
6
+ "files": [
7
+ "src",
8
+ "README.md",
9
+ "LICENSE"
10
+ ],
11
+ "scripts": {
12
+ "dev": "node src/index.js"
13
+ },
14
+ "keywords": [
15
+ "device",
16
+ "communication",
17
+ "tcp",
18
+ "udp",
19
+ "hardware",
20
+ "iot"
21
+ ],
22
+ "author": "Alice",
23
+ "license": "MIT",
24
+ "type": "commonjs",
25
+ "dependencies": {
26
+ "serialport": "^13.0.0"
27
+ }
28
+ }
@@ -0,0 +1,73 @@
1
+ const EventEmitter = require('events')
2
+ const HexUtils = require('../utils/HexUtils')
3
+ /**
4
+ * 通信通道抽象类,定义了通信通道的基本接口和事件机制。
5
+ */
6
+ class CommChannel extends EventEmitter {
7
+ constructor() {
8
+ super()
9
+
10
+ this.charset = 'gb2312'
11
+ this.isOpen = false
12
+
13
+ // 正在连接的Promise(防重复open)
14
+ this.openingPromise = null
15
+ }
16
+
17
+ // 防止重复 open 的统一入口
18
+ _dedupeOpen(createFn) {
19
+ if (this.isOpen) {
20
+ return Promise.resolve()
21
+ }
22
+
23
+ if (this.openingPromise) {
24
+ return this.openingPromise
25
+ }
26
+
27
+ this.openingPromise = Promise.resolve()
28
+ .then(createFn)
29
+ .finally(() => {
30
+ this.openingPromise = null
31
+ })
32
+
33
+ return this.openingPromise
34
+ }
35
+
36
+ getConfig() {
37
+ throw new Error('必须实现 getConfig() 方法')
38
+ }
39
+
40
+ open() {
41
+ throw new Error('必须实现 open() 方法')
42
+ }
43
+
44
+ close() {
45
+ throw new Error('必须实现 close() 方法')
46
+ }
47
+
48
+ send(data) {
49
+ throw new Error('必须实现 send() 方法')
50
+ }
51
+
52
+ sendString(message) {
53
+ const buffer = Buffer.from(message, this.charset)
54
+ this.send(buffer)
55
+ }
56
+
57
+ onReceive(source, data) {
58
+ this.emit('receive', source, data, data.length)
59
+ }
60
+
61
+ triggerOpen(resource) {
62
+ this.isOpen = true
63
+ this.emit('open', resource)
64
+ }
65
+
66
+ triggerClose(resource) {
67
+ this.isOpen = false
68
+ this.emit('close', resource)
69
+ }
70
+ }
71
+
72
+ module.exports = CommChannel
73
+
@@ -0,0 +1,112 @@
1
+ const { SerialPort } = require('serialport')
2
+ const CommChannel = require('./CommChannel')
3
+
4
+ class SerialChannel extends CommChannel {
5
+
6
+ constructor(path, baudRate) {
7
+ super()
8
+
9
+ this.path = path
10
+ this.baudRate = baudRate || 19200
11
+
12
+ this.port = null
13
+ }
14
+
15
+ getIsOpen() {
16
+ return this.isOpen
17
+ }
18
+
19
+ getConfig() {
20
+ return {
21
+ path: this.path,
22
+ baudRate: this.baudRate
23
+ }
24
+ }
25
+
26
+ async open() {
27
+ return this._dedupeOpen(() => {
28
+ return new Promise((resolve, reject) => {
29
+
30
+ if (this.isOpen && this.port && this.port.isOpen) {
31
+ resolve()
32
+ return
33
+ }
34
+
35
+ if (this.port) {
36
+ try {
37
+ this.port.removeAllListeners()
38
+ } catch (e) { }
39
+ this.port = null
40
+ }
41
+
42
+ this.port = new SerialPort({
43
+ path: this.path,
44
+ baudRate: this.baudRate,
45
+ autoOpen: false
46
+ })
47
+
48
+ const openTimeout = setTimeout(() => {
49
+ reject(new Error(`串口打开超时: ${this.path}`))
50
+ }, 2000)
51
+
52
+ this.port.open((err) => {
53
+ clearTimeout(openTimeout)
54
+
55
+ if (err) {
56
+ reject(new Error(`串口打开失败: ${err.message}`))
57
+ return
58
+ }
59
+
60
+ this.triggerOpen(this.port)
61
+ resolve()
62
+ })
63
+
64
+ this.port.on('data', (data) => {
65
+ this.onReceive(this.port, data)
66
+ })
67
+
68
+ this.port.on('error', (err) => {
69
+ console.error(`串口 [${this.path}] 错误:`, err.message)
70
+
71
+ if (err.message.includes('Access denied') ||
72
+ err.message.includes('Disconnected')) {
73
+ this.isOpen = false
74
+ }
75
+ })
76
+ })
77
+ })
78
+ }
79
+
80
+ close() {
81
+
82
+ if (!this.isOpen || !this.port || !this.port.isOpen) {
83
+ this.isOpen = false;
84
+ return;
85
+ }
86
+
87
+ this.port.close((err) => {
88
+ if (err) {
89
+ console.error(`[SerialChannel] 关闭串口 ${this.path} 失败:`, err.message);
90
+ }
91
+ this.isOpen = false;
92
+ this.triggerClose(this.port);
93
+ })
94
+ }
95
+
96
+ send(data) {
97
+ if (!this.isOpen) {
98
+ throw new Error('串口未打开')
99
+ }
100
+
101
+ return new Promise((resolve, reject) => {
102
+ this.port.write(data, (err) => {
103
+ if (err) reject(err)
104
+ else {
105
+ this.port.drain(resolve)
106
+ }
107
+ })
108
+ })
109
+ }
110
+ }
111
+
112
+ module.exports = SerialChannel
@@ -0,0 +1,84 @@
1
+ const net = require('net')
2
+ const CommChannel = require('./CommChannel')
3
+
4
+ class TcpChannel extends CommChannel {
5
+
6
+ constructor(host, port) {
7
+ super()
8
+
9
+ this.host = host
10
+ this.port = port
11
+
12
+ this.socket = null
13
+ }
14
+
15
+ getIsOpen() {
16
+ return this.isOpen
17
+ }
18
+
19
+ getConfig() {
20
+ return {
21
+ host: this.host,
22
+ port: this.port
23
+ }
24
+ }
25
+
26
+ open() {
27
+ return this._dedupeOpen(() => {
28
+
29
+ return new Promise((resolve, reject) => {
30
+
31
+ this.socket = new net.Socket()
32
+
33
+ const connectTimeout = setTimeout(() => {
34
+ this.socket.destroy()
35
+ reject(new Error(`TCP连接超时 ${this.host}:${this.port}`))
36
+ }, 2000)
37
+
38
+ this.socket.connect(this.port, this.host, () => {
39
+ clearTimeout(connectTimeout)
40
+
41
+ this.triggerOpen(this.socket)
42
+ resolve()
43
+ })
44
+
45
+ this.socket.on('error', (err) => {
46
+ clearTimeout(connectTimeout)
47
+ reject(new Error(`TCP连接失败: ${err.message}`))
48
+ })
49
+
50
+ this.socket.on('close', () => {
51
+ this.isOpen = false
52
+ this.triggerClose(this.socket)
53
+ })
54
+
55
+ this.socket.on('data', (data) => {
56
+ this.onReceive(this.socket, data)
57
+ })
58
+ })
59
+ })
60
+ }
61
+
62
+ close() {
63
+ if (!this.socket) return
64
+
65
+ this.socket.destroy()
66
+ this.socket = null
67
+ this.isOpen = false
68
+ }
69
+
70
+ send(data) {
71
+ if (!this.isOpen || !this.socket) {
72
+ throw new Error('TCP未连接')
73
+ }
74
+
75
+ return new Promise((resolve, reject) => {
76
+ this.socket.write(data, (err) => {
77
+ if (err) reject(err)
78
+ else resolve()
79
+ })
80
+ })
81
+ }
82
+ }
83
+
84
+ module.exports = TcpChannel
@@ -0,0 +1,149 @@
1
+ const net = require('net')
2
+ const CommChannel = require('./CommChannel')
3
+
4
+ class TcpServerChannel extends CommChannel {
5
+
6
+ constructor(port = 9000, host = '0.0.0.0') {
7
+ super()
8
+
9
+ this.port = port
10
+ this.host = host
11
+
12
+ this.server = null
13
+ this.isOpen = false
14
+
15
+ // 管理所有客户端
16
+ this.clients = new Set()
17
+ }
18
+
19
+ getIsOpen() {
20
+ return this.isOpen
21
+ }
22
+
23
+ getConfig() {
24
+ return {
25
+ host: this.host,
26
+ port: this.port
27
+ }
28
+ }
29
+
30
+ open() {
31
+ return this._dedupeOpen(() => {
32
+
33
+ if (this.isOpen) {
34
+ return Promise.resolve()
35
+ }
36
+
37
+ this.server = net.createServer((socket) => {
38
+ this.clients.add(socket)
39
+ this.onClientConnect(socket)
40
+
41
+ socket.on('data', (data) => {
42
+ this.onReceive(socket, data)
43
+ })
44
+
45
+ socket.on('close', () => {
46
+ this.clients.delete(socket)
47
+ this.onClientDisconnect(socket)
48
+ })
49
+
50
+ socket.on('error', (err) => {
51
+ this.emit('clientError', socket, err)
52
+ })
53
+ })
54
+
55
+ this.server.on('error', (err) => {
56
+ console.error('TCP Server 运行期错误:', err)
57
+ if (!this.server.listening) {
58
+ this._handleCloseCleanup()
59
+ }
60
+ })
61
+
62
+ return new Promise((resolve, reject) => {
63
+ const startErrorHandler = (err) => {
64
+ reject(err)
65
+ }
66
+
67
+ this.server.once('error', startErrorHandler)
68
+
69
+ this.server.listen(this.port, this.host, () => {
70
+ this.server.off('error', startErrorHandler)
71
+
72
+ this.isOpen = true
73
+ this.triggerOpen(this.server)
74
+ resolve()
75
+ })
76
+ })
77
+ })
78
+ }
79
+
80
+ _handleCloseCleanup() {
81
+ this.isOpen = false
82
+ // 关闭所有客户端
83
+ for (const client of this.clients) {
84
+ client.destroy()
85
+ }
86
+ this.clients.clear()
87
+ this.triggerClose(this.server)
88
+ }
89
+
90
+ close() {
91
+ if (!this.isOpen) return Promise.resolve()
92
+
93
+ return new Promise((resolve) => {
94
+ this._handleCloseCleanup()
95
+
96
+ if (this.server) {
97
+ this.server.close(() => {
98
+ this.server = null
99
+ resolve()
100
+ })
101
+ } else {
102
+ resolve()
103
+ }
104
+ })
105
+ }
106
+
107
+ send(data, client = null) {
108
+
109
+ if (!this.isOpen) {
110
+ throw new Error('TCP Server未启动')
111
+ }
112
+
113
+ // 如果指定 client,就单发
114
+ if (client) {
115
+ return this._write(client, data)
116
+ }
117
+
118
+ // 否则广播
119
+ const tasks = []
120
+ for (const c of this.clients) {
121
+ tasks.push(this._write(c, data))
122
+ }
123
+
124
+ return Promise.all(tasks)
125
+ }
126
+
127
+ _write(socket, data) {
128
+ return new Promise((resolve, reject) => {
129
+ // 检查客户端连接是否还可写
130
+ if (!socket.writable) {
131
+ return reject(new Error('客户端连接已不可写'))
132
+ }
133
+ socket.write(data, (err) => {
134
+ if (err) reject(err)
135
+ else resolve()
136
+ })
137
+ })
138
+ }
139
+
140
+ onClientConnect(socket) {
141
+ this.emit('clientConnect', socket)
142
+ }
143
+
144
+ onClientDisconnect(socket) {
145
+ this.emit('clientDisconnect', socket)
146
+ }
147
+ }
148
+
149
+ module.exports = TcpServerChannel