halmet-tank-monitor
Water tank level monitor for three tanks — port, centre, starboard — built on HALMET hardware with SensESP and Signal K.
Project links
What it does
This firmware runs on a HALMET board mounted in the boat's bilge area. It reads three resistive tank senders (the standard marine float-type senders with a variable resistance output) via the on-board ADS1115 ADC, converts the readings to fill percentages, and publishes them to Signal K over WiFi WebSocket. A BME280 environmental sensor mounted inside the port water tank compartment adds temperature, humidity, and pressure readings. The device registers itself as halmet-water-tanks on the boat network and supports OTA firmware updates.
Hardware
| Component | Purpose | Connection |
|---|---|---|
| HALMET board (ESP32) | Main controller, WiFi, SensESP framework | — |
| ADS1115 16-bit ADC | Reads resistive tank sender voltage | I2C (built into HALMET) |
| Tank sender — Port water | Float-type resistive sender | ADS1115 channel A0 |
| Tank sender — Centre water | Float-type resistive sender | ADS1115 channel A1 |
| Tank sender — Starboard water | Float-type resistive sender | ADS1115 channel A2 |
| BME280 | Temperature, humidity, pressure in tank compartment | I2C (0x76 or 0x77, auto-detected) |
Code structure
The project uses PlatformIO and is structured as a standard SensESP application:
| File | Purpose |
|---|---|
src/main.cpp | All application logic — setup, sensor connections, Signal K outputs |
src/halmet_analog.cpp/.h | HALMET-specific ADC helpers — ConnectTankSender() wraps the ADS1115 reading, calibration curve, and SK output into one call |
src/halmet_digital.cpp/.h | Digital input helpers for alarms and tacho (included but not used in this project) |
src/halmet_display.cpp/.h | OLED display helpers (display disabled in this build — no display connected) |
src/halmet_const.h | Pin constants for the HALMET board (SDA, SCL, CAN pins etc.) |
src/n2k_senders.h | NMEA 2000 output helpers (NMEA 2000 output disabled in this build) |
src/expiring_value.h | Utility: a value that becomes stale after a timeout (used for sensor freshness checking) |
platformio.ini | Build configuration, library dependencies, OTA upload settings |
Signal K paths published
| Signal K path | Unit | Description |
|---|---|---|
tanks.water.port.currentLevel | ratio (0–1) | Port water tank fill level |
tanks.water.centre.currentLevel | ratio (0–1) | Centre water tank fill level |
tanks.water.starboard.currentLevel | ratio (0–1) | Starboard water tank fill level |
environment.inside.temperature.port_water_tank | °C | Port tank compartment temperature (BME280) |
environment.inside.humidity.port_water_tank | % | Port tank compartment humidity (BME280) |
environment.inside.pressure.port_water_tank | hPa | Port tank compartment pressure (BME280) |
Signal K values for tank levels are always in the range 0–1 (a ratio). Multiply by 100 to get a percentage. The SensESP framework handles this conversion automatically via halmet_analog.cpp.
Key things to customise
Almost everything you'd need to change for a different boat is in src/main.cpp:
| What to change | Where in the code | Notes |
|---|---|---|
| WiFi credentials | set_wifi_client("SSID", "password") in setup() |
Or remove the line entirely to use the SensESP web AP for first-time setup |
| Signal K server address | set_sk_server("ip", port) in setup() |
Commented out by default — SensESP discovers the server via mDNS |
| OTA password | enable_ota("password") in setup() |
Change from default before deploying |
| Hostname | set_hostname("halmet-water-tanks") |
Used for mDNS, OTA target, and identification in Signal K |
| Which tanks are connected | ConnectTankSender(ads1115, channel, name, sk_path, sort_order, enable_sk) |
Channel is 0–3 (A0–A3 on ADS1115). Comment out any tanks you don't have. |
| Tank sender calibration | Via the SensESP web UI at http://halmet-water-tanks.local |
Set min/max resistance for your specific senders — no recompile needed |
| Signal K path names | The sk_path argument in ConnectTankSender() |
e.g. change "water.port" to "water.fresh.starboard" for different naming |
| Enable NMEA 2000 output | Uncomment #define ENABLE_NMEA2000_OUTPUT |
Sends tank levels onto NMEA 2000 bus as well as Signal K |
How the tank sender reading works
A marine resistive tank sender is a float on a lever arm with a variable resistor — resistance changes with tank level (typically 0–190 Ω or 0–240 Ω depending on manufacturer). The HALMET board provides a reference voltage and a sense resistor; the ADS1115 reads the resulting voltage divider output.
The ConnectTankSender() function in halmet_analog.cpp creates an ADS1115VoltageInput reading from the specified channel, applies a Linear transform (configurable via the web UI) to convert voltage to a 0–1 ratio, and connects the output to a SKOutputFloat. The configuration path (e.g. /Tanks/Port Water) is used as the key in the SensESP web configuration UI, where you can set the min and max voltage values that correspond to empty and full.
ratio = (voltage - V_empty) / (V_full - V_empty)
The calibration values are stored in LittleFS on the ESP32 and persist across reboots and OTA updates.
First-time setup
- Flash the firmware using PlatformIO:
pio run -t upload(serial) or OTA if already on the network. - On first boot (or if WiFi fails), SensESP creates a WiFi access point called
Configure halmet-water-tanks. Connect to it and enter your WiFi credentials. - Once connected to your boat network, open
http://halmet-water-tanks.local(or the IP address) in a browser. - In the web UI, navigate to each tank input and set the empty and full voltage values by observing readings with known tank levels.
- The Signal K server should auto-discover the device via mDNS. Approve it in the Signal K admin interface if needed.