Nodejs串口转tcp
Nodejs serial port to tcp
有没有办法建立主机以使用 nodejs 通过 tcp 流式传输串行连接 - 我想将传感器数据从我计算机上的物联网设备流式传输到连接的计算机到 Web 服务器。原始数据的流式传输很好——远程计算机将对其进行处理。我正在研究 net
和 serialport
npm 包 - 但我不确定如何将两者结合起来......
谢谢!
准备
几乎每个供应商或设备都有自己的串行通信协议。通常这些设备也使用带有 headers、校验和的数据包,但每个设备以不同的方式执行此操作。
第一个问题确实是,您想将数据包headers和校验和信息转发到什么程度。您可能希望将传入的数据包转换为事件,或者可能已经转换为某种 JSON 消息。
假设您只想转发原始格式的数据而不需要任何 pre-processing,确定数据包的开始和结束位置仍然很有价值。当您通过 TCP/IP 刷新数据时,最好不要在这些串行数据包之一中途这样做。
例如,您的设备可能是条形码扫描仪。大多数条码扫描器在每次扫描结束时发送一个 CR(回车 return)。主动读取传入字节以查找 CR 字符是有意义的。然后每次注意到一个 CR 字符时,您就会刷新字节缓冲区。
但是,它并不总是 CR。一些设备将它们的数据打包在 STX (0x02) 和 ETX (0x03) 字符之间。还有一些发送 fixed-length 包(例如每条消息 12 个字节)。
为了清楚起见,您最终可能每 100 个字节发送一次数据,而一条消息实际上是 12 个字节。那会破坏一些数据包。偶尔您的 TCP 接收器会收到一个不完整的数据包。说了这么多。您还可以在 TCP 接收端添加所有这些逻辑。当收到不完整的数据包时,您可以将其保存在缓冲区中,假设下一个传入数据包将包含丢失的字节。
考虑一下是否值得
请注意,您可以购买现成的商用 RS232-to-ethernet 设备并进行配置(约 100 欧元),它们完全可以满足您的需求。通常在设置此类设备时,您可以选择配置 flush-character。 (例如那个 CR)。 MOXA 可能是您能得到的最好的。 ADAM 也生产不错的设备。这些供应商生产这种设备已有 30 年了。
让你开始
但是为了锻炼身体,我们开始吧。
首先,您需要一些东西来与您的串行设备进行通信。
我用过这个:
npm install serialport@^9.1.0
您几乎可以盲目复制以下代码。但显然您需要设置自己的 RS232 或 USB 端口设置。查看设备手册以确定波特率、数据位、停止位、奇偶校验和可选的 RTS/DTR
import SerialPort from "serialport";
export class RS232Port {
private port: SerialPort;
constructor(private listener: (buffer: Buffer) => any, private protocol) {
this.port = new SerialPort("/dev/ttyS0", {
baudRate: 38400,
dataBits: 8,
stopBits: 1,
parity: "none",
});
// check your RTS/DTR settings.
// this.port.on('open', () => {
// this.port.set({rts: true, dtr: false}, () => {
// });
//});
const parser = this.port.pipe(this.protocol);
parser.on('data', (data) => {
console.log(`received packet:[${toHexString(data)}]`);
if (this.listener) {
this.listener(data);
}
});
}
sendBytes(buffer: Buffer) {
console.log(`write packet:[${toHexString(buffer)}]`);
this.port.write(buffer);
}
}
上面的代码不断地从串行设备读取数据,并使用“协议”来确定消息的位置start/end。它有一个“监听器”,这是一个回调。它还可以使用其 sendBytes
函数发送字节。
这将我们带到了协议,如前所述,在找到分隔符之前应该阅读它。
因为我不知道你的分隔符是什么。我会给你一个替代方案,它只是等待沉默。它假设在一定时间内没有传入数据时,消息将完成。
export class TimeoutProtocol extends Transform {
maxBufferSize: number;
currentPacket: [];
interval: number;
intervalID: any;
constructor(options: { interval: number, maxBufferSize: number }) {
super()
const _options = { maxBufferSize: 65536, ...options }
if (!_options.interval) {
throw new TypeError('"interval" is required')
}
if (typeof _options.interval !== 'number' || Number.isNaN(_options.interval)) {
throw new TypeError('"interval" is not a number')
}
if (_options.interval < 1) {
throw new TypeError('"interval" is not greater than 0')
}
if (typeof _options.maxBufferSize !== 'number' || Number.isNaN(_options.maxBufferSize)) {
throw new TypeError('"maxBufferSize" is not a number')
}
if (_options.maxBufferSize < 1) {
throw new TypeError('"maxBufferSize" is not greater than 0')
}
this.maxBufferSize = _options.maxBufferSize
this.currentPacket = []
this.interval = _options.interval
this.intervalID = -1
}
_transform(chunk: [], encoding, cb) {
clearTimeout(this.intervalID)
for (let offset = 0; offset < chunk.length; offset++) {
this.currentPacket.push(chunk[offset])
if (this.currentPacket.length >= this.maxBufferSize) {
this.emitPacket()
}
}
this.intervalID = setTimeout(this.emitPacket.bind(this), this.interval)
cb()
}
emitPacket() {
clearTimeout(this.intervalID)
if (this.currentPacket.length > 0) {
this.push(Buffer.from(this.currentPacket))
}
this.currentPacket = []
}
_flush(cb) {
this.emitPacket()
cb()
}
}
然后拼图的最后一块是 TCP/IP 连接。这里要确定哪一端是client,哪一端是server。我现在跳过了它,因为有很多教程和代码示例向您展示了如何设置 TCP/IP client-server 连接。
在上面的一些代码中,我使用函数 toHexString(Buffer)
将缓冲区的内容转换为十六进制格式,这样可以更轻松地将其打印到控制台日志中。
export function toHexString(byteArray: Buffer) {
let s = '0x';
byteArray.forEach(function (byte) {
s += ('0' + (byte & 0xFF).toString(16)).slice(-2);
});
return s;
}
有没有办法建立主机以使用 nodejs 通过 tcp 流式传输串行连接 - 我想将传感器数据从我计算机上的物联网设备流式传输到连接的计算机到 Web 服务器。原始数据的流式传输很好——远程计算机将对其进行处理。我正在研究 net
和 serialport
npm 包 - 但我不确定如何将两者结合起来......
谢谢!
准备
几乎每个供应商或设备都有自己的串行通信协议。通常这些设备也使用带有 headers、校验和的数据包,但每个设备以不同的方式执行此操作。
第一个问题确实是,您想将数据包headers和校验和信息转发到什么程度。您可能希望将传入的数据包转换为事件,或者可能已经转换为某种 JSON 消息。
假设您只想转发原始格式的数据而不需要任何 pre-processing,确定数据包的开始和结束位置仍然很有价值。当您通过 TCP/IP 刷新数据时,最好不要在这些串行数据包之一中途这样做。
例如,您的设备可能是条形码扫描仪。大多数条码扫描器在每次扫描结束时发送一个 CR(回车 return)。主动读取传入字节以查找 CR 字符是有意义的。然后每次注意到一个 CR 字符时,您就会刷新字节缓冲区。
但是,它并不总是 CR。一些设备将它们的数据打包在 STX (0x02) 和 ETX (0x03) 字符之间。还有一些发送 fixed-length 包(例如每条消息 12 个字节)。
为了清楚起见,您最终可能每 100 个字节发送一次数据,而一条消息实际上是 12 个字节。那会破坏一些数据包。偶尔您的 TCP 接收器会收到一个不完整的数据包。说了这么多。您还可以在 TCP 接收端添加所有这些逻辑。当收到不完整的数据包时,您可以将其保存在缓冲区中,假设下一个传入数据包将包含丢失的字节。
考虑一下是否值得
请注意,您可以购买现成的商用 RS232-to-ethernet 设备并进行配置(约 100 欧元),它们完全可以满足您的需求。通常在设置此类设备时,您可以选择配置 flush-character。 (例如那个 CR)。 MOXA 可能是您能得到的最好的。 ADAM 也生产不错的设备。这些供应商生产这种设备已有 30 年了。
让你开始
但是为了锻炼身体,我们开始吧。 首先,您需要一些东西来与您的串行设备进行通信。 我用过这个:
npm install serialport@^9.1.0
您几乎可以盲目复制以下代码。但显然您需要设置自己的 RS232 或 USB 端口设置。查看设备手册以确定波特率、数据位、停止位、奇偶校验和可选的 RTS/DTR
import SerialPort from "serialport";
export class RS232Port {
private port: SerialPort;
constructor(private listener: (buffer: Buffer) => any, private protocol) {
this.port = new SerialPort("/dev/ttyS0", {
baudRate: 38400,
dataBits: 8,
stopBits: 1,
parity: "none",
});
// check your RTS/DTR settings.
// this.port.on('open', () => {
// this.port.set({rts: true, dtr: false}, () => {
// });
//});
const parser = this.port.pipe(this.protocol);
parser.on('data', (data) => {
console.log(`received packet:[${toHexString(data)}]`);
if (this.listener) {
this.listener(data);
}
});
}
sendBytes(buffer: Buffer) {
console.log(`write packet:[${toHexString(buffer)}]`);
this.port.write(buffer);
}
}
上面的代码不断地从串行设备读取数据,并使用“协议”来确定消息的位置start/end。它有一个“监听器”,这是一个回调。它还可以使用其 sendBytes
函数发送字节。
这将我们带到了协议,如前所述,在找到分隔符之前应该阅读它。
因为我不知道你的分隔符是什么。我会给你一个替代方案,它只是等待沉默。它假设在一定时间内没有传入数据时,消息将完成。
export class TimeoutProtocol extends Transform {
maxBufferSize: number;
currentPacket: [];
interval: number;
intervalID: any;
constructor(options: { interval: number, maxBufferSize: number }) {
super()
const _options = { maxBufferSize: 65536, ...options }
if (!_options.interval) {
throw new TypeError('"interval" is required')
}
if (typeof _options.interval !== 'number' || Number.isNaN(_options.interval)) {
throw new TypeError('"interval" is not a number')
}
if (_options.interval < 1) {
throw new TypeError('"interval" is not greater than 0')
}
if (typeof _options.maxBufferSize !== 'number' || Number.isNaN(_options.maxBufferSize)) {
throw new TypeError('"maxBufferSize" is not a number')
}
if (_options.maxBufferSize < 1) {
throw new TypeError('"maxBufferSize" is not greater than 0')
}
this.maxBufferSize = _options.maxBufferSize
this.currentPacket = []
this.interval = _options.interval
this.intervalID = -1
}
_transform(chunk: [], encoding, cb) {
clearTimeout(this.intervalID)
for (let offset = 0; offset < chunk.length; offset++) {
this.currentPacket.push(chunk[offset])
if (this.currentPacket.length >= this.maxBufferSize) {
this.emitPacket()
}
}
this.intervalID = setTimeout(this.emitPacket.bind(this), this.interval)
cb()
}
emitPacket() {
clearTimeout(this.intervalID)
if (this.currentPacket.length > 0) {
this.push(Buffer.from(this.currentPacket))
}
this.currentPacket = []
}
_flush(cb) {
this.emitPacket()
cb()
}
}
然后拼图的最后一块是 TCP/IP 连接。这里要确定哪一端是client,哪一端是server。我现在跳过了它,因为有很多教程和代码示例向您展示了如何设置 TCP/IP client-server 连接。
在上面的一些代码中,我使用函数 toHexString(Buffer)
将缓冲区的内容转换为十六进制格式,这样可以更轻松地将其打印到控制台日志中。
export function toHexString(byteArray: Buffer) {
let s = '0x';
byteArray.forEach(function (byte) {
s += ('0' + (byte & 0xFF).toString(16)).slice(-2);
});
return s;
}