Skip to content

Latest commit

History

9 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

PoolLevel logo

PoolLevel ESP8266

MQTT-connected pool expansion tank level monitor — up to 4 float switches, Home Assistant auto-discovery, captive-portal setup UI.

PlatformFrameworkHALicense


Overview

PoolLevel monitors the water level of a pool expansion/buffer tank using 2–4 vertical float switches wired to an ESP8266. It publishes a single named state to MQTT (TOO_LOW / OK / HIGH etc.) and registers itself automatically with Home Assistant via MQTT discovery. Configuration is done entirely through a browser-based UI — no flashing required after initial upload.


Features

  • 2–4 float switches — configurable count, each mapped to any safe GPIO
  • Single MQTT state sensor — named states instead of raw binary values
  • Bottom-up validation — detects wiring/sensor faults if switches don't trigger in order, publishes SENSOR_ERROR
  • Home Assistant auto-discovery — appears as a named sensor entity automatically
  • Captive-portal setup — spawns a WiFi access point with a full config UI if no credentials are stored
  • Async HTTP server — UI never blocks MQTT or switch polling (fixes lwIP slot exhaustion)
  • SSE live push — status page updates in real time without polling
  • HTTP OTA — drag-and-drop firmware update from the UI, LittleFS config survives
  • mDNS — reachable at http://<clientid>.local
  • Factory reset — one button in UI erases config and returns to AP mode

Hardware

Recommended board

Wemos D1 Mini (or any ESP8266 with ≥4 free GPIOs)

Default GPIO mapping

Switch #Position in tankDefault GPIOD1 Mini pin
1Bottom (TOO_LOW)GPIO 4D2
2GPIO 5D1
3GPIO 12D6
4TopGPIO 14D5

All GPIOs are remappable in the setup UI.

Wiring (active-LOW default)

GND ──[float switch]── GPIOx

Each switch is wired between GND and its GPIO pin. The firmware uses INPUT_PULLUP by default — float closed = LOW = water reached this level. Active-HIGH mode is also selectable per switch in the UI.

Important: Wire switches in physical order, bottom to top of the tank. Switch 1 must be the lowest, Switch N the highest.


Level States

The firmware derives a single level state from how many switches are active, after validating that they triggered in the correct bottom-up order.

Active switches2-switch setup3-switch setup4-switch setup
0TOO_LOWTOO_LOWTOO_LOW
1OKLOWLOW
2HIGHOKOK
3HIGHHIGH
4TOO_HIGH
gap detectedSENSOR_ERRORSENSOR_ERRORSENSOR_ERROR

SENSOR_ERROR is published when a higher switch is active but a lower one is not — indicating a wiring fault, failed switch, or physical impossibility.


MQTT Topics

TopicDirectionRetainedDescription
<base>/statepublishCurrent level state
<base>/availabilitypublishonline / offline (LWT)
homeassistant/sensor/<clientId>/configpublishHA discovery payload

Default base topic: pool/level


Home Assistant

The device registers itself automatically via MQTT discovery. A single sensor entity appears under the device with:

  • Device class:enum
  • Icon:mdi:water-percent
  • Options: all valid states for the configured switch count + SENSOR_ERROR

No manual HA configuration needed. To use in automations:

trigger:
- platform: stateentity_id: sensor.poollevel_levelto: "TOO_LOW"action:
- service: notify.mobile_appdata:
message: "Pool expansion tank is empty!"

Software Stack

LibraryVersionPurpose
ESPAsyncWebServer^3.7Non-blocking HTTP server
PubSubClient^2.8MQTT client
ArduinoJson^7.0JSON config serialisation
ESP8266mDNSbuilt-in<clientid>.local hostname
LittleFSbuilt-inConfig persistence

Why ESPAsyncWebServer? The ESP8266 lwIP stack has only 5 TCP/UDP PCB slots. ESP8266WebServer + mDNS exhausts them, leaving no free slot for the MQTT TCP connection (manifests as state=-4 timeout). ESPAsyncWebServer uses far fewer slots, solving this completely.


Getting Started

1. Prerequisites

  • PlatformIO (VS Code extension or CLI)
  • ESP8266 board (Wemos D1 Mini recommended)

2. Clone & build

git clone https://github.com/<your-username>/poollevel-esp8266.git
cd poollevel-esp8266
pio run

3. Flash

pio run --target upload
pio run --target uploadfs # upload LittleFS (first time only)

4. First-time setup

  1. Power on the board — it will start as WiFi AP PoolLevel-Setup (password: poolsetup)
  2. Connect your phone or laptop to that network
  3. Open http://192.168.4.1 (or any URL — captive portal redirects automatically)
  4. Configure WiFi, MQTT, and Switches tabs
  5. Click Save & Reboot
  6. Device connects to your network, registers with Home Assistant

5. Subsequent updates

Navigate to the OTA tab in the UI, drag your .pio/build/d1_mini/firmware.bin onto the upload area. Settings are preserved.


Project Structure

poollevel-esp8266/
├── platformio.ini
└── src/
├── config.h # structs, constants, GPIO defaults
├── config.cpp # LittleFS JSON load/save
└── main.cpp # WiFi, MQTT, HTTP server, level logic, UI

Configuration Reference

All settings are stored in /config.json on LittleFS and editable via the web UI.

KeyDefaultDescription
wifi_ssidWiFi network name
wifi_passWiFi password
mqtt_hostMQTT broker IP or hostname
mqtt_port1883MQTT broker port
mqtt_userMQTT username (optional)
mqtt_passMQTT password (optional)
mqtt_topicpool/levelMQTT base topic
device_namePoolLevelHuman-readable HA device name
client_idpoollevelMQTT client ID + mDNS hostname
ha_discoverytrueEnable HA MQTT auto-discovery
num_switches2Number of switches (2, 3, or 4)
sw0_gpiosw3_gpio4,5,12,14GPIO pin per switch
sw0_actlowsw3_actlowtrueActive-LOW logic per switch

Migrating from v1.x / v2.0

Previous firmware versions published individual binary_sensor entities per switch. These leave stale retained discovery messages in your broker. Clean them up:

CLIENTID="poollevel"# change if differentforiin 0 1 2 3;do
mosquitto_pub -h <broker-ip> \
-t "homeassistant/binary_sensor/${CLIENTID}_sw${i}/config" \
-n -r
done

The v2.1+ firmware also does this automatically on every MQTT connect.


Troubleshooting

SymptomCauseFix
MQTT state=-4lwIP slots exhaustedEnsure ESPAsyncWebServer is used, not ESP8266WebServer
SENSOR_ERROR in HASwitch gap detectedCheck wiring order — sw1 must be below sw2, etc.
Device not in HADiscovery not receivedVerify ha_discovery=true, check broker retained messages
Can't reach device.localmDNS not resolvingUse IP address directly; mDNS can be unreliable on some networks
Settings lost after OTAWrong OTA targetUse HTTP OTA from UI — never pio run --target uploadfs after first setup

License

MIT — free to use, modify, and distribute.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages