Arduino firmware for Seeed XIAO ESP32-S3 that reads Precision Circuits Digi-Level tank sensors and publishes levels to Victron GX devices via the dbus-mqtt-devices driver.
- Seeed Studio XIAO ESP32-S3
- 3× 47Ω resistors (1/4W) — series current sense
- 4× 6.8kΩ resistors (1/4W) — voltage divider upper
- 4× 10kΩ resistors (1/4W) — voltage divider lower
- Enclosure (weather-resistant if installing in bay)
- USB-C cable for power
Each tank sensor uses a 47Ω series resistor to create a voltage drop proportional to current, then a 6.8k/10k divider to scale the signal for the 3.3V ADC. An additional divider on A2 measures the 5V rail as a reference for dynamic threshold calculation.
┌─────────────────┐
│ XIAO ESP32-S3 │
│ │
┌─── Fresh Tank ──┐ │ │
│ │ │ │
│ 5V ──47Ω──┬── Sensors ── GND │
│ │ │ │
│ ├── 6.8k ───────┤ A0 │
│ │ │ │
│ └── 10k ────────┤ GND │
└─────────────────┘ │ │
│ │
┌─── Grey Tank ───┐ │ │
│ │ │ │
│ 5V ──47Ω──┬── Sensors ── GND │
│ │ │ │
│ ├── 6.8k ───────┤ A1 │
│ │ │ │
│ └── 10k ────────┤ GND │
└─────────────────┘ │ │
│ │
┌─── 5V Reference ┐ │ │
│ │ │ │
│ 5V ── 6.8k ────┼───────────┤ A2 │
│ │ │ │
│ 10k │ │
│ │ │ │
│ GND │ │
└─────────────────┘ │ │
│ │
┌─── Black Tank ──┐ │ │
│ │ │ │
│ 5V ──47Ω──┬── Sensors ── GND │
│ │ │ │
│ ├── 6.8k ───────┤ A3 │
│ │ │ │
│ └── 10k ────────┤ GND │
└─────────────────┘ │ 5V ├──── USB Power
│ │
└─────────────────┘
Note: Avoid A2 (GPIO3) for sensors — it's a strapping pin that can
cause boot issues. A2 is safe for the passive voltage reference.
Panel 5V
│
47Ω (series, creates voltage drop)
│
├────────── Sensor Loop ────── GND (panel)
│
6.8k (divider upper)
│
├────────── ADC Pin
│
10k (divider lower)
│
GND
A flake is provided for NixOS users:
# Enter dev environment
cd esp32-tank-sensor
direnv allow # or: nix develop
# Build and upload
pio run -t upload
# Monitor serial
pio device monitorIf the XIAO isn't detected, enter bootloader mode: hold BOOT, press RESET, release BOOT.
USB permissions on NixOS:
users.users.youruser.extraGroups = [ "dialout" ];- Install PlatformIO CLI or IDE
- Libraries are specified in
platformio.iniand installed automatically
Create include/secrets.h (git-ignored):
#pragma once
#define WIFI_SSID "YourNetwork"
#define WIFI_PASS "your_password"
#define MQTT_USER "mqtt_username" // optional
#define MQTT_PASS "mqtt_password" // optional
// #define MQTT_SERVER "192.168.1.50" // optional, defaults to venus.localEdit include/config.h for tank settings:
// Tank capacities (liters) and probe counts
#define TANK_FRESH_PROBES 3
#define TANK_FRESH_CAPACITY 300.0f
#define TANK_GREY_PROBES 3
#define TANK_GREY_CAPACITY 150.0f
#define TANK_BLACK_PROBES 3
#define TANK_BLACK_CAPACITY 150.0f-
Enable MQTT on your Victron GX device:
- Settings → Services → MQTT
- Enable "MQTT on LAN (SSL)" (port 8883)
- Note the IP address or use
venus.local(mDNS)
-
The dbus-mqtt-devices driver is included in recent Venus OS versions.
| Pattern | Meaning |
|---|---|
| Solid ON | Running normally |
| Slow blink (1Hz) | Connecting to WiFi |
| Fast blink (4Hz) | Connecting to MQTT |
| Double blink | Waiting for registration |
| OFF | Error or initializing |
Connect at 115200 baud. On the Victron GX via SSH:
microcom -p /dev/ttyACM0 -s 115200
# Exit with Ctrl-X| Command | Description |
|---|---|
RAW |
Show reference voltage, thresholds, and ADC readings |
STATUS |
Show device status, WiFi, MQTT, and tank levels |
HELP |
List available commands |
The Digi-Level system uses capacitive sensors that switch ~2kΩ resistors in parallel when water is detected. The 47Ω series resistor creates a small voltage drop, and the 6.8k/10k divider scales it to ADC range.
Signal is inverted: Higher ADC = emptier tank.
| Level | Sensors | ADC Count (approx) |
|---|---|---|
| Empty | 0 | ~3820 |
| 1/3 | 1 | ~3690 |
| 2/3 | 2 | ~3560 |
| Full | 3 | ~3450 |
The voltage reference on A2 measures the 5V rail directly. Thresholds are calculated as ratios of this reference, making readings immune to supply voltage variations (e.g., laptop USB vs Victron 5V rail).
The firmware uses TLS on port 8883 and the dbus-mqtt-devices registration protocol:
- On connect, publishes registration to
device/tanksensor/Status - Receives device instance assignments on
device/tanksensor/DBus - Publishes tank values via
device/tanksensor/Proxy - Last Will message notifies GX on disconnect
INIT → WIFI_CONNECT → MQTT_CONNECT → REGISTER → WAIT_REGISTRATION → RUNNING
↑ ↑ ↑ │
└──────────────────┴──────────────┴────────────────────────────┘
(on connection loss)
If readings don't match your panel:
- Connect via serial and run
RAWcommand - Note the reference ADC and actual readings at each level
- Calculate new ratios:
threshold / reference * 1000 - Update
THRESH_*_RATIOvalues inconfig.h
Example: If reference=4000 and 1/3 level reads 3800:
#define THRESH_33_RATIO 950 // 3800/4000 * 1000No WiFi connection:
- Check SSID and password in
secrets.h - Verify ESP32 is in range
- LED will slow-blink continuously
MQTT won't connect:
- Verify GX device IP or that
venus.localresolves - Check that MQTT SSL is enabled on GX (port 8883)
- Try
RAWcommand to verify device is running - LED will fast-blink continuously
Tanks not appearing in VRM:
- Check serial output for registration response
- Verify
portalIdis received (useSTATUScommand) - Confirm device appears in GX device list first
ADC readings seem wrong:
- Use
RAWcommand to see reference and thresholds - Verify all resistors are correct values
- Check ground reference connection to panel
- Compare readings between laptop USB and GX 5V power
Boot issues with A2:
- GPIO3 (A2) is a strapping pin — don't use for sensors
- A2 is safe for the passive voltage reference divider
- Use A3 for the third tank
esp32-tank-sensor/
├── src/
│ └── main.cpp # Main firmware
├── include/
│ ├── config.h # Configuration
│ └── secrets.h # Credentials (git-ignored)
├── platformio.ini # PlatformIO config
├── flake.nix # Nix dev environment
└── .envrc # direnv integration
MIT License