Smart hot tub with automated temperature control and LED lighting overlooking scenic view
The finished installation: water temperature maintained automatically, RGB lighting adjustable from a smartphone, with full remote monitoring for the property manager.

The Control Problem

A hot tub or spa imposes a set of control requirements that push beyond what a simple timer or thermostat can handle. The heater must never run without confirmed water flow, or it will burn out the element within minutes. The filter schedule must pause automatically when the jets or blower are running, because both loads together will exceed the panel's single-phase supply rating. RGB mood lighting needs smooth PWM dimming, not relay switching. Modern installations also demand connectivity: the property manager must be able to monitor status, adjust setpoints, and troubleshoot remotely, without a site visit for every routine change.

This reference design uses the EQSP32C controller. It demonstrates how a single 16-channel controller handles all outputs, all inputs, all logic, and all connectivity, while the property manager accesses the system through a smartphone app with no native app installation required.

I/O Inventory

This installation uses 9 of the EQSP32C's 16 software-configurable channels, leaving 7 free for future expansion. The complete channel assignment is shown below.

Ch. Signal Mode Type Notes
1 Water temperature TIN Temp. input NTC thermistor, β=3950, Rref=10 kΩ @ 25°C. Library returns °C; application averages 256 samples for noise rejection.
2 Flow switch SWT Digital in Debounced switch input. Immediate heater cutout if open; also published as status telemetry.
3 Jets panel button SWT Digital in Each rising edge cycles jet state: off → jet 1 → jet 2 → both → off.
4 Lights panel button SWT Digital in Debounced toggle for on/off. Color set via MQTT only.
5 Bubbles panel button SWT Digital in Debounced toggle for blower pump.
7 Filter pump relay RELAY Relay out Hold power 350 ms, derate to 250 ms. Disabled while jets or blower run.
9 RGB red channel POUT PWM out Three POUT channels at 1 kHz. Payload is a single 0xRRGGBB integer via MQTT; firmware splits into per-channel PWM values (0–1000 scale).
10 RGB green channel POUT PWM out
11 RGB blue channel POUT PWM out
12 Jet pump 1 relay RELAY Relay out Hold 350 ms / derate 250 ms. Addressable individually or as a pair via MQTT.
13 Jet pump 2 relay RELAY Relay out Hold 350 ms / derate 250 ms.
14 Blower/bubbles relay RELAY Relay out Hold 350 ms / derate 250 ms. Triggers filter load-shedding when active.
16 Heater relay RELAY Relay out Hold 350 ms / derate 500 ms. Requires both flow confirmed and system power on.

7 channels free. Channels 6, 8, 15, and others remain unassigned. Candidates for expansion: UV disinfection enable, chemical dosing pump, ozone generator, towel rail heater, or a second temperature zone.

Panel Architecture and Wiring

The EQSP32C operates at 24 V DC and its output channels source up to 1 A each, which is sufficient to drive 24 V relay coils directly. The relay contacts then switch the 230 V AC loads: jet pumps, blower, filter pump, and heater. This is the same two-stage architecture used by most professional panel builders: the controller never sees AC voltage on its output terminals.

The heater relay is DPST (double-pole single-throw), breaking both the live and neutral conductors. This is a code requirement in wet-environment installations across most jurisdictions, and it prevents leakage current from energising the heating element when the relay is commanded off. All other loads use SPST relays.

A ground-fault circuit interrupter (GFCI/RCD) is fitted upstream of all 230 V AC loads. This device is independent of the EQSP32 and provides the primary safety layer against ground faults in the wet zone. The EQSP32's flow-switch interlock is a secondary, application-level protection for the heater element specifically.

EQSP32 hot tub wiring diagram showing relay outputs, flow switch, NTC thermistor, RGB LED, and panel buttons
Wiring diagram for the hot tub control panel. Each EQSP32 output drives a 24 V relay coil; the relay contacts switch the 230 V AC loads. The heater relay is DPST, breaking both conductors.

Relay derate pattern

The EQSP32 RELAY mode applies full drive current for a configurable hold period, then reduces to a lower sustain current. This reduces relay coil heating significantly in installations where relays may remain energised for hours at a time. The fill valve solenoid uses a shorter hold time (50 ms vs 350 ms for the pumps) because solenoid valves pull in faster than mechanical relay armatures and benefit from a more aggressive derate.

Temperature Control and Heater Interlock

