Files
MeshBot/CHANGELOG.md
T
2026-09-24 23:14:32 +02:00

12 KiB
Raw Permalink Blame History

Changelog

v0.7.1 — 2026-09-24 21:10

  • Fix GET /api/device and MCP device_info crashing with TypeError: Do not know how to serialize a BigInt on live hardware (fw 2.7.15): protobuf-es decodes proto uint64 fields as native JS bigint, which Express' res.json/JSON.stringify reject, and MeshService.deviceInfo() passed raw decoded proto objects through unconverted. Known affected fields: Config.PowerConfig.powermonEnables (in localConfig.power) and host-metrics byte counters (freememBytes/diskfree*).
  • Fix at the single choke point: one small recursive converter (jsonSafe, bigint → decimal string, plain objects/arrays/primitives pass through) applied in deviceInfo() to everything that comes from raw decoded proto messages — DeviceMetadata plus every localConfig and moduleConfig section. No per-field patching; any uint64 field in those messages is covered generically. Values that were bigint now serialize as strings (e.g. "powermonEnables": "123"); no other endpoint/shape changes, redaction logic untouched.
  • Tests: new device-info spec feeds a power config section frame with powermonEnables set to a real bigint through the fake bridge and asserts the value comes back as its string form ("123") and that JSON.stringify(deviceInfo()) succeeds — the old fixtures never set a uint64 field, which is why this slipped past the suite.

v0.7.0 — 2026-09-24 20:21

  • New GET /api/device endpoint (and matching MCP tool device_info, now seven tools): device info captured during the initial sync — this device's node number, firmware version and metadata (meshtastic.DeviceMetadata as-is; it carries no key material), plus per-section config and module-config messages keyed by section name. In-memory only, reset on every bridge reconnect like channels/nodes.
  • MeshService.deviceInfo() captures the new FromRadio cases during sync: metadata (DeviceMetadata), config (one meshtastic.Config per section — oneof payloadVariant cases device/position/power/network/display/lora/bluetooth/security/sessionkey/deviceUi) and moduleConfig (one ModuleConfig per module). Verified against the installed @meshtastic/protobufs 2.8.0: FromRadio carries meshtastic.Config, not LocalConfig (that type lives in a separate localonly namespace and is not part of FromRadio).
  • Mandatory redaction (trust boundary): key material never leaves the process — security section publicKey/privateKey/adminKey and network section wifiPsk are replaced with "REDACTED" in all output.
  • Node summaries (GET /api/nodes, GET /api/nodes/:id, MCP list_nodes/get_node) gain optional fields from the NodeInfo: snr (dB), hopsAway, viaMqtt — present only when the device reported them. Note: meshtastic.NodeInfo in 2.8.0 has no firmware version field, so per-node firmwareVersion is not captured (device-level firmware version comes from DeviceMetadata via /api/device).
  • Swagger: GET /api/device documented with the redaction note and an example; nodes endpoint example/description extended for the new optional fields. README + AGENTS.md endpoint lists updated.
  • Tests: fake-bridge gains metadataFrame, configSectionFrame, moduleConfigSectionFrame; new device-info specs (metadata → firmwareVersion, config/module-config sections captured with security key material absent from the JSON output), NodeInfo snr/hopsAway/viaMqtt in summaries; MCP spec updated to seven tools incl. a device_info call check.

