@am32/serial-msp
v0.1.0
Published
Standalone MSP codec and browser serial transport utilities for AM32.
Readme
@am32/serial-msp
MSP codec, parser, client, and browser serial transport utilities extracted from the AM32 configurator.
@am32/serial-msp is split into focused entrypoints so you can use MSP helpers on their own or combine them with the Web Serial transport layer.
Install
Install the package itself:
npm install @am32/serial-mspSerial transport usage is built on top of webserial-wrapper, which is included as a package dependency.
Entrypoints
@am32/serial-msp- convenience root export for both MSP and serial APIs@am32/serial-msp/msp- MSP commands, encoders, parser, andMspClient@am32/serial-msp/serial- packet boundary probes and browser/Web Serial transport
Use the subpath imports when you want the clearest intent in application code:
import { MSP_COMMANDS, MspClient, encodeMspCommand, parseMspResponse } from '@am32/serial-msp/msp';
import { SerialTransport, inferPacketProbe } from '@am32/serial-msp/serial';The root entrypoint is available as a convenience:
import { MSP_COMMANDS, MspClient, SerialTransport, inferPacketProbe } from '@am32/serial-msp';What This Package Covers
- MSP utilities are transport-agnostic and can be used independently of Web Serial
parseMspResponse()parses MSP v1$M...packets only; it does not parse MSP v2 responsesSerialTransportis browser-focused and built around the Web Serial API- serial transport usage requires a
webserial-wrapperWebSerialinstance and aSerialPort
MSP Examples
Encode an MSP request
Use encodeMspCommand() when you want the package to encode MSP v1 for command IDs <= 254 and MSP v2 otherwise.
import { MSP_COMMANDS, encodeMspCommand } from '@am32/serial-msp/msp';
const request = encodeMspCommand(MSP_COMMANDS.MSP_API_VERSION);
const bytes = new Uint8Array(request);
// bytes: "$M<" + payload length + command + checksumTo force a specific protocol version, use encodeMspV1() or encodeMspV2() directly:
import { MSP_COMMANDS, encodeMspV1, encodeMspV2 } from '@am32/serial-msp/msp';
const v1Request = encodeMspV1(MSP_COMMANDS.MSP_MOTOR_CONFIG, new Uint8Array());
const dshotPayload = new Uint8Array([0x01, 0x00, 0x00, 0x00]);
const v2Request = encodeMspV2(MSP_COMMANDS.MSP2_SEND_DSHOT_COMMAND, dshotPayload);Parse an MSP response buffer
parseMspResponse() consumes a Uint8Array. On successful parse it returns an object with the numeric command ID in the commandName field and a DataView over the payload; otherwise it returns undefined for incomplete or checksum-invalid input. It parses MSP v1 $M... packets only, not MSP v2 responses.
import { MSP_COMMANDS, parseMspResponse } from '@am32/serial-msp/msp';
const response = new Uint8Array([
36, 77, 62,
3,
MSP_COMMANDS.MSP_API_VERSION,
1, 44, 0,
47
]);
const parsed = parseMspResponse(response);
if (parsed?.commandName === MSP_COMMANDS.MSP_API_VERSION) {
const major = parsed.data.getUint8(0);
const minor = parsed.data.getUint8(1);
const patch = parsed.data.getUint8(2);
console.log({ major, minor, patch });
}The current implementation accepts $M<, $M>, and $M! marker bytes after the $M header.
Create an MspClient with an adapter
MspClient wraps request encoding plus response parsing. The adapter shape matches the AM32 configurator pattern and includes write(), read(), and canRead() methods. Only write() is needed for send() and sendWithPromise(), while read() and canRead() are used by MspClient.read().
import { MSP_COMMANDS, MspClient } from '@am32/serial-msp/msp';
const adapter = {
async write(buffer: ArrayBuffer, timeout = 250): Promise<Uint8Array | null> {
void timeout;
// Replace with your own transport implementation.
return new Uint8Array([
36, 77, 62,
3,
MSP_COMMANDS.MSP_API_VERSION,
1, 44, 0,
47
]);
},
async read<T = Uint8Array>(): Promise<ReadableStreamReadResult<T>> {
return {
done: false,
value: undefined
};
},
canRead() {
return false;
}
};
const client = new MspClient(adapter, {
log: console.log,
logError: console.error
});
const parsed = await client.sendWithPromise(MSP_COMMANDS.MSP_API_VERSION);
const major = parsed.data.getUint8(0);Web Serial Transport Examples
@am32/serial-msp/serial is intended for browser environments that expose Web Serial. It uses webserial-wrapper stream helpers internally.
Construct SerialTransport
SerialTransport needs a WebSerial instance, the selected SerialPort, and optional stream getters/setters if you want to reuse the same stream across exchanges.
import type { WebSerial } from 'webserial-wrapper';
import type { StreamInfo } from 'webserial-wrapper';
import { SerialTransport } from '@am32/serial-msp/serial';
declare const serial: WebSerial;
declare const port: SerialPort;
let stream: StreamInfo | null = null;
const transport = new SerialTransport({
serial,
port,
getStream: () => stream,
setStream: (nextStream) => {
stream = nextStream;
},
logError: console.error
});Call exchange() with inferPacketProbe
This matches the configurator's request/response flow: encode an MSP packet, infer the correct completion probe from the outgoing bytes, then await the combined response buffer.
import { MSP_COMMANDS, encodeMspCommand, parseMspResponse } from '@am32/serial-msp/msp';
import { SerialTransport, inferPacketProbe } from '@am32/serial-msp/serial';
declare const transport: SerialTransport;
const request = encodeMspCommand(MSP_COMMANDS.MSP_API_VERSION);
const requestBytes = new Uint8Array(request);
const response = await transport.exchange(request, {
timeout: 250,
probe: inferPacketProbe(requestBytes)
});
if (response) {
const parsed = parseMspResponse(response);
console.log(parsed?.commandName);
}Use SerialTransport behind an MspClient
This is the direct composition used by the AM32 configurator: adapt SerialTransport.exchange() and SerialTransport.read() to the MspClient adapter interface.
import { MspClient } from '@am32/serial-msp/msp';
import { inferPacketProbe, SerialTransport } from '@am32/serial-msp/serial';
declare const transport: SerialTransport;
const client = new MspClient({
write: (buffer, timeout) => {
return transport.exchange(buffer, {
timeout,
probe: inferPacketProbe(new Uint8Array(buffer))
});
},
read: <T = Uint8Array>() => transport.read<T>(),
canRead: () => true
}, {
log: console.log,
logError: console.error
});API Notes
encodeMspCommand()encodes MSP v1 for command IDs<= 254and MSP v2 otherwiseparseMspResponse()returnsundefinedwhen the buffer is incomplete or checksum validation failsinferPacketProbe()returns the MSP packet probe for$M</$X<requests and falls back to the FourWay probe otherwiseSerialTransport.exchange()resolves with accumulated bytes on timeout, can resolvenullwhen no data was accumulated, and may reject on transport, write, or cleanup errors
Browser Caveats
SerialTransportis for browser/Web Serial usage, not generic Node.js serial I/O- Web Serial requires a compatible browser and user-granted device access
SerialTransportis built onwebserial-wrapperand requires aWebSerialinstance plus aSerialPort- timeout handling relies on
globalThis.setTimeout
Build
Build the package from the repository root:
yarn tsc -p packages/serial-msp/tsconfig.jsonThe build emits JavaScript, declarations, source maps, and declaration maps in packages/serial-msp/dist.
Development
For local package builds during development, run:
yarn tsc -p packages/serial-msp/tsconfig.json