This commit is contained in:
@@ -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"?: <node number | node name | "!b" | "broadcast"> }` — 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"?: <node number | node name | "!b" | "broadcast"> }` — 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
|
||||
|
||||
|
||||
@@ -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 '<to>'`. New service method `MeshService.resolveDest(to?)` does the resolution — `sendText()` keeps its resolved-number signature.
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mesh-bot",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"private": true,
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user