Initial Node.js serial transport (#779)
* Initial Node.js serial transport * Minor doc fixes * Add serialport to lockfile * Typo fix * Fix link:
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
# @meshtastic/transport-node-serial
|
||||
|
||||
[](https://jsr.io/@meshtastic/transport-node-serial)
|
||||
[](https://github.com/meshtastic/js/actions/workflows/ci.yml)
|
||||
[](https://cla-assistant.io/meshtastic/meshtastic.js)
|
||||
[](https://opencollective.com/meshtastic/)
|
||||
[](https://vercel.com?utm_source=meshtastic&utm_campaign=oss)
|
||||
|
||||
## Overview
|
||||
|
||||
`@meshtastic/transport-noden-node` Provides Serial transport (Node) for Meshtastic
|
||||
devices. Installation instructions are available at
|
||||
[JSR](https://jsr.io/@meshtastic/transport-node-serial)
|
||||
[NPM](https://www.npmjs.com/package/@meshtastic/transport-node-serial)
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { MeshDevice } from "@meshtastic/core";
|
||||
import { TransportNodeSerial } from "@meshtastic/transport-node-serial";
|
||||
|
||||
const transport = await TransportNodeSerial.create("/dev/cu.usbserial-0001");
|
||||
const device = new MeshDevice(transport);
|
||||
```
|
||||
|
||||
## Stats
|
||||
|
||||

|
||||
@@ -0,0 +1 @@
|
||||
export { TransportNodeSerial } from "./src/transport.ts";
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "@meshtastic/transport-node-serial",
|
||||
"version": "0.0.1",
|
||||
"description": "NodeJS-specific serial transport layer for Meshtastic web applications.",
|
||||
"exports": {
|
||||
".": "./dist/mod.mjs"
|
||||
},
|
||||
"main": "./dist/mod.mjs",
|
||||
"module": "./dist/mod.mjs",
|
||||
"types": "./dist/mod.d.mts",
|
||||
|
||||
"license": "GPL-3.0-only",
|
||||
"tsdown": {
|
||||
"entry": "mod.ts",
|
||||
"dts": true,
|
||||
"format": ["esm"],
|
||||
"splitting": false,
|
||||
"clean": true
|
||||
},
|
||||
"files": [
|
||||
"package.json",
|
||||
"README.md",
|
||||
"LICENSE",
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"prepack": "cp ../../LICENSE ./LICENSE",
|
||||
"clean": "rm -rf dist LICENSE",
|
||||
"build:npm": "tsdown",
|
||||
"publish:npm": "pnpm clean && pnpm build:npm && pnpm publish --access public",
|
||||
"prepare:jsr": "rm -rf dist && pnpm dlx pkg-to-jsr",
|
||||
"publish:jsr": "pnpm run prepack && pnpm prepare:jsr && deno publish --allow-dirty --no-check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@meshtastic/core": "workspace:*",
|
||||
"serialport": "^13.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Readable, Writable } from "node:stream";
|
||||
import type { Types } from "@meshtastic/core";
|
||||
import { Utils } from "@meshtastic/core";
|
||||
import { SerialPort } from "serialport";
|
||||
|
||||
export class TransportNodeSerial implements Types.Transport {
|
||||
private readonly _toDevice: WritableStream<Uint8Array>;
|
||||
private readonly _fromDevice: ReadableStream<Types.DeviceOutput>;
|
||||
private port: SerialPort | undefined;
|
||||
|
||||
/**
|
||||
* Creates and connects a new TransportNode instance.
|
||||
* @param path - Path to the serial device
|
||||
* @param baudRate - The port number for the TCP connection (defaults to 4403).
|
||||
* @returns A promise that resolves with a connected TransportNode instance.
|
||||
*/
|
||||
public static create(path: string, baudRate = 115200): Promise<TransportNodeSerial> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const port = new SerialPort({
|
||||
path,
|
||||
baudRate,
|
||||
autoOpen: true,
|
||||
});
|
||||
|
||||
const onError = (err: Error) => {
|
||||
port.close();
|
||||
reject(err);
|
||||
};
|
||||
|
||||
port.once("error", onError);
|
||||
port.on("open", () => {
|
||||
port.removeListener("error", onError);
|
||||
resolve(new TransportNodeSerial(port));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new TransportNode.
|
||||
* @param port - An active Node.js SerialPort connection.
|
||||
*/
|
||||
constructor(port: SerialPort) {
|
||||
this.port = port;
|
||||
this.port.on("error", (err) => {
|
||||
console.error("Serial port connection error:", err);
|
||||
});
|
||||
|
||||
const fromDeviceSource = Readable.toWeb(
|
||||
port,
|
||||
) as ReadableStream<Uint8Array>;
|
||||
this._fromDevice = fromDeviceSource.pipeThrough(Utils.fromDeviceStream());
|
||||
|
||||
// Stream for data going FROM the application TO the Meshtastic device.
|
||||
const toDeviceTransform = Utils.toDeviceStream;
|
||||
this._toDevice = toDeviceTransform.writable;
|
||||
|
||||
// The readable end of the transform is then piped to the Node.js SerialPort connection.
|
||||
// A similar assertion is needed here because `Writable.toWeb` also returns
|
||||
// a generically typed stream (`WritableStream<any>`).
|
||||
toDeviceTransform.readable
|
||||
.pipeTo(Writable.toWeb(port) as WritableStream<Uint8Array>)
|
||||
.catch((err) => {
|
||||
console.error("Error piping data to serial port:", err);
|
||||
this.port.close(err as Error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The WritableStream to send data to the Meshtastic device.
|
||||
*/
|
||||
public get toDevice(): WritableStream<Uint8Array> {
|
||||
return this._toDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* The ReadableStream to receive data from the Meshtastic device.
|
||||
*/
|
||||
public get fromDevice(): ReadableStream<Types.DeviceOutput> {
|
||||
return this._fromDevice;
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.port.close();
|
||||
this.port = undefined;
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"target": "ES2020",
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"moduleResolution": "bundler",
|
||||
"emitDeclarationOnly": false,
|
||||
"esModuleInterop": true,
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user