Nodejs serial port to tcp



有没有一种方法可以建立一个主机,使用nodejs通过tcp流式传输串行连接-我想将传感器数据从我计算机上的iot设备流式传输到连接的计算机和web服务器。原始数据的流传输很好-远程计算机会处理它。我正在研究netserialportnpm包,但我不确定如何将两者结合起来。。。

谢谢!

准备

几乎每个供应商或设备都有自己的串行通信协议。通常,这些设备也使用带有标头、校验和的数据包,但每个设备都以不同的方式使用。

第一个问题实际上是,您希望将数据包标头和校验和信息转发到何种程度。您可能想要将传入的数据包转换为事件,或者可能已经转换为某种JSON消息。

假设您只想在没有任何预处理的情况下以原始格式转发数据,那么确定数据包的起点和终点仍然很有价值。当您通过TCP/IP刷新数据时,最好不要在其中一个串行数据包的中途这样做。

例如,您的设备可能是条形码扫描仪。大多数条形码扫描仪在每次扫描结束时都会发送CR(回车(。主动读取传入字节以查找CR字符是有意义的。然后,每次注意到CR字符时,都会刷新缓冲区中的字节。

但它并不总是CR。有些设备将数据封装在STX(0x02(和ETX(0x03(字符之间。还有一些发送固定长度的包(例如,每条消息12个字节(。

为了清楚起见,您可能会每100个字节发送一次数据,而一条消息实际上是12个字节。那会打碎一些包裹。偶尔你的TCP接收器会收到一个不完整的数据包。说了这么多。您也可以在TCP接收端添加所有这些逻辑。当收到一个不完整的数据包时,您可以将其保存在缓冲区中,假设下一个传入的数据包将包含丢失的字节。

考虑一下是否值得

请注意,有一些商业RS232到以太网设备,您可以从货架上购买并配置(约100EUR(,完全可以随心所欲。通常在这种设备的设置中,您可以选择配置一个flush字符。(例如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);
}
}

上面的代码连续地从串行设备读取数据;协议";以确定消息的起始/结束位置。它有一个";听众";,这是一个回调。它还可以通过其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连接。在这里,您必须确定哪一端是客户端,哪一端是服务器。我现在跳过了,因为有很多教程和代码示例向您展示了如何设置TCP/IP客户端-服务器连接。

在上面的一些代码中,我使用函数toHexString(Buffer)将缓冲区的内容转换为十六进制格式,这样可以更容易地将其打印到控制台日志中。

export function toHexString(byteArray: Buffer) {
let s = '0x';
byteArray.forEach(function (byte) {
s += ('0' + (byte & 0xFF).toString(16)).slice(-2);
});
return s;
}