Network Bridges and Gateways
How vehicle sensor buses reach the cloud: gateway classes, uplink behaviour, store-and-forward, MQTT delivery semantics, clock drift, failure modes, payload volumes and multi-tenant design, with parameters for the Sense simulator.
1. Overview and where bridges sit
A bridge layer sits between in-vehicle buses and sensors and the cloud broker. It reads frames from CAN (J1939, ISO-TP), NMEA 2000, NMEA 0183, Modbus RTU, LIN, BLE and one-way TPMS radio, stamps and filters the data, buffers it, and publishes it over a cellular, LoRaWAN, Wi-Fi or satellite uplink.
Source figures are tagged in the research as sourced from a URL or as an engineering estimate. Values marked estimate below are engineering estimates, not verified from a fetched source, and should be used as priors with wide jitter.
| Item | Value | Basis |
|---|---|---|
| CAN 2.0B classic | 8 B per frame; about 130 bits including stuffing; at 250 kbps about 0.5 ms wire time, 1.3-1.6 ms effective with arbitration | Estimate |
| J1939 TP.CM_BAM (broadcast) | Up to 1785 B = 255 packets x 7 B; PGN 0xEC00 (CM) and 0xEB00 (DT); inter-packet gap 50-200 ms (50 ms typical) | Partly sourced; numbers are estimates |
| J1939 TP RTS/CTS (peer-to-peer) | RTS, CTS (n packets), DT x n, EndOfMsgACK; timers T1 750 ms, T2 1250 ms, T3 1250 ms, T4 1050 ms, Tr 200 ms, Th 500 ms | Estimate; walkthrough sourced |
| TP failure modes | Missed DT aborts the session (timeout, duplicate or bad sequence); BAM has no retry so one lost frame drops the whole message; connection exhaustion | Sourced |
| J1939 typical PGNs | EEC1 (61444) 20 ms engine speed; CCVS (65265) 100 ms; ET1 (65262) 1 s; DM1 (65226) 1 s while fault active (BAM if more than 1 DTC); VIN (65260) multi-frame on request | Estimate |
| ISO-TP (15765-2) | Single frame up to 7 B (6 with extended addressing); first frame carries length up to 4095 B (larger in CAN FD); consecutive frame sequence 0-15 wraps; flow control with block size and STmin (0-127 ms, 0xF1-F9 = 100-900 us) | Sourced |
| NMEA 2000 | 250 kbps CAN, 29-bit ID; Fast Packet up to 223 B = 6 + 31 x 7 over 32 frames; byte 0 = 3-bit sequence id + 5-bit frame counter; frame 0 byte 1 = total length | Sourced |
| Fast-packet loss | Missing frame or sequence mismatch discards the whole PGN; concurrent senders separated by sequence id 0-7 and source address | Sourced |
| NMEA 2000 rates | Position rapid 10 Hz (129025); COG/SOG 4 Hz (129026); engine rapid 100 ms (127488); engine dynamic 500 ms (127489, fast-packet); GNSS 129029 at 1 Hz, 43 B fast-packet; AIS 129038 fast-packet | Estimate |
| NMEA 0183 | 4800 baud default (AIS 38400); 8N1 gives about 480 B/s; sentence up to 82 chars; XOR checksum; ceiling about 10 sentences/s; full NGW-1 conversion can overload the 4800 output so a selective conversion list is needed | Sourced |
| Modbus RTU | 9600-19200 baud typical; poll 100 ms-1 s per register block; CRC16; timeout 100-500 ms; 3-10% retries on noisy power | Estimate |
| LIN | 19.2 kbps; master schedule slots 5-20 ms; low-value body data; bridged via a LIN-CAN node | Estimate |
| BLE / TPMS | BLE adverts every 1-10 s; gateway scan misses 5-20% of adverts; RSSI -60 to -95 dBm; TPMS is one-way 315/433 MHz with manufacturer-defined interval, assumed 60-300 s parked and 3-30 s moving or on pressure change | Source for TPMS; intervals are estimates |
2. Gateway types and protocol translation
| Class | Examples | Inputs | Uplink | Notes |
|---|---|---|---|---|
| CAN-to-MQTT/TCP telematics box | Teltonika FMC/FMB, CalAmp, Geotab GO, Samsara, Webfleet, Digital Matter | 1-2 CAN, J1939/OBD, K-line, GNSS, accelerometer | 4G Cat-1/Cat-4, LTE-M | Records every 1-10 s, batched 30-300 s; binary codec over TCP or JSON over MQTT |
| NMEA gateway | Actisense NGW-1/NGX-1, Yacht Devices YDGW-02, Digital Yacht, Maretron | N2K, 0183 | Wi-Fi, Ethernet or serial to an onboard router (4G, VSAT, Starlink) | Raw-CAN mode emits hundreds of frames/s on a busy bus |
| Industrial edge | Advantech, Moxa, Siemens, Node-RED, Greengrass | Modbus, CAN, GPIO | Ethernet, 4G | Sparkplug B or JSON |
| LPWAN node | LoRaWAN class A | Analog, BLE, UART | LoRaWAN gateway | Tiny payloads |
| Cloud-defined | AWS IoT FleetWise (signal catalog, campaigns, condition-based collection) | CAN, OBD | MQTT, Protobuf | Collection rules are defined in the cloud |
A Teltonika-style binary AVL record is about 40-120 B, with up to 255 records per packet (estimate).
Protocol translation steps in the research: reassemble multi-frame transport (J1939 TP, ISO-TP, N2K Fast Packet), decode raw signals with scaling and sentinel handling, filter by deadband or exception, then encode into the uplink codec (see sections 4 and 7).
3. Uplinks (cellular, LoRaWAN, Wi-Fi offload, satellite)
| Link | RTT | Throughput | Basis |
|---|---|---|---|
| 4G LTE | 30-60 ms (p95 150) | 5-50 up / 20-100 down Mbps | Estimate |
| LTE-M | 100-300 ms (3GPP target under 10 s) | Up to about 1 Mbps | Sourced |
| NB-IoT | 1.5-10 s; up to 128 UL repetitions | About 20-60 kbps | Sourced |
| 5G | 10-40 ms | 20-300 Mbps | Estimate |
| Wi-Fi offload (depot or port) | 5-30 ms | 20-200 Mbps | Estimate |
| LoRaWAN EU868 | 1-5 s | 0.3-5.5 kbps; app payload 51 B (DR0-2), 115 B (DR3), 222 B (DR4-7) | Sourced |
| GEO VSAT | 600-800 ms (field 700-800) | E.g. 3 (max 10) Mbps down / 1 (max 2) up; HTS 50-150 Mbps | Sourced |
| Starlink maritime | 25-60 ms advertised; polar test 19-99 ms | Polar test about 133 down / 5 up Mbps | Sourced |
| Iridium Certus | 500-600 ms typical; polar 71 ms-10 s | 704/352 kbps (Certus 700); Certus 100 about 88/22 kbps | Sourced |
| Iridium SBD | 20-60 s | 340 B up / 270 B down per message | Estimate |
| Link | Loss and outage |
|---|---|
| 4G LTE | Loss 0.1-1%; handover blips 50-500 ms; weak-cell stalls 2-10 s |
| LTE-M | PSM/eDRX wake delays of seconds to minutes; some plans bill a minimum 2 kB per upload |
| NB-IoT | Poor mobility (reselection, no connected handover); MCL 164 dB |
| 5G | Handover 20-100 ms; LTE fallback |
| Wi-Fi offload | Association 1-5 s; roam failures; used for backfill, OTA and high-rate logs |
| LoRaWAN EU868 | 10-30% loss for moving urban; retries up to 8. 1% duty cycle: 55 B at SF7 = 123 ms airtime (12.3 s wait), at SF12 = 2957 ms |
| GEO VSAT | Rain fade, mast blockage and turns: 5-15% outage, episodes of 1-30 min |
| Starlink maritime | Obstruction drops of 1-15 s common; re-acquisition 1-5 min (estimate) |
| Iridium Certus | Session drops near the poles |
| Iridium SBD | Per-message cost |
| Scenario | Pattern |
|---|---|
| Urban bus | 99%+ coverage; dead spots of 5-30 s at depots and underpasses; handover glitch every 1-5 km |
| Tunnels | 30 s-10 min full loss; GNSS also lost; data buffered |
| Rural truck | 3-10% of drive time without service; episodes 1-30 min, some over 1 h |
| Ambulance | Mostly urban; alarms prioritised; reconnect bursts |
| Ship near coast | Cellular up to about 20-40 km, then VSAT or Starlink; switchover 5-60 s; port Wi-Fi/4G backfill |
| Ship at sea | VSAT 95-99.5% availability; Starlink 97-99.5% with short drops; Iridium 99%+ at low rate |
| Power | Key-off sleep with CAN or accelerometer wake; cranking brownout (under 6 V) reboots 0.1-1 per day on weak-battery trucks |
4. Store-and-forward, batching and MQTT delivery semantics
Store-and-forward (all values estimates)
| Parameter | Value |
|---|---|
| Media | RAM ring 10-100 kB to 1 MB plus flash 4-128 MB; SD on NMEA loggers |
| Capacity | 128 MB at 40 B/record is about 3M records; common specs 100k-1M records |
| Policy | FIFO drop-oldest (silent loss) versus drop-newest; priority lane for alarms and DTCs |
| Replay | Rate-limited at 1-10x realtime, interleaved with live data, which causes out-of-order arrival; or newest-first then backfill |
| Corruption | Flash CRC fail 0.01-0.1%; power loss mid-write loses the last batch |
| Overflow | After 6-72 h offline at 1 Hz, records are dropped; sequence gap visible |
Batching, compression and edge filtering
| Technique | Parameters | Effect |
|---|---|---|
| Time batching | 10 s live, 60 s typical, 300 s eco; flush on ignition-off or buffer at 70-80% | Fewer publishes; latency equals the window |
| Count batching | 20-255 records | Codec limit |
| Compression | gzip/zstd/LZ4 on a JSON batch gives 70-90% reduction; delta-of-delta timestamps plus XOR floats 8-12x | Estimate |
| Deadband | Speed 1 km/h, coolant 1 degC, fuel 0.5%, GPS 10-50 m or 15-30 deg heading | Removes 60-90% of samples (estimate) |
| Report by exception | Send on change plus heartbeat every 30-300 s; DTC on set and clear | Estimate |
| Tiered rate | Running 1 Hz, parked 1/600 s, trip start and end events | Estimate |
| Link-adaptive | VSAT/Iridium 1 sample per 30-300 s, alarms first | Estimate |
MQTT delivery semantics
| QoS | Handshake | Loss | Duplicates | Order | Use |
|---|---|---|---|---|---|
| 0 | None | Yes on any link drop; broker usually does not queue for offline subscribers | No | In order per connection | High-rate position |
| 1 | PUBLISH/PUBACK; retry with DUP=1 and the same packet id | No if session kept | Yes: a lost PUBACK causes redelivery; DUP is only a hint. Model 0.1-2% on lossy cell and 5-15% of in-flight messages around reconnects (estimate) | Retried messages can be overtaken by new ones | Default telemetry |
| 2 | PUBREC/PUBREL/PUBCOMP | No | No at protocol level | In order | Alarms and billing; about 2x round trips, rare on cellular |
- Resend happens only on reconnect with Clean Start=0 and a session present. A clean session discards all in-flight and queued messages.
- Packet ids are reused after completion, so DUP=1 with the same id is ambiguous.
- Keepalive 30-120 s; cellular NAT timeouts 30-300 s so use 60 s or less; reconnect backoff 1-60 s with jitter; TLS handshake 2-4 RTT and 3-6 kB (resumption about 1 kB) (estimates).
- AWS IoT Core allows 128 KB maximum message size; device buffers are often 16-64 KB.
- Cloud dedupe key: (device_id, boot_id, seq). A sequence reset on reboot is a classic source of false duplicates and gaps.
5. Timestamps, clock drift and ordering
| Source | Behaviour |
|---|---|
| GNSS-disciplined time | Error under 1 ms to 1 s; lost in tunnels, then free-runs on RTC |
| Crystal RTC 20-50 ppm | Drift 1.7-4.3 s/day; a cheap RTC up to 100 ppm drifts 8.6 s/day |
| Bus frames carry no timestamp | Gateway stamps at ISR (jitter 0.1-5 ms) or at batch time (error equals the batch window, up to 60 s) |
| Boot without fix | Timestamp is 1970-01-01, 2000-01-01 or last-known; flag 0.1-1% of records |
| Backfill after outage | Record timestamp preserved; received_at is broker time; delta from minutes to days |
| Skew tail | 0.5-2% of devices offset by more than 5 s; 0.05% by more than 1 h (dead RTC battery, GPS week rollover) |
| NMEA 0183 | Time only in RMC/ZDA (1 Hz); other sentences are untimed |
Ordering: QoS 1 retries can be overtaken by newer messages, and replay interleaved with live data arrives out of order (sections 4 and 6). Consumers should order by the record timestamp and detect gaps by sequence, not by arrival time.
6. Bridge failure modes (with frequency/signatures)
All inject rates below are estimates from the research file.
| Failure | Cause | Signature | Rate |
|---|---|---|---|
| CAN bus-off | TEC above 255 (wiring, bitrate mismatch) | All signals vanish for 0.1-5 s (hardware recovery 128x11 recessive bits, app restart 100 ms-10 s) or until power cycle | 0.05-0.5 episodes/vehicle/day; 1% of devices chronic |
| Wrong bitrate | Install or config | Error frames, no data | 0.5% of installs |
| Dropped frames | RX FIFO overrun above 70% bus load | Gaps in cyclic PGNs, alive-counter skips | 0.01-0.5%; up to 5% on an overloaded gateway |
| Duplicate frames | Gateway retry, bridged-bus echo, TP retransmit | Identical timestamp and value | 0.1-1% |
| TP session failure | Lost DT, timer expiry | DM1/VIN missing, abort codes | 0.5-3% of multi-frame messages |
| Fast-packet corruption | Sequence mismatch | 129029/127489 missing or stale | 0.5-2% |
| Buffer overflow | Long outage, DTC storm | Sequence gap, oldest dropped | 0.5-2% of outages over 6 h |
| Timestamp skew | See section 5 | Future timestamp or 1970 | 0.5-2% of devices |
| Encoding errors | Scaling x10/x100, signed/unsigned, endianness, J1939 0xFF/0xFFFF not-available and 0xFE/0xFExx error sentinels, NaN | Impossible values (6553.5 km/h, 25.5) | 0.1-1% of records; sentinels frequent when a sensor is absent |
| Firmware or schema mismatch | Gateway firmware versus cloud parser or DBC version | Unknown IDs, shifted signals, dropped fields | 2-10% of fleet on old firmware for 1-4 weeks after rollout |
| Frozen value | ECU fault or gateway repeats last value | Flatline with valid timestamp | 0.1-1% of signals |
| Reconnect storm | Tower restore, broker restart | Mass reconnect, burst replay, duplicates | Event-based |
| Clock jump | NTP or GNSS correction | Timestamp steps by plus or minus seconds | A few per month |
| GNSS jitter | Urban canyon | 5-50 m jumps, HDOP above 5 | Continuous in cities |
Format-level errors from the research: JSON can carry NaN/Inf, invalid UTF-8 and truncation at the buffer limit; a protobuf schema mismatch drops unknown fields and defaults to 0; a vendor binary firmware or parser mismatch garbles IO IDs.
7. Payload formats and data volumes per vehicle type
| Format | Size vs JSON | Notes |
|---|---|---|
| JSON | 1.0 (250-600 B per 10-signal record) | Repeated keys, text numbers; errors include NaN/Inf, invalid UTF-8, truncation at buffer limit |
| CBOR | About 0.5-0.8 (one test 0.79); integer keys about 0.3-0.5 | 1:1 JSON data model |
| Protobuf | 0.2-0.5 numeric-heavy (4-5x smaller); about 0.66 mixed (9% smaller when both gzip) | Schema mismatch drops unknown fields, defaults to 0 |
| Vendor binary | 0.1-0.3 | Firmware or parser mismatch garbles IO IDs |
| Sparkplug B | Protobuf plus metric alias | NBIRTH/DBIRTH/NDATA/NDEATH; topic spBv1.0/group/msgtype/edge/device has no tenant level |
Measured reference points: Digital Matter reports 1-3 MB/month for battery trackers, 5-10 MB/month for wired trackers, 1-1.5 kB per NB-IoT heartbeat, and firmware of 300-500 kB.
| Vehicle | Streams | Publishes | Bytes per publish | Records/s | Daily volume |
|---|---|---|---|---|---|
| Basic tracker | GPS every 30 s moving, ignition | 1 per 60-300 s | 150-600 B JSON, 50-200 B binary | 0.03 | 0.1-0.5 MB |
| City bus (8-16 h) | GNSS 1 Hz, speed, doors, passenger count, FMS J1939, TPMS | 1 per 10-60 s | 0.5-4 kB JSON (0.2-1 kB CBOR/PB) | 2-10 | 5-40 MB JSON; 1-8 MB binary or compressed |
| Long-haul truck (10-14 h) | J1939 rpm/speed/fuel/DPF/EBS/axle, reefer Modbus, TPMS 6-18 wheels, GNSS | 1 per 10-30 s | 1-6 kB JSON | 5-30 | 10-60 MB JSON; 2-15 MB binary |
| Ambulance (6-24 h, mostly idle) | GNSS, siren/lights, engine, patient-bay temp/O2 (Modbus/BLE), device status | 1 per 5-30 s (1 s on dispatch) | 0.5-3 kB | 1-15 bursty | 3-30 MB |
| Ship (24 h) | N2K engines 1-10 Hz x2, GNSS 1-10 Hz, nav, tanks, bilge, AIS, 0183, Modbus gensets | 1 per 5-60 s over satellite | 2-30 kB batched | 20-300 raw, 1-20 after deadband | 50 MB-2 GB raw; 5-100 MB reduced; under 5 MB on Iridium |
| Full raw CAN trace | All frames | Continuous | - | 500-3000 frames/s | 0.5-5 GB (not normally streamed) |
| Vehicle | Monthly volume |
|---|---|
| Tracker | 3-10 MB |
| Bus | 150-1000 MB |
| Truck | 300-1500 MB |
| Ambulance | 100-600 MB |
| Ship | 1-30 GB (VSAT/Starlink); 50-300 MB (Iridium) |
8. Multi-tenant topic, schema and policy design
| Concern | Pattern |
|---|---|
| Device identity | X.509 certificate per device (CN = device_id or IMEI); thing name {tenant}-{class}-{serial}; legacy user/password fallback; ICCID/SIM swaps cause identity drift |
| Topic namespace | t/{tenantId}/v1/{vehicleClass}/{deviceId}/{telemetry|event|status|cmd|ack}; Sparkplug variant t/{tenant}/spBv1.0/{group}/NDATA/{edge}/{device}; LWT retained on .../status; the v1 segment allows schema evolution |
| Isolation | ACL: a device publishes only under t/${tenant}/.../${deviceId}/# and subscribes only to its own cmd; AWS policy variables such as ${iot:Connection.Thing.ThingName}; hard isolation via separate accounts |
| Per-tenant schema | schema_id or version in header or topic; tenants differ in signal set (bus: doors/pax; ambulance: patient-bay temp; marine: N2K PGN names), units (km/h vs kn, degC vs degF), key naming (camel vs snake), time format (epoch ms vs ISO-8601), and missing as null vs sentinel |
| Quotas | Per-tenant msgs/s and bytes/day caps; throttling drops QoS 0 and delays QoS 1 |
| Edge cases | Device moved between tenants (stale retained messages); duplicate client IDs cause connect-kick flapping every 1-5 s; wrong-prefix publish is rejected or disconnected by the ACL |
| Tenant | Sampling policy | Payload format | Retention |
|---|---|---|---|
| A: bus | 1 Hz GPS with 10 m deadband | Signals: doors, passenger count (research gives no format; see inference below) | Raw 7-30 d; 1-min rollups 13 months; 1 h rollups 5-7 y; driver GPS trails shortened or anonymised after N days (GDPR) |
| B: ambulance | 1 Hz always, engine every 5 s | Signals: patient-bay temperature (research gives no format) | Medical audit 6-10 y; legal-hold flag |
| C: marine | 10 s on satellite, 1 s on port Wi-Fi | N2K PGN names, units in kn (research gives no format) | Safety logs per flag state |
Sampling policy is pushed through a retained config topic or device shadow; 1-10% of devices run an old policy for hours (config lag). Payload formats per tenant are not specified in the research. Inference: assign tenants from the format table in section 7 (for example JSON for bus, CBOR or Protobuf for ambulance, batched Protobuf for marine) as generator defaults.
9. Generator guidance for the simulator
The pipeline below is from the research. The concrete parameter choices in the blocks that follow are inference built from the cited ranges, not additional sourced facts.
Pipeline
- Truth signals at native bus rates, then the bus layer (frame loss, TP aborts, bus-off windows, sentinels).
- Gateway: drifting clock, deadband, batch window, codec, finite buffer.
- Link state machine: up, degraded and down per link, with Markov dwell times from section 3.
- Broker semantics: QoS, duplicates, reorder on replay.
- Cloud record with received_at, boot_id, seq, batch_id, fw_version, schema_version and tenant.
- Per-vehicle latent traits: clock ppm, firmware version, buffer size, link type, chronic-fault flag.
- Emit a ground-truth label for every injected imperfection.
Clock and timestamp parameters (inference from section 5)
- Per-vehicle drift ppm: draw from uniform(20, 50); with 10% probability use uniform(50, 100). Drift seconds per day = ppm x 0.0864.
- Skew tail: with p = 0.005-0.02 add an offset above 5 s; with p = 0.0005 add an offset above 1 h.
- Record stamp error: at ISR, uniform(0.1, 5) ms; at batch time, uniform(0, batch_window) up to 60 s.
- Boot without fix: with p = 0.001-0.01 per record set ts to 1970-01-01 or 2000-01-01 and label it.
- Backfill: keep record ts, set received_at = broker time, so received_at minus ts equals the outage duration plus replay delay.
Link state machine (inference from sections 3 and 6)
- States: up, degraded, down. Draw dwell times from exponential or lognormal distributions fitted to the outage ranges in the table in section 3.
- Urban bus: down episodes uniform(5, 30) s, one handover glitch every uniform(1, 5) km, tunnel episodes uniform(30, 600) s.
- Rural truck: 3-10% of drive time down, episode length 1-30 min with a heavy tail beyond 1 h.
- Ship at sea: VSAT down 0.5-5% of time in episodes of 1-30 min; Starlink obstruction drops uniform(1, 15) s with re-acquisition uniform(1, 5) min; switchover from cellular uniform(5, 60) s.
- Message loss on the wire: LTE 0.1-1%, LoRaWAN moving urban 10-30%.
Delivery and buffering (inference from sections 4 and 6)
- QoS 1 duplicate probability: 0.1-2% per publish on a lossy cell; 5-15% of in-flight messages after a reconnect.
- Buffer records capacity = flash_bytes / bytes_per_record, for example 128 MB / 40 B = about 3M. Overflow when outage_seconds x record_rate exceeds capacity; drop-oldest by default and leave a seq gap.
- Replay at rate_multiplier uniform(1, 10) x realtime, interleaved with live data so arrival order differs from ts order.
- Dedupe key (device_id, boot_id, seq); reset seq to 0 and bump boot_id on a reboot. Power-brownout reboots at 0.1-1 per day for weak-battery trucks.
Daily volume check (inference)
- bytes_per_day = records_per_s x active_seconds x bytes_per_record x format_ratio, where format_ratio is taken from the section 7 format table (CBOR 0.5-0.8, Protobuf 0.2-0.5, vendor binary 0.1-0.3).
- Validate output against the per-vehicle daily ranges in section 7; for example a city bus should land at 5-40 MB JSON per day.
| Parameter | A: bus | B: ambulance | C: marine |
|---|---|---|---|
| GPS sampling | 1 Hz, 10 m deadband | 1 Hz always | 10 s on satellite, 1 s on port Wi-Fi |
| Engine sampling | Per bus profile (research gives no value) | 5 s | Per N2K rates (127488 at 100 ms, 127489 at 500 ms) |
| Extra signals | Doors, passenger count | Patient-bay temperature, O2 | N2K PGN names |
| Default payload format (inference) | JSON | CBOR or Protobuf | Batched Protobuf |
| Speed unit (inference) | km/h | km/h | kn |
| Typical publish interval | 10-60 s | 5-30 s (1 s on dispatch) | 5-60 s over satellite |
| Retention | Raw 7-30 d; rollups 13 months and 5-7 y; GDPR trail shortening | 6-10 y audit | Per flag state |
| Config lag | 1-10% of devices on old policy for hours | 1-10% | 1-10% |
10. How Sense and Queen carry this data
Source and status. This section restates TENANT_SUBVARIANT.md (checked against the code on 12 Oct 2026), the Sense documents layout-v1.md and BUILD.md, and META_STORAGE.md (prepared as local edits on 13 Oct 2026). On 13 Oct 2026 the database migrations were applied: the ClickHouse meta-storage migration (five statements), the static variant master (table and 13 seed rows for bus, truck and ambulance) and the subvariant column on the tenant table of all 16 business schemas that have one. Before the ClickHouse migration was applied it was run in a throwaway clickhouse-local process on the live server version (26.8.16.41); that run found a rejected tenant_profile definition, which was fixed with the table setting allow_dimensions_outside_sorting_key = 1. The new Queen, the platform API code and the new Sense are not deployed. Volume figures are estimates, not measurements. Queen is the cloud service that receives Sense's transmissions and writes them to ClickHouse.
10.1 The subvariant is stored on the tenant
A tenant with a variant (for example bus) can carry an optional subvariant (for example city-transit); an empty string means none. The field is a lowercase string matching [a-z][a-z0-9_-]{0,39}, accepted only when a variant is given; malformed values are rejected with HTTP 400. An existing tenant without one returns an empty string, and a later create call with a different non-empty value updates it (an empty or missing value never removes it).
- Business database: column
TENANT_DEF.subvariant VARCHAR(40) NOT NULL DEFAULT '', added by the migration2026-10-12-tenant-subvariant.sql. - Static database (
pn_static_db): master tableTENANT_VARIANT_MASTERwith id, variant, subvariant, label, description, sort order, active flag and created date, unique on (variant, subvariant); the row with an empty subvariant is the variant itself. Created and seeded with the bus subvariants by2026-10-12-tenant-variant-master.sql; truck and ambulance are added by2026-10-13-tenant-variant-master-truck-ambulance.sql. Further variants need only inserted rows, no code change. - Runtime tolerance: the platform PHP detects the column and the table at runtime, so the code can be deployed before the migrations. Without the column the subvariant is returned empty and not stored; without the master table (or with the static database unreachable) only the regular expression is checked. If the master has active subvariants for the variant, an unknown value is rejected with the allowed values listed.
- Who reads it: the tenant create, list, keys-all and links endpoints return it;
get_tenant_layout.phpadds it to its tenant block, which is what Sense reads for the tenant identity. Sense keeps it in the settingbuilding.subvariantonly to choose the vehicle profile of its compatibility check. The stored layout format andset_tenant_layout.phpare unchanged, so the layout sent to the platform does not carry it. The Controller and the simulator desktop app read it too (the simulator's change was uncommitted when checked).
| Variant | Subvariants (code: label) | Migration |
|---|---|---|
| truck | long-haul: Long-haul truck; reefer: Reefer / cold-chain truck; tanker: Tanker truck | 2026-10-13 |
| bus | staff-shuttle: Hospital staff shuttle; city-transit: City transit; intercity-coach: Intercity coach; electric-fleet: Electric fleet | 2026-10-12 |
| ambulance | bls: Basic life support; als: Advanced life support; critical-care: Critical-care transport | 2026-10-13 |
10.2 Tenant meta in the transmission
Sense adds the subvariant to the tenant meta of every transmission (readings, alarms and messages) under the name subvariant, after the building manager's custom values. A custom value that is itself named subvariant is kept and the platform's is not sent; nothing is sent when the tenant has none. Sense and Queen are independent in this: a transmission with or without the subvariant meta is valid for both. In a transmission the entity group is the path down to the controller without the network, and the network is sent in the entity's meta.network.
10.3 What Queen stores: new columns and the tenant_profile table
Before this change Queen kept only a fraction of what it parsed: all tenant meta, tags, path and the non-coordinate location parts, the fixedread node meta, all entity meta and the impact and actor names of reading entities were lost on every table. The change is additive (nothing dropped or renamed, no TTL).
| Column | Holds |
|---|---|
| tenant_meta (Map) | The tenant meta, including the subvariant |
| tenant_tags, tenant_path, tenant_location | Tags as sent; path as JSON text; name, address, lat, lng, city, state, country and region as text |
| node_location | The node's location only when it differs from the tenant's (empty means the same) |
| meta (reading only) | The fixedread node meta |
| entity_meta | Entity meta: a map on reading, one map per entity (parallel to entity.name) on notification and change |
| entity_impact, entity_actors | Impact entity names and actor names of the entity |
| detail (change only) | Entity manager, actors and each entity's impact entities in full, as JSON |
- bhairava.tenant_profile (new, durable, written by Queen): the tenant's own description (variant, group, tags, path, location, meta) per tenant and content, joined to the other tables on (owner_oid, tenant_type, tenant_name). It is an AggregatingMergeTree keyed with a
meta_hash(first 16 hex digits of the SHA-256 of variant, group, tags, path, location and meta, with meta keys sorted), so a change of the subvariant or any custom value becomes a new row and the history is kept with the first and last time each was seen. Queen writes a row only when the profile changed or an hour has passed, about 24 small rows per tenant per day. - bhairava.sensor_profile.entity_meta (new column, written by the sampler): the stable part of a sensor's newest entity meta (alias, controller alias, network, device, address, object type and similar), with impact and actor names under the keys
@impactand@actors. Per-packet keys (raw datagram hex, source and receive times, sequence numbers, status flags, values over 200 characters) are left out.
Still lost, by design or because the code cannot carry it: the transmission @ts (the parser never reads it, and Sense does not send it), the changerecord entity @variant, and the per-packet entity meta and fixedread node meta after the pruning below.
10.4 Pruning of readings and what lasts
The sampler (SensorSampler, every 5 minutes) drops each hourly bhairava.reading partition that ends at or before the watermark minus a 10-minute overlap, after every successful run. A reading therefore lives about 1 h 10 min to 2 h 15 min, longer only while the sampler is failing or disabled (a failed run drops nothing). Anything stored only in reading is gone after that; this is why the durable parts are copied to two small durable places.
| What | Where | Kept |
|---|---|---|
| Raw datagram, per-packet entity meta, fixedread node meta, the values themselves | reading | About 1 h 10 min to 2 h 15 min, then the partition is dropped |
| Rollups (entity_rollup, sensor_pass) | Rollup tables | 24 hours (controller_rollup_daily 48 hours); they carry no meta |
| Tenant meta (subvariant, custom values), tags, path, location, variant, group | tenant_profile | Kept, one row per content change, with first and last seen |
| Stable entity meta, impact and actor names of a sensor | sensor_profile.entity_meta | Kept; the latest, replaced as it changes (no history) |
| Notifications and state changes with their tenant and entity meta | notification, change | Kept (no TTL) |
| Sensor, controller and incident profiles; tags | sensor_profile, controller_profile, incident, incident_window | Kept |
Estimate, not measured: the new columns add about 1.4 KB (0.9-2.0 KB) per reading row, mostly the raw datagram; at 26 events per second per bus and 24 buses that is about 75 GB written per day, but because of the pruning only about 3.8-7.1 GB uncompressed is on disk at any moment. Compression was not measured (5x to 10x is an expectation). The durable additions are small: about 0.3 MB a day written for tenant_profile for 24 buses. Whether raw meta should be kept at all, or for how long, is left to the owner; no TTL was applied.
10.5 Safe rollout order
- Done on 13 Oct 2026: run the migration
migrations/2026-10-13-meta-storage.sqlon ClickHouse (additive, metadata only, safe to re-run): the meta columns,sensor_profile.entity_metaandtenant_profile. The Queen that is running now names its columns in every statement, so it keeps working and the new columns stay empty. Run it right after a sampler run (they are 5 minutes apart). - Not done yet: deploy the new Queen.
- Not done yet: deploy the new Sense where subvariants are wanted.
The platform migrations of 10.1 can run in any order relative to the code, because the PHP detects the column and table at runtime. Rolling Queen back afterwards is safe.
| Missing | Effect |
|---|---|
| A new column on a data table | The insert is refused (code 16). The table now falls back at once to the old column list: rows are saved without their meta (that meta is lost for good, the rows are not), an ERROR line says so, and the full insert is retried every 30 s. Before this change the same refusal either blocked all tables behind one stuck writer or dropped the rows. |
| tenant_profile | Its rows are dropped and counted (profile_dropped), with an ERROR line once per 5 minutes; the data tables are not affected. |
| sensor_profile.entity_meta | Every sampler run fails, so the watermark does not move and nothing is pruned; no rows are lost, and reading grows until the migration runs. There is deliberately no fallback for this one. |
10.6 What is not verified
- Not deployed: the new Queen, the platform API code and the new Sense. The migrations are applied; the code that writes the new columns and tables is not running yet, so they are empty.
- Validated in clickhouse-local before applying: the ALTER and CREATE TABLE statements (applied to the old definition they give the same columns as a fresh install, and a second run changes nothing), the insert formats with the new columns, and two profile rows merging into one with the first and last seen times kept. Not validated: ARRAY JOIN over the parallel entity arrays, and argMax over a Map. The HTTP status ClickHouse returns for code 16 was not observed (both 4xx and 5xx are handled and tested with a fake server).
- That ADD COLUMN on reading blocks nothing while the sampler runs is reasoned, not tested. The tests (Queen's save handler and sampler checks) pass against a fake HTTP server and an in-memory store, not against ClickHouse.
- The dashboard is not changed and does not use the new meta.
11. Uncertainty and assumptions
Most numbers in this report are engineering estimates, not verified from a fetched source; the research file tags them as such. Use them as priors and apply wide jitter.
- Sourced figures: ISO-TP and NMEA 2000 framing, NMEA 0183 limits, MQTT QoS behaviour, LoRaWAN EU868 payload sizes and duty cycle, LTE-M, NB-IoT, VSAT, Starlink and Iridium Certus link figures, format size ratios, and Digital Matter data use.
- Estimates: all failure rates, store-and-forward parameters, clock drift tails, coverage patterns, per-vehicle data volumes and the 4G, 5G and Wi-Fi link figures.
- Inference (added in this report): the per-tenant payload format and unit defaults, and the generator distributions in section 9. The research gives tenant sampling policy and retention but not formats.
- Source citations for some rows are partial: the J1939 BAM row cites standards documents but the numbers are estimates.
- The topic structure and ACL patterns are design suggestions, not mandated standards. Sparkplug B has no tenant level in its topic namespace.
12. Sources
- AUTOSAR SAE J1939 Transport Layer specification
- Transport Protocol Vulnerabilities on SAE J1939 Networks (Colorado State)
- Copperhill: J1939 multipacket RTS/CTS session
- NDSS VehicleSec 2023 slides
- ISO 15765-2 (Wikipedia)
- Zephyr ISO-TP documentation
- Pico Technology: NMEA 2000 decoding
- open-ships/n2k framer
- Actisense: choosing the right NGW-1
- Panbo: AMEC NK-80 0183/N2K adaptor
- Tomorrow's Technician: TPMS sensor signals
- AWS IoT FleetWise
- Hubble: JSON vs CBOR for IoT payloads
- Auth0: beating JSON performance with Protobuf
- Eclipse Sparkplug topic namespace and state management v2.2
- EMQX: introduction to MQTT QoS
- ThingsBoard: MQTT QoS
- MQTT Google Group thread
- AWS re:Post: maximum MQTT payload length
- DTU: benchmarking cellular IoT technology
- Digital Matter: how much data do devices use
- arXiv 2106.13286 (NB-IoT)
- Embedded.com article on NB-IoT
- The Things Network: EU868 regional parameters
- The Things Network: fair use policy explained
- Boatnews: satellite internet for boats
- AWI: satellite connectivity notes
- UNOLS: Starlink appendix
- Iridium: maritime low latency
- Iridium Certus 100 fact sheet
- Digital Matter: SIM cards and data usage
- 1NCE playbook: vehicle telematics
- AWS IoT: connect and publish
- AWS IoT: connect policy
- HiveMQ: understanding MQTT topic namespace for IIoT