{"id":46705091,"url":"https://github.com/marklynch/pool-controller-code","last_synced_at":"2026-05-17T02:08:04.116Z","repository":{"id":340064620,"uuid":"1133208352","full_name":"marklynch/pool-controller-code","owner":"marklynch","description":"ESP32 Code for pool controller for Connect 10","archived":false,"fork":false,"pushed_at":"2026-05-12T05:41:34.000Z","size":499,"stargazers_count":2,"open_issues_count":1,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-12T07:33:28.596Z","etag":null,"topics":["esp32","home-assistant","home-automation","swimming-pool"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marklynch.png","metadata":{"files":{"readme":"README.md","changelog":"changelog.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-01-13T03:08:14.000Z","updated_at":"2026-05-12T05:40:38.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/marklynch/pool-controller-code","commit_stats":null,"previous_names":["marklynch/pool-controller-code"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/marklynch/pool-controller-code","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marklynch%2Fpool-controller-code","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marklynch%2Fpool-controller-code/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marklynch%2Fpool-controller-code/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marklynch%2Fpool-controller-code/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marklynch","download_url":"https://codeload.github.com/marklynch/pool-controller-code/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marklynch%2Fpool-controller-code/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32965880,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-12T23:30:32.555Z","status":"online","status_checked_at":"2026-05-13T02:00:07.132Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["esp32","home-assistant","home-automation","swimming-pool"],"created_at":"2026-03-09T08:04:18.809Z","updated_at":"2026-05-17T02:08:04.086Z","avatar_url":"https://github.com/marklynch.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pool Controller\n\n  [![Build](https://github.com/marklynch/pool-controller-code/actions/workflows/build.yml/badge.svg)](https://github.com/marklynch/pool-controller-code/actions/workflows/build.yml\n  )\n  [![Release](https://img.shields.io/github/v/release/marklynch/pool-controller-code)](https://github.com/marklynch/pool-controller-code/releases)\n  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n  [![ESP-IDF](https://img.shields.io/badge/ESP--IDF-v5.5+-red)](https://github.com/espressif/esp-idf)\n  [![Platform](https://img.shields.io/badge/platform-ESP32--C6-blue)](https://core-electronics.com.au/esp32-c6-mini-development-board-wifi6-bluetooth5.html)\n\nCode to listen on and control a Connect 10 pool controller.  I created it as a learning project and happy to collaborate with people who find it useful.\nThis has been created by listening to the communications on the control bus, and decoding the instructions by trial and error.\n\n## Core components\n1. [Controller code (this repo)](https://github.com/marklynch/pool-controller-code)\n2. [Circuit and PCB design](https://github.com/marklynch/pool-controller-pcb)\n3. ESP32-C6 - It's been designed around the [Waveshare ESP32-C6 Mini Development Board ](https://core-electronics.com.au/esp32-c6-mini-development-board-wifi6-bluetooth5.html)\n4. [Case for Pool Controller](https://github.com/marklynch/pool-controller-case)\n\n**Note** this is **not an official product** and does not come with support or any warranty.  Note it is NOT connected to or supported by Fluidra.\n\n## Current Status\n\nTested as working:\n- Lights work fully — state, colour, zone name, multicolor capability ✅\n- Pool/Spa mode works ✅\n- Temperature set points for pool and spa work ✅\n- Heater on/off works ✅\n- Channel switching working - toggle On/Auto/Off ✅\n- Valves reading and switching working ✅\n- Reading of timers ✅\n- Reading of ORP/PH settings ✅\n- Reading of water temperature ✅\n- Reading config for channels, lights and heater ✅\n- Reading of config/state for Internet Gateway ✅\n- Reading of touchscreen and Internet Gateway firmware versions ✅\n- Auto-requests missing timer and light config when Internet Gateway is absent ✅\n\n\n## Getting Started\n\nThe device has three connectors:\n\n- **2 × RJ12 sockets** — for the pool control bus. Use a standard flat RJ12 cable to connect either socket to your Connect 10 system; this also powers the device. The two sockets are wired in parallel, so the second one can be used to daisy-chain another device (e.g. another controller, gateway, or accessory) on the same bus.\n- **1 × USB-C socket** — for manually flashing firmware and serial monitoring from a computer. It is **not** required for normal operation.\n\nTo bring the device online for the first time:\n\n1. Plug a flat RJ12 cable from the Connect 10 into either RJ12 socket on the device. This both connects it to the pool bus and powers it. (Optional: run a second cable from the other RJ12 socket to daisy-chain the next device on the bus.)\n2. Wait for the LED to turn **purple**, which indicates the device is in provisioning mode and ready for WiFi setup (see [Initial Wifi Provisioning](#initial-wifi-provisioning) below).\n\n## Initial Wifi Provisioning\n\n1. When the LED is **purple**, the device is in provisioning mode.\n2. On your phone, connect to the WiFi network named **`POOL_AABBCC`** (e.g. `POOL_A1B2C3`) — the `AABBCC` suffix is unique to each device. The password is **`poolsetup`**.\n3. In your phone's browser navigate to **http://192.168.4.1** and choose your WiFi network and enter the password.\n4. The device will save the credentials and restart. The LED will turn white then green once connected.\n\nOnce on your network the device is accessible at **`http://poolcontrol-AABBCC.local`** — using the same `AABBCC` suffix as the AP you provisioned through (e.g. `http://poolcontrol-A1B2C3.local`).\n\n**Note:** If the wrong password is entered the device will retry for about 30 seconds then return to provisioning mode.\n\n**Note:** To re-provision, erase the flash (\"Erase Flash Memory from device\" in your IDE) to clear the saved credentials.\n\n## Visual Feedback (LED Status):\n\n### Persistent States (Solid Colors)\n* **Blue** - Startup (brief, during boot)\n* **Purple** - Unconfigured (no WiFi credentials, provisioning mode active)\n* **White** - WiFi connected, waiting for MQTT connection\n* **Green** - Fully operational (WiFi + MQTT connected) ✓\n* **Orange** - MQTT disconnected (WiFi ok, MQTT issue)\n\n### Activity Indicators (Brief Flashes)\n* **Cyan flash** - RJ12 data received (RX)\n* **Magenta flash** - RJ12 data transmitted (TX)\n\n### Boot Flow Examples\n\n**First Boot (No WiFi):**\n1. Blue (startup)\n2. Purple (unconfigured - connect to AP)\n3. Connect to AP → Configure WiFi → Device restarts\n\n**Normal Boot (WiFi Configured):**\n1. Blue (startup)\n2. White (WiFi connected)\n3. Green (MQTT connected) ✓\n\n**MQTT Connection Issue:**\n1. Blue (startup)\n2. White (WiFi connected)\n3. Orange (MQTT failed to connect)\n\n\n## TCP Debug Connection (Port 7373)\n\nThe device exposes a raw TCP server on port 7373 that streams all bus traffic as hex and forwards any bytes you send back onto the bus. It also mirrors the device's log output, so you can monitor activity without a USB cable.\n\nEach device gets a unique mDNS hostname derived from the last 3 bytes of its MAC address:\n`poolcontrol-AABBCC.local` — where `AABBCC` matches the suffix of the provisioning AP name (`POOL_AABBCC`).\n\nFor example, if you provisioned via the `POOL_A1B2C3` network, the device will be accessible at `poolcontrol-A1B2C3.local`.\n\n### Mac / Linux\n\nUse `nc` (netcat), which is installed by default:\n\n```bash\nnc poolcontrol-A1B2C3.local 7373\n```\n\nExample session:\n```\nConnected to pool control bus bridge.\nUART bytes will be shown here in hex.\nBytes you send will be forwarded to the bus.\n\n00\n02 00 50 FF FF 80 00 FD 0F DC 19 0E 01 28 03\n00\n```\n\nTo send a raw command to the bus, type the bytes as a hex string and press Enter:\n```\n02 00 F0 00 50 80 00 39 0F 0E E7 01 00 00 03\n```\n\n### Windows\n\n**Option 1 — PuTTY (recommended)**\n\n1. Download [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) if you don't have it.\n2. Set **Connection type** to **Raw**.\n3. Enter `poolcontrol-A1B2C3.local` as the host and `7373` as the port.\n4. Click **Open**.\n\n**Option 2 — Telnet**\n\nIf the Telnet client is enabled (Control Panel → Programs → Turn Windows features on/off → Telnet Client):\n\n```cmd\ntelnet poolcontrol-A1B2C3.local 7373\n```\n\n**Option 3 — Windows Subsystem for Linux (WSL)**\n\nIf WSL is installed, use `nc` exactly as on Mac/Linux:\n\n```bash\nnc poolcontrol-A1B2C3.local 7373\n```\n\n## Testing Message Decoding\n\nYou can test individual messages against the decoder using the HTTP API endpoint:\n\n```bash\ncurl -X POST http://poolcontrol-A1B2C3.local/api/test_decode \\\n  -d \"02 00 50 FF FF 80 00 38 0F 17 D0 01 02 1A 03\"\n```\n\n**Response:**\n```json\n{\n  \"success\": true,\n  \"decoded\": true,\n  \"length\": 15,\n  \"hex\": \"02 00 50 FF FF 80 00 38 0F 17 D0 01 02 1A 03\",\n  \"message\": \"Check ESP logs for decode details\"\n}\n```\n\n- `decoded: true` - Pattern matched and message was decoded\n- `decoded: false` - Unknown message type\n\n**To see full decode details**, monitor the ESP logs:\n```bash\nidf.py monitor\n```\n\nYou'll see output like:\n```\nI (12345) MSG_DECODER: [Controller -\u003e Broadcast] Lighting zone 1 state - On\n```\n\nThis allows you to quickly test message patterns and verify decoder behavior without needing to send messages to the actual bus.\n\n\n## General architecture\n\n```mermaid\nflowchart TD\n\n    Pool[fa:fa-life-ring Pool Connect 10]\n\n    subgraph ESP32-C6[fa:fa-microchip ESP32-C6 Pool Controller]\n        subgraph Transport[Transport Layer]\n            Bus[Bus Interface\u003cbr/\u003eUART 9600 baud]\n            TCP[TCP Bridge\u003cbr/\u003ePort 7373]\n            RegReq[Register Requester\u003cbr/\u003eAuto-polls when GW absent]\n        end\n\n        subgraph Protocol[Protocol Layer]\n            Decoder[Message Decoder\u003cbr/\u003eRegister Dispatch Table]\n        end\n\n        subgraph State[State Management]\n            PoolState[Pool State\u003cbr/\u003eMutex Protected]\n        end\n\n        subgraph Network[Network Layer]\n            WiFi[WiFi Provisioning\u003cbr/\u003eSoftAP + mDNS]\n        end\n\n        subgraph Application[Application Layer]\n            WebAPI[Web Handlers\u003cbr/\u003eHTTP Endpoints]\n\n            subgraph MQTTSub[MQTT Subsystem]\n                MQTTClient[MQTT Client]\n                MQTTPub[MQTT Publish]\n                MQTTDisc[MQTT Discovery]\n                MQTTCmd[MQTT Commands]\n            end\n        end\n\n        subgraph Status[Status Indication]\n            LED[LED Helper\u003cbr/\u003eWS2812 RGB]\n        end\n\n        Bus \u003c--\u003e TCP\n        Bus --\u003e Decoder\n        Decoder --\u003e PoolState\n        Decoder -.-\u003e|notify on new zone| RegReq\n        RegReq --\u003e PoolState\n        RegReq --\u003e|CMD 0x39 requests| Bus\n        PoolState --\u003e MQTTPub\n        PoolState --\u003e WebAPI\n        MQTTCmd --\u003e PoolState\n        WiFi -.-\u003e WebAPI\n        WiFi -.-\u003e MQTTClient\n        MQTTClient --\u003e MQTTPub\n        MQTTClient --\u003e MQTTDisc\n        MQTTClient --\u003e MQTTCmd\n        MQTTPub --\u003e MQTTSub\n        MQTTDisc --\u003e MQTTSub\n        MQTTCmd --\u003e MQTTSub\n        PoolState -.-\u003e LED\n        WiFi -.-\u003e LED\n    end\n\n    Clients[Network Clients\u003cbr/\u003enc, telnet, custom]\n    Browser[Web Browser]\n    HA[Home Assistant]\n\n    Pool \u003c--\u003e|RJ12 Serial Bus| Bus\n    TCP \u003c--\u003e|TCP/IP Port 7373| Clients\n    WebAPI \u003c--\u003e|HTTP Port 80\u003cbr/\u003epoolcontrol-AABBCC.local| Browser\n    MQTTSub \u003c--\u003e|MQTT over WiFi| HA\n```\n\nThe system consists of an ESP32 C6 module that can be daisy chained into an existing connect 10 system via a RJ12 connection.\n\nIt sets up a WiFi AP called `POOL_AABBCC` (where `AABBCC` is the last 3 bytes of the device's MAC address). Connecting to that AP and navigating to `192.168.4.1` opens the provisioning page. Once configured and on your network, the device is accessible at `poolcontrol-AABBCC.local` using the same suffix.\n\nIt uses MQTT to connect and publish information and receive information from Home Assistant.\n\n## Building and Flashing\n\nThis project uses ESP-IDF v5.5+. See `CLAUDE.md` for build commands and architecture details.\n\n```bash\nidf.py build          # Build the project\nidf.py flash monitor  # Flash to device and monitor output\n```\n\n## Releases\n\nReleases are produced by a GitHub Actions workflow (`.github/workflows/build.yml`) that fires on any tag matching `v*`. To cut a release:\n\n```bash\ngit tag -a v1.0.0 -m \"Release v1.0.0\"\ngit push origin v1.0.0\n```\n\nThe workflow will:\n\n1. Build the firmware with `PROJECT_VER` set to the tag name (embedded into the binary and visible via the device's `/status` page).\n2. Create a draft GitHub Release with auto-generated notes.\n3. Attach two assets:\n   - `pool-controller-update-v1.0.0.bin` — app-only binary, for the existing `/update` OTA flow.\n   - `pool-controller-full-v1.0.0.bin` — merged bootloader + partition table + otadata + app, for first-time flashing via `esptool.py --chip esp32c6 write_flash 0x0 pool-controller-full-v1.0.0.bin`.\n4. Publish the draft.\n\nThe published release appears at `https://github.com/marklynch/pool-controller-code/releases/tag/v1.0.0`.\n\nTo re-test the workflow without cutting a real release, run it manually from the Actions tab (Build \u0026 Release → Run workflow). Manual runs build the firmware and upload it as a workflow artifact but do not create a GitHub Release.\n\n## Documentation\n\n### [PROTOCOL.md](PROTOCOL.md) — Bus Protocol Reference\n\nDocuments the proprietary serial protocol used by the Connect 10, reverse-engineered by sniffing bus traffic. Covers:\n\n- **Message framing** — `START (0x02) | SRC | DST | CTRL | CMD | DATA | CHECKSUM | END (0x03)`\n- **Device addresses** — Touch screen (`0x0050`), controller (`0x006F`), chlorinator (`0x0090`), internet gateway (`0x00F0`)\n- **30+ decoded message types** — temperatures, channel states, lighting zones (state, colour, name, multicolor capability), chlorinator pH/ORP, controller clock, firmware versions, gateway network status, and more\n- **Register system** — A unified register/slot dispatch mechanism used for channel names, types, lighting colors, and labels\n- **Control commands** — How to toggle channels, set temperature setpoints, control lighting zones, switch pool/spa mode, and control the heater (all by impersonating the internet gateway address `0x00F0`)\n- **Checksum algorithm** and message validation rules\n\n### [OTA_UPDATE.md](OTA_UPDATE.md) — Over-The-Air Firmware Updates\n\nDescribes the web-based OTA update system. Covers:\n\n- **How to update** — Build the `.bin`, navigate to `http://\u003cdevice-ip\u003e/update`, upload via the web form\n- **Dual-partition layout** — Updates alternate between `ota_0` and `ota_1`, with automatic rollback if the new firmware fails to boot\n- **Safety** — Image validation before write, boot confirmation required by new firmware, rollback after 3 failed boots\n- **Version information** — Version string generated from `git describe` (e.g. `v1.0.0-5-g870d65b`)\n- **Security notes** — No authentication on `/update` currently; see the doc for recommended production hardening\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarklynch%2Fpool-controller-code","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarklynch%2Fpool-controller-code","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarklynch%2Fpool-controller-code/lists"}