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.
3.6 KiB
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
commandstable inserts can publish a command tocommands: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
0x06or Codec 14 Type0x06/0x11) are correlated to the in-flight command and published tocommands:responsesfor 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:
- Ingestion exposes no user-facing HTTP — never. All command authorization happens in Directus.
- Commands are data before transport. Every command has a row in Directus's
commandstable before it ever reaches Redis. - 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.
- 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 (
commandstable, 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, orfailedreasons) and Directus updates the row.