The water temperature is read by a 10 kΩ NTC thermistor connected in a voltage divider configuration to the EQSP32 TIN input on channel 1. The EQSP32 library handles the β-model conversion internally (β=3950, Rref=10 kΩ at 25°C), returning a calibrated temperature in tenths of a degree Celsius. The firmware maintains a 256-sample ring buffer of raw readings and averages them before use, providing effective noise rejection without phase lag on slow thermal processes.

Thermostat logic

The setpoint is configurable via MQTT in the range 0–45°C (default 20°C). The controller uses a symmetric ±1°C hysteresis band to prevent relay chatter near the setpoint:

ConditionAction
Water temp < setpoint −1°C AND flow confirmed Heater ON
Water temp > setpoint +1°C AND flow confirmed Heater OFF
Within band (setpoint −1 to +1°C) Heater state latched (no change)
Flow switch open (no water flow) Heater OFF immediately, every control cycle
System power off Heater OFF, jets and filter off
Temperature reading out of range (<0 or >60°C) Reading set to error sentinel (99), heater off

No delay on flow loss. The heater cutout on flow-switch open is evaluated every control loop iteration (10 ms). There is intentionally no debounce delay on this path: if water flow stops for any reason, the heater de-energises within one scan cycle. Open-circuit (broken thermistor wire) and short-circuit conditions are also detected by the TIN driver and treated as faults.

Filter Scheduling and Load Management

The filter pump runs on a recurring one-hour cycle. Within each hour the filter runs for a configurable period of 0 to 60 minutes, then rests for the remainder. Setting it to zero disables scheduled filtering entirely. The cycle restarts automatically at power-up. Whenever the jets or blower are active, the filter switches off and its timer pauses rather than resets, so the filter always completes its scheduled runtime regardless of interruptions. This prevents the jets and filter from simultaneously loading the single-phase supply, which would exceed the panel's rating.

AI Programming: a Natural Fit for the Control Engineer

Look at the control requirements for this installation: a thermostat with hysteresis, a flow-switch safety interlock that overrides everything else, a filter cycle that yields to the jets, a swimjet timeout. Every one of these is a condition with a consequence — precisely the kind of structured, causal reasoning a PLC programmer does instinctively. Writing it in ladder is natural. What is less natural is translating that logic into C++ firmware, managing timers in code, wiring up MQTT topics, and building the smartphone interface on top. That work traditionally belongs to a software developer, and the hand-off between disciplines introduces delay, misinterpretation, and rework.

With the EQ-AI Coding Assistant, that hand-off disappears. The entire hot tub firmware — thermostat logic, flow interlock, filter scheduling, jets timeout, MQTT publishing — was generated by describing each requirement in plain English, in the same terms a controls engineer would use to brief a colleague. EQ-AI produced working C++ code. The engineer reviewed it against logic they already understood. No software developer was involved.

From Ladder Logic to AI Prompt

A PLC programmer approaching the heater interlock would instinctively reach for a set/reset latch with a safety override rung. The logic is straightforward to express in ladder: three rungs, each with a clear condition and a clear action. The EQSP32 firmware implements exactly the same logic, and it was generated by describing that logic to EQ-AI in plain English rather than drawing rungs on a screen.

The comparison below shows the same requirement expressed in both forms. The ladder is how a PLC programmer thinks about the problem. The prompt is what produces the working firmware.

Heater control logic — IEC 61131-3
Left Rail (Power)                                             Right Rail (Neutral)
     |                                                                   |
     |== RUNG 1: Latch Heater ON ========================================|
     |                                                                   |
     |    System_Power      Water_Flow     Temp < (SP-1°C)    Heater_ON  |
     |--------| |--------------| |--------------| |--------------(S)-----|
     |                                                                   |
     |                                                                   |
     |== RUNG 2: Normal Cut-off =========================================|
     |                                                                   |
     |    Temp > (SP+1°C)                                    Heater_OFF  |
     |--------| |------------------------------------------------(R)-----|
     |                                                                   |
     |                                                                   |
     |== RUNG 3: Safety Interlock & Power-Down Override =================|
     |                                                                   |
     |     Water_Flow                                        Heater_OFF  |
     |--------|/|------------+-----------------------------------(R)-----|
     |                       |                                           |
     |    System_Power       |                                           |
     |--------|/|------------|                                           |
     |                                                                   |
EQ-AI prompt fragment
Control a heater relay on output channel 16.

Turn the heater ON when all three conditions are true:
- system power is enabled
- the flow switch on channel 2 is closed
- water temperature is more than 1°C below the desired setpoint

Turn the heater OFF when temperature rises more than 1°C above the setpoint.

