diff --git a/AGENTS.md b/AGENTS.md index 7bd570b..808e4a6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,7 +17,7 @@ ## API 1. **Live base URL** — `http://bot.dev.kkhost.pl` (Traefik → docker-compose stack on this host; container port 3000 via external network dev_net, no published ports). -2. **Endpoints** — all under the `/api` global prefix: `GET /api/status` (`{ connected, myNodeNum, host, port }`), `POST /api/send` (body `{ "text": "...", "to"?: }` — numbers pass through as-is (integer 1..0xffffffff), names resolve like `GET /api/nodes/:id`, omitted/""/"!b"/"broadcast" → broadcast; unknown or ambiguous targets are a 400. Returns `{ id }`), `GET /api/channels` (the device's Meshtastic channels as `{ index, name, uplinkEnabled, downlinkEnabled }[]`; psk/keys redacted), `GET /api/messages` (message history newest first: rx text with `from`, tx sends with `to` + `status` pending→delivered/failed via routing ACKs — broadcasts stay pending because firmware strips want_ack; last 100, in-memory, reset on reconnect), `GET /api/nodes` (known nodes sorted by num as `{ num, longName?, shortName?, hwModel?, batteryLevel?, lastHeard: number|null, position?: { lat, lon, alt? } }[]`, from the initial sync plus live POSITION_APP updates; reset on reconnect) and `GET /api/nodes/:id` (lookup by exact node number/numeric string, then case-insensitive exact long/short name, else unique substring; 404 when ambiguous or missing). Scalar API docs are served at `/` (OpenAPI JSON at `/docs-json`). Keep this list in sync with the controller. All `POST /api/send` traffic targets channel index 2 (`testowy`) on purpose — never the primary channel; override via `MESH_SEND_CHANNEL` (default `2`). +2. **Endpoints** — all under the `/api` global prefix: `GET /api/status` (`{ connected, myNodeNum, host, port }`), `POST /api/send` (body `{ "text": "...", "to"?: }` — numbers pass through as-is (integer 1..0xffffffff), names resolve like `GET /api/nodes/:id`, omitted/""/"!b"/"broadcast" → broadcast; unknown or ambiguous targets are a 400. Returns `{ id }`), `GET /api/channels` (the device's Meshtastic channels as `{ index, name, uplinkEnabled, downlinkEnabled }[]`; psk/keys redacted), `GET /api/messages` (message history newest first: rx text with `from`, tx sends with `to` + `status` pending→delivered/failed via routing ACKs — broadcasts stay pending because firmware strips want_ack; last 100, in-memory, reset on reconnect), `GET /api/nodes` (known nodes sorted by num as `{ num, longName?, shortName?, hwModel?, batteryLevel?, lastHeard: number|null, position?: { lat, lon, alt? } }[]`, from the initial sync plus live POSITION_APP updates; reset on reconnect) and `GET /api/nodes/:id` (lookup by exact node number/numeric string, then case-insensitive exact long/short name, else unique substring; 404 when ambiguous or missing). Scalar API docs are served at `/` (OpenAPI JSON at `/docs-json`). Keep this list in sync with the controller. All `POST /api/send` traffic targets channel index 3 (`test`) on purpose — never the primary channel; override via `MESH_SEND_CHANNEL` (default `3`). ## Other diff --git a/CHANGELOG.md b/CHANGELOG.md index 10d1986..26da020 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 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 ''`. New service method `MeshService.resolveDest(to?)` does the resolution — `sendText()` keeps its resolved-number signature. diff --git a/README.md b/README.md index c6a30d6..4da8da3 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ broadcast address `0xffffffff`; anything else is a node number or name. DMs are unicast and wantAck-tracked via `GET /api/messages`; broadcasts stay pending because firmware strips want_ack before TX. -All sends always target channel index 2 (`testowy`) — never the primary +All sends always target channel index 3 (`test`) — never the primary channel. Override with `MESH_SEND_CHANNEL`. ## Env vars @@ -58,7 +58,7 @@ channel. Override with `MESH_SEND_CHANNEL`. - `MESH_HOST` (default `192.168.1.4`) — bridge host - `MESH_PORT` (default `4403`) — bridge port - `MESH_RECONNECT_MS` (default `5000`) — reconnect interval in ms -- `MESH_SEND_CHANNEL` (default `2`) — channel index all sends target (`testowy`; never the primary channel) +- `MESH_SEND_CHANNEL` (default `3`) — channel index all sends target (`test`; never the primary channel) - `PORT` (default `3000`) — HTTP listen port Note: the serial bridge accepts only ONE client at a time — if another app is diff --git a/package.json b/package.json index aa35731..d5bf830 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mesh-bot", - "version": "0.4.0", + "version": "0.5.0", "description": "", "author": "", "private": true, diff --git a/src/mesh/mesh.controller.ts b/src/mesh/mesh.controller.ts index f068a51..884df12 100644 --- a/src/mesh/mesh.controller.ts +++ b/src/mesh/mesh.controller.ts @@ -57,7 +57,7 @@ export class MeshController { @ApiOperation({ summary: 'Send a text message over the mesh', description: - 'Queues a TEXT_MESSAGE_APP packet to the resolved target (node number, node name, or broadcast). DMs are unicast and wantAck-tracked via GET /api/messages; broadcasts stay pending because firmware strips want_ack. All sends ride the pinned test channel (index 2, "testowy" — never the primary channel) regardless of DM or broadcast, since modern firmware PKI handles unicast encryption/channel anyway. Returns the Meshtastic packet id.', + 'Queues a TEXT_MESSAGE_APP packet to the resolved target (node number, node name, or broadcast). DMs are unicast and wantAck-tracked via GET /api/messages; broadcasts stay pending because firmware strips want_ack. All sends ride the pinned test channel (index 3, "test" — never the primary channel) regardless of DM or broadcast, since modern firmware PKI handles unicast encryption/channel anyway. Returns the Meshtastic packet id.', }) @ApiResponse({ status: 200, @@ -93,8 +93,8 @@ export class MeshController { example: [ { index: 0, name: 'Default', uplinkEnabled: true, downlinkEnabled: true }, { - index: 2, - name: 'testowy', + index: 3, + name: 'test', uplinkEnabled: false, downlinkEnabled: false, }, @@ -118,8 +118,8 @@ export class MeshController { id: 456789, dir: 'tx', to: 42, - text: 'hello testowy', - channel: 2, + text: 'hello test', + channel: 3, at: 1758000123456, status: 'delivered', }, diff --git a/src/mesh/mesh.service.spec.ts b/src/mesh/mesh.service.spec.ts index 659d957..be99a8c 100644 --- a/src/mesh/mesh.service.spec.ts +++ b/src/mesh/mesh.service.spec.ts @@ -295,11 +295,11 @@ describe('MeshService connect', () => { }); describe('MeshService send', () => { - it('sends on the test channel (index 2) by default, never primary', async () => { + it('sends on the test channel (index 3) by default, never primary', async () => { const bridge = await startBridge(); process.env.MESH_HOST = '127.0.0.1'; process.env.MESH_PORT = String(bridge.port); - delete process.env.MESH_SEND_CHANNEL; // default path: "testowy" (index 2) + delete process.env.MESH_SEND_CHANNEL; // default path: "test" (index 3) const svc = new MeshService(); try { svc.onModuleInit(); @@ -309,7 +309,7 @@ describe('MeshService send', () => { const out: Uint8Array[] = []; bridge.sockets[0].on('data', (c) => out.push(c)); - const id = await svc.sendText(42, 'hello testowy'); + const id = await svc.sendText(42, 'hello test'); expect(id).toBeGreaterThan(0); const readOutbound = (): P.Mesh.MeshPacket | undefined => { @@ -331,7 +331,7 @@ describe('MeshService send', () => { await waitFor(() => readOutbound() !== undefined); const pkt = readOutbound()!; - expect(pkt.channel).toBe(2); // "testowy" — never the primary channel (0) + expect(pkt.channel).toBe(3); // "test" — never the primary channel (0) expect(pkt.to).toBe(42); const decoded = pkt.payloadVariant?.case === 'decoded' @@ -339,7 +339,7 @@ describe('MeshService send', () => { : undefined; expect( new TextDecoder().decode(decoded?.payload ?? new Uint8Array()), - ).toBe('hello testowy'); + ).toBe('hello test'); } finally { delete process.env.MESH_SEND_CHANNEL; svc.onModuleDestroy(); @@ -537,13 +537,13 @@ describe('MeshService message history', () => { () => bridge.sockets.length === 1 && svc.status().connected, ); - const id = await svc.sendText(42, 'hello testowy'); + const id = await svc.sendText(42, 'hello test'); expect(svc.messages()[0]).toMatchObject({ id, dir: 'tx', to: 42, - text: 'hello testowy', - channel: 2, + text: 'hello test', + channel: 3, status: 'pending', }); diff --git a/src/mesh/mesh.service.ts b/src/mesh/mesh.service.ts index 72e57b4..53369c4 100644 --- a/src/mesh/mesh.service.ts +++ b/src/mesh/mesh.service.ts @@ -71,9 +71,9 @@ export class MeshService implements OnModuleInit, OnModuleDestroy { process.env.MESH_RECONNECT_MS ?? '5000', 10, ); - // sends are pinned to the test channel ("testowy", index 2) on purpose — never the primary channel + // sends are pinned to the test channel ("test", index 3) on purpose — never the primary channel private readonly sendChannel = parseInt( - process.env.MESH_SEND_CHANNEL ?? '2', + process.env.MESH_SEND_CHANNEL ?? '3', 10, );