@maustec/slip
v0.1.0
Published
SLIP protocol library for Maus-Tec devices
Readme
@maustec/slip
TypeScript client for the Maus-Tec SLIP protocol. This is a base module to be consumed by product-specific SDKs. It provides all framing, request/ response correlation, ACK handling, chunked-message reassembly, and typed wrappers for every SLIP command exposed by the firmware.
npm install @maustec/slipQuick start
This package is byte-source agnostic and can be used with any transport library
pointed at your connected device. SlipTransport expects an async write callback,
and bytes are written to it with receive(). Everything else (nonce allocation,
timeouts, response correlation, ACKs) is handled internally.
Node (serialport)
import { SerialPort } from 'serialport';
import { SlipTransport } from '@maustec/slip';
const port = new SerialPort({ path: '/dev/ttyUSB0', baudRate: 115200 });
const transport = new SlipTransport({
write: (data) =>
new Promise((resolve, reject) =>
port.write(Buffer.from(data), (err) => (err ? reject(err) : resolve())),
),
onEvent: (pkt) => console.log('event', pkt),
});
port.on('data', (buf: Buffer) => transport.receive(new Uint8Array(buf)));
const info = await transport.system.deviceInfo();
console.log(`${info.vendor} ${info.productName} (fw ${info.fwVersion})`);Browser (WebSerial)
const port = await navigator.serial.requestPort();
await port.open({ baudRate: 115200 });
const writer = port.writable!.getWriter();
const reader = port.readable!.getReader();
const transport = new SlipTransport({
write: (data) => writer.write(data),
});
(async () => {
for (;;) {
const { value, done } = await reader.read();
if (done) break;
if (value) transport.receive(value);
}
})();Built-in operation modules
Operations are grouped by module on the transport instance. Each method
returns a typed result and throws SlipError on a non-SUCCESS device
status.
await transport.system.ping(new TextEncoder().encode('hello'));
await transport.system.firmwareVersion(); // => '1.4.2'
await transport.system.health(); // => { framesOk, errCrc, ... }
await transport.file.list('/sdcard'); // => FileEntry[]
await transport.file.read('/sdcard/log.txt'); // chunked download
await transport.file.write('/sdcard/cfg.json', bytes);
await transport.events.subscribe([0x42]);
await transport.log.tail();Extending for a product SDK
Product firmware adds its own SLIP command interfaces. Codegen emits raw wrappers
under generated/<module>/commands.ts. The developer is expected to build a thin
facade on top to expose a more developer-friendly interface.
import { SlipTransport, type SendOptions } from '@maustec/slip';
import { setSpeed, getStatus } from './generated/motor/commands.js';
export class MotorOps {
constructor(private transport: SlipTransport) {}
setSpeed(value: number, opts?: SendOptions) {
return setSpeed(this.transport, { value }, opts);
}
status(opts?: SendOptions) {
return getStatus(this.transport, opts);
}
}
export class MyDevice {
readonly motor: MotorOps;
constructor(private readonly transport: SlipTransport) {
this.motor = new MotorOps(transport);
}
// Built-in modules remain reachable through the transport.
get system() { return this.transport.system; }
}For direct access to the raw command wrappers, import them directly from the codegen output:
import * as motor from './generated/motor/commands.js';
await motor.setSpeed(transport, { value: 128 });Error handling
import { SlipError, SlipStatus } from '@maustec/slip';
try {
await transport.file.read('/missing');
} catch (err) {
if (err instanceof SlipError && err.status === SlipStatus.ERR_NOT_FOUND) {
// handle missing file
} else {
throw err;
}
}Development
npm install
npm test # vitest
npm run lint
npm run build
npm run smoke -- /dev/ttyUSB0 # exercise a real device (smoke is not literal, we hope)License
MIT