If the flow switch opens at any time, turn the heater off immediately with no debounce delay. This is a safety interlock — evaluate it every control scan, before all other heater logic.

EQ-AI generated the firmware implementation from this description. The output correctly handled the hysteresis band, the set/reset latch structure, and the priority ordering of the flow-loss safety rung over the temperature conditions — without the engineer specifying the implementation mechanism. For a PLC programmer, the translation is natural: wherever you would draw a rung, you write a sentence.

Connecting to the Internet

Remote access is no longer optional. Property managers, maintenance teams, and end users expect to check status and make adjustments from a phone, whether they are on-site or across the country. Connectivity also enables proactive service: a technician can verify water temperature and heater state before driving to a site, log historical data to identify recurring faults, and push configuration changes without a physical visit. Any product that lacks this capability is increasingly difficult to justify against alternatives that include it as standard.

MQTT is the protocol that makes this possible. The EQSP32 publishes the state of every element — heater, filter pump, jets, blower, and lights — to a cloud broker in real time, and accepts commands on the same connection. A smartphone app subscribes to those topics and gives the property manager full visibility and control from anywhere, with no port-forwarding or VPN required. The broker also retains the last known value of every topic, so the app displays current status the moment it opens, without waiting for the controller to publish again.

How the Webapp Was Built: an Agentic Development Workflow

The webapp was not designed screen by screen in a visual editor or assembled from a component library. The development process started with a single structured prompt given to a Claude AI agent running inside the Cursor IDE. The prompt described the desired interface in plain terms (four screens, a temperature gauge, rotary dials, a colour wheel, a bottom nav bar) and pointed the agent at two sources of truth: the EQSP32 firmware source file and the MQTT broker credentials. The agent read the firmware, identified every entity registration call, reconstructed the complete topic list from those registrations, connected to the broker to verify retained values, and then wrote the entire PWA (HTML, CSS, JavaScript, service worker, and manifest) as a single coherent codebase. The first version ran in a browser and communicated correctly with the physical controller in under an hour.

Prompt used: Cursor + Claude
I have an EQSP32 IoT controller running the firmware in LimniJacuzziMQTT.ino. Read the source file and extract every MQTT topic the device publishes and subscribes to — they follow the pattern state/EQ_<deviceId>/<entity_name> and cmd/EQ_<deviceId>/<entity_name>. The device ID is LimniTub. The broker is EMQX Cloud at p6c2051c.ala.eu-central-1.emqxsl.com, port 8084 over WebSocket Secure, with the credentials I will provide. Build a mobile-first progressive web app in vanilla HTML/CSS/JS (no framework, MQTT.js via CDN) that connects to this broker and provides full control of the hot tub. The UI should have a dark industrial theme and four screens accessible from a bottom nav bar: a main screen with an SVG arc temperature gauge, status chips for heater/filter/flow, and buttons for jets 1, jets 2, bubbles, lights, and a master power toggle; a heater screen with a rotary canvas dial for the temperature setpoint; a lights screen with a canvas HSV colour wheel that sends a single 0xRRGGBB integer to the colour topic; and a filter screen with a rotary dial for filter run duration.

Every button should show an orange pending state immediately on tap, clearing only when the corresponding status topic arrives from the controller. Track controller liveness separately from broker connectivity: if no state/EQ_LimniTub/ message arrives for 15 seconds, mark the device offline even if the WebSocket is still connected. Auto-reconnect to the broker every 4 seconds on disconnect. Include a PWA manifest and service worker that cache the app shell for offline loading. The app should be installable via Add to Home Screen on iOS and Android.

For a controls engineer, the closest analogy is asking a colleague to read your PLC program and build you a SCADA screen for it. Normally that exchange takes days: you document the tag database, hand it to the SCADA developer, answer questions about what each tag means, review a first draft, and iterate. Here, the agent performed all of that autonomously. It did not need a tag list because it could read the source of truth directly. It did not need to ask what st_water_flow means because it could see the flow-switch interlock logic in the firmware and infer the correct label, colour coding, and behaviour for the corresponding indicator. The mapping from controller state to interface element was derived, not specified.

What this means in practice is that the HMI and the firmware stayed in sync without any manual coordination effort. When a new topic was added to the firmware, the agent could be asked to add the corresponding indicator to the right screen with another short prompt. There was no tag database to update, no separate configuration file to edit, no HMI software licence to open. The webapp is plain text, versioned in a git repository alongside the firmware, readable and modifiable by any engineer who can open a text editor. That combination of AI-generated speed and full human auditability is what makes the agentic development workflow genuinely different from either hand-coding a UI or using a proprietary IoT platform.

