Boat project

halmet-tank-monitor

Water tank level monitor for three tanks — port, centre, starboard — built on HALMET hardware with SensESP and Signal K.

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

ComponentPurposeConnection
HALMET board (ESP32)Main controller, WiFi, SensESP framework
ADS1115 16-bit ADCReads resistive tank sender voltageI2C (built into HALMET)
Tank sender — Port waterFloat-type resistive senderADS1115 channel A0
Tank sender — Centre waterFloat-type resistive senderADS1115 channel A1
Tank sender — Starboard waterFloat-type resistive senderADS1115 channel A2
BME280Temperature, humidity, pressure in tank compartmentI2C (0x76 or 0x77, auto-detected)

Code structure

The project uses PlatformIO and is structured as a standard SensESP application:

FilePurpose
src/main.cppAll application logic — setup, sensor connections, Signal K outputs
src/halmet_analog.cpp/.hHALMET-specific ADC helpers — ConnectTankSender() wraps the ADS1115 reading, calibration curve, and SK output into one call
src/halmet_digital.cpp/.hDigital input helpers for alarms and tacho (included but not used in this project)
src/halmet_display.cpp/.hOLED display helpers (display disabled in this build — no display connected)
src/halmet_const.hPin constants for the HALMET board (SDA, SCL, CAN pins etc.)
src/n2k_senders.hNMEA 2000 output helpers (NMEA 2000 output disabled in this build)
src/expiring_value.hUtility: a value that becomes stale after a timeout (used for sensor freshness checking)
platformio.iniBuild configuration, library dependencies, OTA upload settings

Signal K paths published

Signal K pathUnitDescription
tanks.water.port.currentLevelratio (0–1)Port water tank fill level
tanks.water.centre.currentLevelratio (0–1)Centre water tank fill level
tanks.water.starboard.currentLevelratio (0–1)Starboard water tank fill level
environment.inside.temperature.port_water_tank°CPort tank compartment temperature (BME280)
environment.inside.humidity.port_water_tank%Port tank compartment humidity (BME280)
environment.inside.pressure.port_water_tankhPaPort 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 changeWhere in the codeNotes
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

  1. Flash the firmware using PlatformIO: pio run -t upload (serial) or OTA if already on the network.
  2. 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.
  3. Once connected to your boat network, open http://halmet-water-tanks.local (or the IP address) in a browser.
  4. In the web UI, navigate to each tank input and set the empty and full voltage values by observing readings with known tank levels.
  5. The Signal K server should auto-discover the device via mDNS. Approve it in the Signal K admin interface if needed.