{"_id":"@am32/serial-msp","name":"@am32/serial-msp","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@am32/serial-msp","version":"0.1.0","description":"Standalone MSP codec and browser serial transport utilities for AM32.","type":"module","sideEffects":false,"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./msp":{"types":"./dist/msp/index.d.ts","import":"./dist/msp/index.js"},"./serial":{"types":"./dist/serial/index.d.ts","import":"./dist/serial/index.js"}},"scripts":{"build":"tsc -p tsconfig.json"},"publishConfig":{"access":"public"},"dependencies":{"webserial-wrapper":"^1.0.4"},"devDependencies":{"@types/w3c-web-serial":"^1.0.6","typescript":"^5.5.4"},"_id":"@am32/serial-msp@0.1.0","_nodeVersion":"22.22.0","_npmVersion":"10.9.4","dist":{"integrity":"sha512-c1ZYHYDnlwIP2hOm14EsMqOhFxPtel3LweCEefxxylSfbXKrRla+YXfDM6TO3kmaqKiGuMPMAZRAdf9f63Knkg==","shasum":"eafbcf430b3022d1c706b0eda8128448d74ea7ed","tarball":"https://registry.npmjs.org/@am32/serial-msp/-/serial-msp-0.1.0.tgz","fileCount":46,"unpackedSize":45314,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDZcYuIplj44GEJFwLyOTibBNa3rUWyunHsKxHACGvP8AiEA7F24EA9Jlkjvsa6TmATT/kZa7PA45YDOLnOr/ttgwCU="}]},"_npmUser":{"name":"freasy","email":"eike@ahmels.org"},"directories":{},"maintainers":[{"name":"freasy","email":"eike@ahmels.org"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/serial-msp_0.1.0_1774941138035_0.43426445619841236"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-31T07:12:17.885Z","0.1.0":"2026-03-31T07:12:18.173Z","modified":"2026-03-31T07:12:18.446Z"},"maintainers":[{"name":"freasy","email":"eike@ahmels.org"}],"description":"Standalone MSP codec and browser serial transport utilities for AM32.","readme":"# @am32/serial-msp\n\nMSP codec, parser, client, and browser serial transport utilities extracted from the AM32 configurator.\n\n`@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.\n\n## Install\n\nInstall the package itself:\n\n```sh\nnpm install @am32/serial-msp\n```\n\nSerial transport usage is built on top of `webserial-wrapper`, which is included as a package dependency.\n\n## Entrypoints\n\n- `@am32/serial-msp` - convenience root export for both MSP and serial APIs\n- `@am32/serial-msp/msp` - MSP commands, encoders, parser, and `MspClient`\n- `@am32/serial-msp/serial` - packet boundary probes and browser/Web Serial transport\n\nUse the subpath imports when you want the clearest intent in application code:\n\n```ts\nimport { MSP_COMMANDS, MspClient, encodeMspCommand, parseMspResponse } from '@am32/serial-msp/msp';\nimport { SerialTransport, inferPacketProbe } from '@am32/serial-msp/serial';\n```\n\nThe root entrypoint is available as a convenience:\n\n```ts\nimport { MSP_COMMANDS, MspClient, SerialTransport, inferPacketProbe } from '@am32/serial-msp';\n```\n\n## What This Package Covers\n\n- MSP utilities are transport-agnostic and can be used independently of Web Serial\n- `parseMspResponse()` parses MSP v1 `$M...` packets only; it does not parse MSP v2 responses\n- `SerialTransport` is browser-focused and built around the Web Serial API\n- serial transport usage requires a `webserial-wrapper` `WebSerial` instance and a `SerialPort`\n\n## MSP Examples\n\n### Encode an MSP request\n\nUse `encodeMspCommand()` when you want the package to encode MSP v1 for command IDs `<= 254` and MSP v2 otherwise.\n\n```ts\nimport { MSP_COMMANDS, encodeMspCommand } from '@am32/serial-msp/msp';\n\nconst request = encodeMspCommand(MSP_COMMANDS.MSP_API_VERSION);\nconst bytes = new Uint8Array(request);\n\n// bytes: \"$M<\" + payload length + command + checksum\n```\n\nTo force a specific protocol version, use `encodeMspV1()` or `encodeMspV2()` directly:\n\n```ts\nimport { MSP_COMMANDS, encodeMspV1, encodeMspV2 } from '@am32/serial-msp/msp';\n\nconst v1Request = encodeMspV1(MSP_COMMANDS.MSP_MOTOR_CONFIG, new Uint8Array());\n\nconst dshotPayload = new Uint8Array([0x01, 0x00, 0x00, 0x00]);\nconst v2Request = encodeMspV2(MSP_COMMANDS.MSP2_SEND_DSHOT_COMMAND, dshotPayload);\n```\n\n### Parse an MSP response buffer\n\n`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.\n\n```ts\nimport { MSP_COMMANDS, parseMspResponse } from '@am32/serial-msp/msp';\n\nconst response = new Uint8Array([\n    36, 77, 62,\n    3,\n    MSP_COMMANDS.MSP_API_VERSION,\n    1, 44, 0,\n    47\n]);\n\nconst parsed = parseMspResponse(response);\n\nif (parsed?.commandName === MSP_COMMANDS.MSP_API_VERSION) {\n    const major = parsed.data.getUint8(0);\n    const minor = parsed.data.getUint8(1);\n    const patch = parsed.data.getUint8(2);\n\n    console.log({ major, minor, patch });\n}\n```\n\nThe current implementation accepts `$M<`, `$M>`, and `$M!` marker bytes after the `$M` header.\n\n### Create an `MspClient` with an adapter\n\n`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()`.\n\n```ts\nimport { MSP_COMMANDS, MspClient } from '@am32/serial-msp/msp';\n\nconst adapter = {\n    async write(buffer: ArrayBuffer, timeout = 250): Promise<Uint8Array | null> {\n        void timeout;\n\n        // Replace with your own transport implementation.\n        return new Uint8Array([\n            36, 77, 62,\n            3,\n            MSP_COMMANDS.MSP_API_VERSION,\n            1, 44, 0,\n            47\n        ]);\n    },\n    async read<T = Uint8Array>(): Promise<ReadableStreamReadResult<T>> {\n        return {\n            done: false,\n            value: undefined\n        };\n    },\n    canRead() {\n        return false;\n    }\n};\n\nconst client = new MspClient(adapter, {\n    log: console.log,\n    logError: console.error\n});\n\nconst parsed = await client.sendWithPromise(MSP_COMMANDS.MSP_API_VERSION);\nconst major = parsed.data.getUint8(0);\n```\n\n## Web Serial Transport Examples\n\n`@am32/serial-msp/serial` is intended for browser environments that expose Web Serial. It uses `webserial-wrapper` stream helpers internally.\n\n### Construct `SerialTransport`\n\n`SerialTransport` needs a `WebSerial` instance, the selected `SerialPort`, and optional stream getters/setters if you want to reuse the same stream across exchanges.\n\n```ts\nimport type { WebSerial } from 'webserial-wrapper';\nimport type { StreamInfo } from 'webserial-wrapper';\nimport { SerialTransport } from '@am32/serial-msp/serial';\n\ndeclare const serial: WebSerial;\ndeclare const port: SerialPort;\n\nlet stream: StreamInfo | null = null;\n\nconst transport = new SerialTransport({\n    serial,\n    port,\n    getStream: () => stream,\n    setStream: (nextStream) => {\n        stream = nextStream;\n    },\n    logError: console.error\n});\n```\n\n### Call `exchange()` with `inferPacketProbe`\n\nThis 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.\n\n```ts\nimport { MSP_COMMANDS, encodeMspCommand, parseMspResponse } from '@am32/serial-msp/msp';\nimport { SerialTransport, inferPacketProbe } from '@am32/serial-msp/serial';\n\ndeclare const transport: SerialTransport;\n\nconst request = encodeMspCommand(MSP_COMMANDS.MSP_API_VERSION);\nconst requestBytes = new Uint8Array(request);\n\nconst response = await transport.exchange(request, {\n    timeout: 250,\n    probe: inferPacketProbe(requestBytes)\n});\n\nif (response) {\n    const parsed = parseMspResponse(response);\n    console.log(parsed?.commandName);\n}\n```\n\n### Use `SerialTransport` behind an `MspClient`\n\nThis is the direct composition used by the AM32 configurator: adapt `SerialTransport.exchange()` and `SerialTransport.read()` to the `MspClient` adapter interface.\n\n```ts\nimport { MspClient } from '@am32/serial-msp/msp';\nimport { inferPacketProbe, SerialTransport } from '@am32/serial-msp/serial';\n\ndeclare const transport: SerialTransport;\n\nconst client = new MspClient({\n    write: (buffer, timeout) => {\n        return transport.exchange(buffer, {\n            timeout,\n            probe: inferPacketProbe(new Uint8Array(buffer))\n        });\n    },\n    read: <T = Uint8Array>() => transport.read<T>(),\n    canRead: () => true\n}, {\n    log: console.log,\n    logError: console.error\n});\n```\n\n## API Notes\n\n- `encodeMspCommand()` encodes MSP v1 for command IDs `<= 254` and MSP v2 otherwise\n- `parseMspResponse()` returns `undefined` when the buffer is incomplete or checksum validation fails\n- `inferPacketProbe()` returns the MSP packet probe for `$M<` / `$X<` requests and falls back to the FourWay probe otherwise\n- `SerialTransport.exchange()` resolves with accumulated bytes on timeout, can resolve `null` when no data was accumulated, and may reject on transport, write, or cleanup errors\n\n## Browser Caveats\n\n- `SerialTransport` is for browser/Web Serial usage, not generic Node.js serial I/O\n- Web Serial requires a compatible browser and user-granted device access\n- `SerialTransport` is built on `webserial-wrapper` and requires a `WebSerial` instance plus a `SerialPort`\n- timeout handling relies on `globalThis.setTimeout`\n\n## Build\n\nBuild the package from the repository root:\n\n```sh\nyarn tsc -p packages/serial-msp/tsconfig.json\n```\n\nThe build emits JavaScript, declarations, source maps, and declaration maps in `packages/serial-msp/dist`.\n\n## Development\n\nFor local package builds during development, run:\n\n```sh\nyarn tsc -p packages/serial-msp/tsconfig.json\n```\n","readmeFilename":"README.md","_rev":"1-1b2d2b0af19132b5d760942dadcec85d"}