Webapp as Smartphone Interface

The control interface is a progressive web app (PWA): a static HTML/CSS/JavaScript application served from a web server and accessed through the smartphone's browser. Once visited, the user taps "Add to Home Screen" and the app installs like a native application, with its own icon, launch screen, and full-screen display mode. There is no App Store review process, no user account, and no subscription.

Limni Tub progressive web app running on smartphone showing temperature gauge, heater status, and actuator controls
The webapp running on a smartphone in standalone mode. The main screen shows the current and target water temperature, status indicators for heater, filter, and flow, and the four primary actuator controls.

Why a webapp instead of a native or proprietary app

A number of commercial IoT platforms offer pre-built mobile applications that can be connected to an MQTT device. These platforms are convenient at the prototype stage, but they introduce dependencies that are problematic in a professional or commercial installation. The webapp approach used here avoids all of them:

No App Store review process

Publishing a native app to Google Play or the Apple App Store involves account fees, compliance reviews, binary submissions, and release cycles that can take days to weeks. A webapp is deployed by copying files to a web server. Updates are live the moment the file is saved: no review, no waiting, no version management across stores.

Installable on any device

PWA installation via "Add to Home Screen" works on iOS Safari, Android Chrome, and desktop browsers. One codebase covers all platforms with no platform-specific builds required.

Webapp screen layout

The app has four screens, each accessible from a bottom navigation bar:

The app monitors controller liveness independently of the MQTT broker connection. If no status message arrives from the controller for 15 seconds, the interface shows the controller as offline even if the broker WebSocket is still connected. Reconnection to the broker is automatic with a 4-second retry interval.

Bill of Materials

EQSP32 hot tub retrofit kit: controller, relay board, power supply, thermistor, and flow switch
The core retrofit kit: EQSP32C controller, relay board, 24 V PSU, NTC thermistor probe, and flow switch. Air buttons and the GFCI breaker are sourced locally to match the spa model.
QtyItemDescriptionNotes
1 EQSP32C 16-channel industrial IoT controller, DIN-rail, WiFi/BLE/Ethernet Erqos Technologies
1 24 V DC PSU 220 V AC to 24 V DC, 2.5 A (Meanwell HDR-60-24 or equivalent) Powers EQSP32C and relay coils
1 GFCI/RCD 30 mA ground-fault circuit interrupter, 2-pole or 4-pole Upstream of all wet-zone 230 V AC loads
3 Panel air buttons Pneumatic (air-pulse) switches for jets, lights, bubbles; electrically isolated Spa-rated, wet-zone safe
5 SPST relays (24 V coil) For filter pump, jet pump 1, jet pump 2, blower, drain valve Rated for motor load; e.g. Finder 40.52
1 DPST relay (24 V coil) For heater (breaks both live and neutral) Required for wet-environment heater isolation
1 NTC thermistor 10 kΩ @ 25°C, β=3950 (e.g. NTCLE413E2103F106A) Waterproof probe, immersed in water flow path
1 Water flow switch Paddle-type or differential pressure flow switch, SPST N/O Heater safety interlock

Why EQSP32 for This Application

16 software-configurable I/O channels

Every channel can be independently set as RELAY, POUT (PWM), TIN (thermistor), SWT (debounced switch), or AIN (analog). No fixed I/O allocation, no need for separate relay modules or PWM drivers for the RGB lighting.

Solid-state outputs drive relay coils directly

The EQSP32C's 1 A solid-state outputs can sink 24 V relay coils without intermediate transistor stages. The same outputs that switch small 24 V coils can also drive solenoid valves, LED drivers, or indicator lamps, with no external drive circuitry.

Native MQTT over TLS

MQTT with TLS is handled by the EQSP32 library, including certificate management and automatic reconnection. The firmware sketch contains only application logic. No MQTT library integration, no TLS handshake code, no reconnect loop to write.

Provisioning without code changes

WiFi credentials, MQTT broker address, and device identity are configured over Bluetooth from the EQSP32 smartphone provisioning app. No firmware recompilation is needed when installing the controller at a new site or when the WiFi network changes.

EQ-AI firmware generation

The application firmware was generated almost entirely by EQ-AI, the EQSP32-specific AI coding assistant, from a plain-language description of the required behaviour. The integrator reviewed, tested, and adjusted the output. Total development time from specification to running firmware: under two days.

Ready to build a similar system?

The full source code for the EQSP32 firmware and the webapp is available on request. Our engineering team can also design and deliver a complete integration for your application.

Get started with EQSP32