docs(sdk,sdk-react): expand README for npm publish

Both packages publish to npm with `files: [..., "README.md", ...]` so the
README is the landing page. Old versions were one-line stubs.

- @meshtastic/sdk: explain feature-client surface, subpath exports
  (/transport, /protobuf, /testing), the fake transport for tests, and
  the migration story from @meshtastic/core (one SDK, separate transport
  packages, framework-agnostic core).
- @meshtastic/sdk-react: single-client + registry quickstarts, grouped
  hook reference, and why the React bindings split out of core.

Shim note carried forward in the sdk README: legacyMeshDevice export
stays until the web client finishes migrating; new code should target
MeshClient directly.
This commit is contained in:
Dan Ditomaso
2026-05-26 22:25:41 -04:00
parent f4f9f55f34
commit 1d83da36f0
2 changed files with 181 additions and 23 deletions

View File

@@ -1,20 +1,20 @@
# @meshtastic/sdk-react
React hooks and provider for `@meshtastic/sdk`.
## Install
React hooks for [`@meshtastic/sdk`](https://www.npmjs.com/package/@meshtastic/sdk). Wraps the SDK's
signal-based state in hooks that only re-render when the data they touch changes.
```sh
pnpm add @meshtastic/sdk @meshtastic/sdk-react @meshtastic/transport-web-serial
```
## Quickstart
## One device
If your app talks to a single radio at a time, wrap your tree in `MeshProvider` and you're done:
```tsx
import { MeshClient } from "@meshtastic/sdk";
import { MeshClient, ChannelNumber } from "@meshtastic/sdk";
import { MeshProvider, useDevice, useChat } from "@meshtastic/sdk-react";
import { TransportWebSerial } from "@meshtastic/transport-web-serial";
import { ChannelNumber } from "@meshtastic/sdk";
const transport = await TransportWebSerial.create({ baudRate: 115200 });
const client = new MeshClient({ transport });
@@ -31,11 +31,101 @@ function App() {
function Status() {
const { status, myNodeNum } = useDevice();
const { messages, send } = useChat(ChannelNumber.Primary);
return <div>{status} / {myNodeNum} / {messages.length} msgs</div>;
return (
<div>
{status} · node #{myNodeNum} · {messages.length} msgs
<button onClick={() => send("hello mesh")}>send</button>
</div>
);
}
```
All hooks are read-only against a single `MeshClient` instance supplied through context. Commands are returned as stable functions.
## Several devices
For apps that hold multiple connections at once — say, a desktop dashboard with a USB device plus a
Bluetooth one — use the registry provider instead. Hooks default to the "active" client, and you can
target a specific one by id when you need to.
```tsx
import { MeshRegistry } from "@meshtastic/sdk";
import { MeshRegistryProvider, useNodes } from "@meshtastic/sdk-react";
const registry = new MeshRegistry();
registry.register("usb-1", usbClient);
registry.register("ble-1", bleClient);
registry.setActive("usb-1");
function App() {
return (
<MeshRegistryProvider registry={registry}>
<Nodes />
</MeshRegistryProvider>
);
}
function Nodes() {
const nodes = useNodes(); // active client
return <div>{nodes.length} nodes</div>;
}
```
## Why this is a separate package
The SDK used to live in `@meshtastic/core`, and its React conventions (effects, refs, a few hook
helpers) were baked right into protocol code. That made it impossible to ship the SDK to non-React
consumers — Node CLIs, Deno scripts, native shells — without dragging React along.
When we split that up, the React bits moved here. Now the layering is straightforward:
- `@meshtastic/sdk` — protocol, state, transports. No React, no DOM.
- `@meshtastic/sdk-react` — this. Hooks and providers, nothing else.
- `@meshtastic/transport-*` — per-runtime byte transports.
If you're not using React, you don't need this package at all. Read `@meshtastic/sdk` directly.
## Hooks
Most components only need two or three of these. The full list is here for reference:
**Plumbing**
- `useClient()` — the client from `MeshProvider`
- `useActiveClient()` — the active client from a `MeshRegistryProvider`
- `useClientById(id)` — a specific client from the registry
- `useMeshRegistry()` / `useOptionalMeshRegistry()` — the registry itself
- `useSignal(signal)` / `useSignalValue(signal)` — bridge any SDK signal into React state
**Connection**
- `useConnection()` — status + lifecycle helpers
- `useConnectionProgress()` — live "configuring" progress with per-section counters (handy for
building a connecting overlay)
- `useMeshDevice()` — convenience wrapper around the legacy `MeshDevice` shim
**Device & node state**
- `useDevice()`
- `useNodes()` / `useNode(num)`
- `useNodeError(num)` / `useNodeErrors()` / `useHasNodeError(num)`
**Messaging**
- `useChat(channel)` — channel messages
- `useDirectChat(peer)` — DMs
- `useDraft(...)` — draft messages
**Config**
- `useChannels()` / `useChannel(idx)`
- `useConfig()` / `useModuleConfig()` / `useIsRegionUnset()`
- `useConfigEditor()` — staged edits with dirty tracking
Each hook reads from a single signal, so updates only re-render components that actually use that
data.
## Source / issues
<https://github.com/meshtastic/web>
## License

View File

@@ -1,16 +1,14 @@
# @meshtastic/sdk
Domain-driven SDK for Meshtastic devices. Feature slices with signals-backed reactive state.
Replaces `@meshtastic/core`. During migration a shim layer re-exports the legacy `MeshDevice` class so existing consumers keep building — see `src/shim/`.
## Install
TypeScript SDK for talking to [Meshtastic](https://meshtastic.org) radios. Works in the browser, in
Node, and in Deno. Pick a transport, hand it a `MeshClient`, and you've got reactive access to the
device's chat, nodes, channels, config, and telemetry.
```sh
pnpm add @meshtastic/sdk @meshtastic/transport-web-serial
```
## Quickstart
## Quick example
```ts
import { MeshClient } from "@meshtastic/sdk";
@@ -20,20 +18,90 @@ const transport = await TransportWebSerial.create({ baudRate: 115200 });
const client = new MeshClient({ transport });
await client.connect();
client.chat.send({ text: "hello mesh" });
console.log(client.nodes.list.value);
client.device.status.subscribe((s) => console.log("status:", s));
console.log("my node:", client.device.myNodeNum.value);
console.log("nodes:", client.nodes.list.value);
await client.chat.send({ text: "hello mesh" });
client.events.onConfigComplete.subscribe(() => console.log("configured"));
client.events.onRebooted.subscribe(() => console.log("device rebooted"));
```
See the repo root [README](../../README.md) for architecture and feature slice layout.
State is exposed as signals. Subscribe to them, or read `.value` synchronously. Commands are async and
resolve when the device acks.
## Layout
## Why this is a new package
If you've used Meshtastic on the web before, you've probably seen `@meshtastic/core`. That package
mixed protocol, transports, and consumer state in one class. It worked, but it bled assumptions:
browser apps still pulled the Node serial driver, slice state had a few React-isms baked in, and there
wasn't a clean seam for unit testing or non-browser runtimes.
We split it into three pieces:
- `@meshtastic/sdk` (this one) owns the protocol, the queue, the codec, and the reactive state. No
React. No DOM. No Node builtins. It runs the same everywhere.
- `@meshtastic/transport-*` packages are thin byte adapters. Install only the ones you need. The SDK
talks to them through a tiny `Transport` interface (`fromDevice` / `toDevice` streams plus
`disconnect()`), so you can write your own without forking anything.
- `@meshtastic/sdk-react` lives separately for the React hooks. Skip it if you're not using React.
The old `MeshDevice` class is still exported from `src/shim/` so existing `@meshtastic/core` apps can
upgrade without a full rewrite. The shim will be removed once the web client finishes its migration —
new code should use `MeshClient` directly.
## What's in the client
`MeshClient` is just a small composition root. The interesting bits are the feature clients hanging
off it:
- `client.device` — identity, status, metadata, reboot / shutdown / factory-reset
- `client.chat` — channel + direct messages, drafts, unread tracking
- `client.nodes` — live node list, per-node lookups, error states
- `client.channels` — channel config
- `client.config` — device + module config, plus a staged-edit `ConfigEditor`
- `client.telemetry` — environment / device / power metrics
- `client.position` — position broadcast + node positions
- `client.traceroute` — mesh route discovery
- `client.files` — XModem get/put
Each one can be used on its own; `MeshClient` just hands them a shared transport, queue, and event
bus.
## Subpath exports
| Import path | What's there |
| --- | --- |
| `@meshtastic/sdk` | `MeshClient`, feature clients, signal types |
| `@meshtastic/sdk/transport` | The `Transport` interface and related types — use this if you're writing a custom adapter |
| `@meshtastic/sdk/protobuf` | Re-export of `@meshtastic/protobufs` for raw packet construction |
| `@meshtastic/sdk/testing` | `createFakeTransport()` for unit tests |
## Testing without a radio
```ts
import { MeshClient } from "@meshtastic/sdk";
import { createFakeTransport } from "@meshtastic/sdk/testing";
const { transport, respond } = createFakeTransport();
const client = new MeshClient({ transport });
respond.withMyNodeInfo({ myNodeNum: 42 });
respond.withConfigCompleteId(1);
// client.device, client.nodes, etc. are now populated.
```
src/
core/ # shared kernel: client, transport, event-bus, queue, xmodem, signals, logging, packet-codec
features/ # DDD feature slices (device, chat, nodes, channels, config, telemetry, position, traceroute, files)
shim/ # legacy MeshDevice compatibility exports (removed in Phase C)
```
The fake transport is the same one the SDK's own integration tests use.
## React?
[`@meshtastic/sdk-react`](https://www.npmjs.com/package/@meshtastic/sdk-react).
## Source / issues
<https://github.com/meshtastic/web>
## License