Files
tcp-ingestion/.planning/phase-2-commands/README.md
T
julian c8a5f4cd68 Add Phase 1 and Phase 2 planning documents
ROADMAP plus granular task files per phase. Phase 1 (12 tasks + 1.13
device authority) covers Codec 8/8E/16 telemetry ingestion; Phase 2
(6 tasks) covers Codec 12/14 outbound commands; Phase 3 enumerates
deferred items.
2026-04-30 15:50:49 +02:00

66 lines
3.6 KiB
Markdown

# Phase 2 — Outbound commands
Add server-to-device command delivery using Teltonika codecs 12 (`0x0C`) and 14 (`0x0E`). Codec 13 is one-way device→server (not in scope for outbound); codec 15 is FMX6-only (out of scope entirely).
## Prerequisite
Phase 1 must be complete and stable in production. Phase 2 adds code *alongside* Phase 1, never in the inbound parsing path.
## Outcome statement
When Phase 2 is done:
- Each Ingestion instance maintains its IMEI→instance mapping in `connections:registry` (Redis hash) and a heartbeat key.
- A Directus Flow on `commands` table inserts can publish a command to `commands:outbound:{instance_id}` after looking up the routing.
- Each Ingestion instance runs a command consumer in parallel with the TCP listener; consumed commands are dispatched to the right per-socket write queue, encoded as Codec 12 or 14, and written to the device.
- Device responses (Codec 12 Type `0x06` or Codec 14 Type `0x06`/`0x11`) are correlated to the in-flight command and published to `commands:responses` for Directus to update the row.
- The TCP read path is never blocked by outbound work.
- Phase 1 code is unchanged.
## Architectural anchors
`docs/wiki/concepts/phase-2-commands.md` is the design source of truth. Read it before starting any Phase 2 task.
Key invariants:
1. **Ingestion exposes no user-facing HTTP** — never. All command authorization happens in Directus.
2. **Commands are data before transport.** Every command has a row in Directus's `commands` table before it ever reaches Redis.
3. **One outstanding command per device socket.** Teltonika command codecs have no correlation ID; the protocol assumes serialization. Subsequent commands queue on the per-socket write queue.
4. **Per-instance routing.** Only the Ingestion instance currently holding a device's socket can deliver commands to it. The connection registry exists so Directus knows which instance to publish to.
## Sequencing
```
2.1 Connection registry & heartbeat ─┐
2.2 Registry janitor ├─→ 2.4 Command consumer ─┐
2.3 Per-socket write queue ──────────┘ ├─→ 2.5 Codec 12 handler
└─→ 2.6 Codec 14 handler
```
Tasks 2.1, 2.2, 2.3 can be done in parallel; they are independent infrastructure pieces. 2.5 and 2.6 can be parallelized once 2.4 lands.
## Files added
Phase 2 introduces these new files (no Phase 1 file is modified except `src/main.ts` to wire in the command consumer):
```
src/
├── adapters/teltonika/
│ ├── codec/command/
│ │ ├── codec12.ts ← NEW (encoder + response parser)
│ │ └── codec14.ts ← NEW (encoder + ACK/nACK parser)
│ └── command-consumer.ts ← NEW (stream reader, dispatch)
├── core/
│ ├── connection-registry.ts ← NEW
│ ├── write-queue.ts ← NEW
│ └── janitor.ts ← NEW (separate small process or in-process worker)
└── main.ts ← updated to start consumer + registry
```
`src/adapters/teltonika/codec/command/` already exists from Phase 1 (empty placeholder); Phase 2 fills it.
## Out of scope for this phase
- The Directus side of the system (`commands` table, Flows, sweeper) is owned by the Directus repo, not this one. Phase 2 in this repo only handles the Ingestion-side consumer and writer behavior.
- The pending-command sweeper runs in Directus, not Ingestion. Ingestion publishes terminal status (`delivered`, `responded`, or `failed` reasons) and Directus updates the row.