v0.6.0 — 2026-09-24 18:28

  • New MCP (Model Context Protocol) server endpoint at POST /mcp (outside the /api prefix, like Scalar at /), served in-process via the official @modelcontextprotocol/sdk Streamable HTTP transport in stateless mode (sessionIdGenerator: undefined; a fresh McpServer + transport per request). Six tools map 1:1 to existing MeshService methods with no new logic: mesh_status, send_message(text, to?) (same target resolution as POST /api/send — omitted/""/"!b"/"broadcast" → broadcast, numbers pass through, names via the node DB; resolution errors surface as tool errors with the same messages), list_channels, list_nodes, get_node(id) (tool error when missing/ambiguous), and get_messages. Tool results are JSON in a text content block.
  • New dependency: @modelcontextprotocol/sdk (^1.30.1; zod comes with it). Note: the SDK's root entry is broken in all published 1.x tarballs (upstream issue #2273 — the exports map points to a dist index that was never shipped), so imports use the documented subpaths (@modelcontextprotocol/sdk/server/mcp.js, .../server/streamableHttp.js, .../inMemory.js, .../client/index.js).
  • Test helpers shared by both specs extracted from mesh.service.spec.ts into src/mesh/fake-bridge.ts (fake bridge, waitFor, nodeInfoFrame, KAMIL fixtures); new mcp.controller.spec.ts drives the MCP server over an in-memory transport pair — all six tools listed with descriptions/schemas, and mesh_status / send_message (broadcast + name resolution + error messages) / get_node / list_channels / list_nodes / get_messages return correct shapes.
  • README gains an MCP section; AGENTS.md endpoint list updated for /mcp.

v0.5.0 — 2026-09-24 17:37

  • Publishing channel moved from testowy (index 2) to the new test channel (index 3); MESH_SEND_CHANNEL default is now 3. All docs/comments referencing the old pinning updated accordingly.

v0.4.0 — 2026-09-24 14:39

  • POST /api/send now accepts node names and explicit broadcast sentinels in to: omitted/"" or "!b"/"broadcast" (case-insensitive, trimmed) → broadcast address 0xffffffff; numbers pass through as-is (must be an integer in 1..0xffffffff); numeric strings are parsed the same way; anything else is looked up by name via the existing node DB lookup (exact long/short name, then unique substring). Unknown or ambiguous targets are a 400 with no node matching '<to>'. New service method MeshService.resolveDest(to?) does the resolution — sendText() keeps its resolved-number signature.
  • Swagger: SendTextDto.to documented as number | string with the full resolution order and the DM-vs-broadcast caveat (DMs are unicast wantAck-tracked via GET /api/messages; broadcasts stay pending because firmware strips want_ack); @ApiOperation notes all sends still ride channel index 2 (testowy) regardless of DM or broadcast.
  • README + AGENTS.md endpoint lists updated for the new to semantics.
  • Unit tests: resolveDest coverage on the fake bridge — omitted/""/"!b"/"BROADCAST" → broadcast, number passthrough, numeric string, exact longName match → num (node DB seeded via framed FromRadio nodeInfo), unknown name → no node matching, and invalid numbers (0, negative, non-integer, > 0xffffffff) rejected.

v0.3.0 — 2026-09-24 14:08

  • Message history with delivery tracking, exposed as GET /api/messages (newest first): inbound TEXT_MESSAGE_APP packets (dir:'rx', with from) and our sends (dir:'tx', with to + status). tx records start pending and move to delivered/failed when the routing ACK/NACK arrives — ROUTING_APP packets carry a serialized meshtastic.Routing in Data.payload whose oneof variant is errorReason (Routing_Error: 0 NONE = delivered, anything else failed with a human-readable ackError, e.g. NO_ROUTE/GOT_NAK/TIMEOUT, fallback reason <n>), matched to our sends via Data.requestId. Broadcasts stay pending because firmware strips want_ack pre-TX (documented behavior, no timer). History is a bounded in-memory buffer of the last 100 records (newest kept) and resets on every bridge reconnect alongside channels/nodes.
  • Codec: new PORT_ROUTING_APP = 5 constant (verified against the installed @meshtastic/protobufs PortNum enum), decodeRouting() for ROUTING_APP payloads, and describeRoutingError() which maps a Routing.Error code to its name from the installed package's Mesh.Routing_Error enum.
  • Swagger: GET /api/messages documented with the pending→delivered/failed lifecycle, the broadcast caveat, and an example matching the record shape; README + AGENTS.md endpoint lists updated.

