-
Notifications
You must be signed in to change notification settings - Fork 7
How TankSync Works
Under-the-hood deep dive for the curious: what each part of the system does, how packets flow, what the sleep cycle looks like, how data ends up in your dashboard.
If you just want to use TankSync, Quick Start is enough. This page is for people who want to modify it, port it, or just understand it.
flowchart LR
TX[TX node<br/>solar + 18650] -->|LoRa 865 MHz<br/>~250 ms per wake| RX[RX hub<br/>USB-C]
RX -->|Wi-Fi + MQTT TLS| Broker[(MQTT broker<br/>local or cloud)]
Broker --> HA[Home Assistant<br/>HACS integration]
Broker --> PWA[TankSync PWA<br/>tanksync.smartghar.org]
One TX per tank. One RX per property. Up to 6 TXs per RX. Broker is your own Mosquitto, HA's built-in broker, or mqtt.smartghar.org.
The TX is the power-constrained side. Default behaviour:
- Wake (from deep-sleep timer or hard reset). ~3 ms boot from RTC memory.
-
Power up sensor rail — drive the 5V high-side gate HIGH, wait
PWR_SETTLE_MS(50 ms) for the AJ-SR04M analog front-end to stabilize. - Sample 5 ultrasonic readings, 50 ms apart. Median = the level. Reject readings <5 cm or >400 cm.
- Read INA219 (or ADC for Variant A): battery voltage + signed current (+ve = discharge, -ve = charge).
- Build TANK packet — current sensor state, battery, RSSI estimate, sensor_status byte.
- Transmit + wait for ACK — up to 3 s. Up to 3 retries.
- Power down 5V rail.
-
Deep sleep for
SLEEP_INTERVAL_S(default 300 s).
Awake duration: ~250 ms typical. Sleep current: ~10 µA. Wake current: ~70 mA peak during TX. Solar charging keeps the 18650 topped up indefinitely; without solar, a single 18650 lasts 3-6 months.
See reference_tx_power_budget_2026_05_19 for bench-measured numbers.
The RYLR998 modules talk AT-command-style UART. TankSync wraps a simple protocol on top:
TANK|<addr>|<seq>|<dist_cm>|<bat_v>|<cur_ma>|<sensor_status>|<rssi_est>
| Field | Bytes | Description |
|---|---|---|
TANK |
4 | Magic prefix |
addr |
1-5 | TX address (1, 2, 3… on this hub) |
seq |
1-5 | Per-TX sequence number (wraps at 65535) |
dist_cm |
1-4 | Median distance reading in cm; 0 if read failed |
bat_v |
4-5 | Battery voltage with 2 decimals (e.g. 3.92) |
cur_ma |
1-5 | Signed current in mA (+ = discharging, - = charging) |
sensor_status |
1 |
'o'=ok, 'e'=error, 'u'=unknown |
rssi_est |
3-4 | TX's last-known RX RSSI (so RX can graph link quality) |
Pipe-separated ASCII for human-readability in serial logs. Total ~30 bytes per packet — fits comfortably in RYLR998's payload limit.
ACK|<addr>|<seq>|<next_check_in_s>|<config_blob_or_empty>
next_check_in_s lets the hub adjust the TX's sleep interval dynamically (lower during pump events, higher when stranded). config_blob carries calibration updates pushed from PWA.
PAIR_REQ|<mac>|<requested_addr>|<seq>
MAC included since tx-v2.0.11 — enables tombstone restore on re-pair (preserves name + capacity + alerts across delete cycles).
PAIR_ACK|<assigned_addr>|<netid>|<initial_config>
Assigns the TX a small int address (1, 2, 3…) instead of letting it pick randomly. NETID stays fixed across pairs.
Each RYLR998 listens for packets matching its configured NETID. Packets with a different NETID are filtered at the radio level — they never reach the MCU.
When a hub powers on for the first time, it generates a random NETID in 1-200 and persists it to NVS. The NETID never rotates afterwards (rx-v2.7.10+). All TXs paired to that hub get configured with the same NETID during PAIR_ACK.
Two hubs at the same physical location → 0.5% chance of colliding NETIDs (~10 hubs before the birthday-bound matters). If it happens, factory-reset one hub; its next first-pair picks a different value.
flowchart LR
Boot[Boot] --> Setup{Wi-Fi creds<br/>in NVS?}
Setup -->|No| AP[Setup mode<br/>TankSync-Setup-XXXX AP]
Setup -->|Yes| WiFi[Connect to Wi-Fi]
WiFi --> Live[Live mode<br/>LoRa RX + MQTT pub]
Live -->|BOOT button 5s| AP
AP -->|Captive portal config| WiFi
In Live mode, the hub:
- Listens for LoRa packets continuously (RYLR998 always-on, ~30 mA).
- Acks every TANK packet within ~50 ms.
- Publishes per-tank readings to MQTT (retained topics).
- Computes "things worth knowing" insights locally (total reserve, yesterday's draw, sediment drift) every 30 min and publishes them.
- Serves the local web UI on port 80.
The hub never does cloud round-trips for control decisions. The cloud + PWA are observers; the hub is the source of truth for pairing and state.
All topics scoped under tanksync/<device_id>/ where <device_id> is the hub's MAC-derived ID.
| Topic | Direction | Retained? | What |
|---|---|---|---|
tanksync/<hub>/status |
hub → broker | Yes | Hub-level state (online, version, NETID) |
tanksync/<hub>/tank/<addr>/state |
hub → broker | Yes | Per-tank current state (level, battery, signal, last-seen) |
tanksync/<hub>/tank/<addr>/history |
hub → broker | No | History buffer dumps (PWA → graphs) |
tanksync/<hub>/insights |
hub → broker | Yes | "Things worth knowing" derived insights |
tanksync/<hub>/cmd |
PWA → broker → hub | No | Remote commands (pair, calibrate, remove_tx, identify) |
tanksync/<hub>/cmd_ack |
hub → broker | No | Ack for the command above |
homeassistant/sensor/<discovery>/config |
hub → broker | Yes | HA auto-discovery payloads (if enabled) |
Home Assistant auto-discovery means tanks show up as entities automatically — no YAML config needed.
Two paths:
- PWA logs into your account.
- Account row in cloud DB lists the device IDs you've claimed.
- PWA subscribes to
tanksync/<device_id>/+/statetopics via the cloud broker. - Updates stream in via WebSocket.
- PWA cannot fetch local-IP HTTP URLs (mixed-content blocked).
- Instead, PWA shows a "Open hub web UI" button that opens
http://<hub-ip>/in a new tab when you're on the same Wi-Fi.
The hub publishes its current local IP in the retained <hub>/status topic. The PWA reads it from there. Works as long as you've claimed the hub at least once.
| Component | License | Public |
|---|---|---|
| RX firmware | AGPL-3.0 | ✅ |
| TX firmware | AGPL-3.0 | ✅ |
| Hardware (schematics, BOM, STL) | CC-BY-SA 4.0 | ✅ |
| HACS integration | MIT | ✅ |
PWA (tanksync.smartghar.org) |
Proprietary | 🚫 |
| Cloud server (Node + MQTT bridge + DB) | Proprietary | 🚫 |
The open parts are enough to build a fully working TankSync system without ever touching the cloud. The cloud + PWA are the SaaS convenience layer — paid users get it hosted; everyone else can self-host with Mosquitto + Home Assistant.
- Wiring Reference (Hub) — pin map + power chain diagram
- Wiring Reference (Transmitter) — same for TX
- Manual Flashing — build from source + idf.py monitor
- Self-Host Guide — run your own broker, skip the cloud
- Firmware Versions — per-binary release history
Start here
Build it
Flash it
Use it
Reference