v0.2.0 — 2026-09-24 13:08

  • Node database collected during the initial sync (FromRadio.nodeInfo frames, upserted by node number) and kept fresh by live POSITION_APP packets (the Position is serialized in Data.payload; unknown senders get a stub entry). The map resets on every bridge reconnect alongside channels.
  • New endpoints: GET /api/nodes — lean summaries sorted by node number as { num, longName?, shortName?, hwModel?, batteryLevel?, lastHeard (epoch secs | null), position? { lat, lon, alt? } }[]; names/hwModel/battery appear only when the device reported them, hwModel is the enum's string name (UNSET omitted), and position only when both coordinates are known. GET /api/nodes/:id — lookup by exact node number (numbers/numeric strings), then case-insensitive exact long/short name, else unique case-insensitive substring; 404 with a clear message when nothing or more than one node matches.
  • Codec: new PORT_POSITION_APP = 3 constant (verified against the installed @meshtastic/protobufs PortNum enum), decodePosition() for POSITION_APP payloads, and loadHwModelNames() reverse lookup of the HardwareModel enum.
  • Swagger docs made genuinely useful: Mesh tag on the controller (description via DocumentBuilder.addTag — v12's ApiTagOptions has no description field) plus @ApiOperation summary/description and @ApiResponse examples on all endpoints, existing and new.
  • Unit tests build real framed FromRadio bytes for nodeInfo/POSITION_APP: sync collection + summary shape (lat/lon = value × 1e-7, battery), stub entry from an unknown POSITION_APP sender, lookup ladder (exact num / numeric string / exact name / unique substring) with ambiguous and missing → null, and reconnect reset.

v0.1.4 — 2026-09-23 20:33

  • Send a ToRadio with want_config_id (random non-zero uint32 nonce) on every bridge connect, so the firmware streams its initial sync data (myInfo, channels, config_complete_id) — previously nothing was written on connect, which is why /api/channels stayed empty and myNodeNum stayed null in production. New codec helper buildWantConfigToRadio; unit test captures the outbound frame on the fake bridge, asserts the wantConfigId oneof case with a non-zero value, then feeds back myInfo + configCompleteId frames and checks service state.

v0.1.3 — 2026-09-23 19:58

  • Sends are now pinned to the test channel testowy (channel index 2) instead of the hardcoded primary channel (channel: 0). The target channel is configurable via MESH_SEND_CHANNEL (default 2) and parsed once at service construction; buildTextToRadio takes the channel index as a parameter. Unit tests now decode the outbound ToRadio frame on the fake bridge and assert packet.channel === 2 on the default send path.

v0.1.2 — 2026-09-23 19:51

  • Added a Gitea Actions CI pipeline (.gitea/workflows/ci.yml), mirroring flight's runner conventions: on push/PR to main (plus manual dispatch) one job runs npm ci, nest build, the vitest unit suite, and a production Docker image build — with retry loops for the runner's flaky uplink. No app code changed.

v0.1.1 — 2026-09-23 19:38

  • Fixed the default bridge port to 4403, the meshtastic-serial-bridge's actual TCP port (the old value pointed at a closed port). Updated the MESH_PORT default in code, the docker-compose environment, and docs.

v0.1.0 — 2026-09-23 18:54

  • New GET /api/channels endpoint listing the device's Meshtastic channels as { index, name, uplinkEnabled, downlinkEnabled }; psk/keys are redacted and never exposed over HTTP. Channels are collected from FromRadio.channel frames streamed during initial sync (before config_complete_id) and reset on every bridge reconnect so stale data is dropped after a disconnect.
  • All endpoints moved under the /api namespace via app.setGlobalPrefix("api"): GET /status → GET /api/status, POST /send → POST /api/send.
  • Scalar API docs served at / (OpenAPI JSON at /docs-json) using @nestjs/swagger + @scalar/nestjs-api-reference; added light swagger decorators (ApiProperty on the send body, ApiOperation summaries) so the docs are useful.
  • Reconnect interval is now configurable via MESH_RECONNECT_MS (default 5000 ms).