- Python 80.1%
- HTML 17.7%
- Shell 1.3%
- Dockerfile 0.9%
- Remove unconditional print in LIVE packet handler (logged every ~2s) - Reset _reconnect_attempts to 0 on successful ONLINE connection so backoff always starts from 1s after recovery - Add deploy.sh: git pull + docker-compose down + build + up -d - Improve HEALTHCHECK exit logic and bump start-period to 15s |
||
|---|---|---|
| .vscode | ||
| API | ||
| docs | ||
| misc | ||
| proto | ||
| tools | ||
| .dockerignore | ||
| .gitignore | ||
| bytebuffer.py | ||
| CHANGELOG.md | ||
| crc_diagnostic.py | ||
| deploy.sh | ||
| Dockerfile | ||
| HA_auto_mqtt.py | ||
| levven_packet.py | ||
| License.md | ||
| README.md | ||
| requirements.txt | ||
| SETUP.txt | ||
| SpaBoii.code-workspace | ||
| SpaBoii.py | ||
| TODO.md | ||
SPABoii — No Cloud Access for ARCTIC SPA (Levven) Systems
Disclaimer: USE AT OWN RISK. I TAKE NO RESPONSIBILITY FOR ANY ERRORS THIS MIGHT CAUSE.
Background
Thread with progress and history links: https://community.home-assistant.io/t/arctic-spa-no-cloud-api-spa-boii/782040
Overview
SPABoii communicates directly with Arctic SPA / Levven hot tub controllers over TCP (port 65534), bypassing the manufacturer's cloud entirely. It exposes spa controls and sensors as Home Assistant entities via MQTT auto-discovery.
What works:
- Temperature setpoint control (°F)
- Current temperature monitoring
- Pump 1 (OFF / LOW / HIGH)
- Pump 2, Pump 3 (OFF / ON → HIGH only)
- Heater 1, Heater 2 status
- Blower 1, Blower 2
- Lights
- Boost / Onzen (ON/OFF)
- Cl Range (Low / Mid / High) — sets ORP limits
- pH, ORP sensor readings
- Error status monitoring (no_flow, heater_over_temp, etc.)
- Device info (firmware, GUID) via MQTT
- Device capabilities via MQTT
Requirements
- Python 3.10+
- Docker / Docker Compose
- Home Assistant with MQTT broker (Mosquitto add-on)
- SPA reachable on local network (TCP port 65534)
Setup
1. Clone the repository
git clone <repo-url> SpaBoii
cd SpaBoii
2. Create settings.yaml
host: YOUR_MQTT_BROKER_HOST
username: MQTT_USERNAME
password: MQTT_PASSWORD
spa_ip: YOUR_SPA_IP_ADDRESS
3. Build and run
docker-compose up -d --build
4. Verify
docker logs -f spaboii-ha-mqtt
You should see:
Spa IP: 192.168.x.xPacket: 0x06 - Authenticate (size=100)— device info receivedPacket: 0x03 - Configuration (size=42)— capabilities receivedPacket: 0x00 - Live (size=56)— real-time sensor data
Home Assistant Entities
After startup, the following entities appear in HA via MQTT auto-discovery:
| Entity | Type | Description |
|---|---|---|
| SPABoii.SetPoint | Number | Target temperature (°F) |
| SPABoii.CurrentTemp | Sensor | Current water temperature |
| SPABoii.Pump1 | Select | OFF / LOW / HIGH |
| SPABoii.Pump2 | Switch | OFF / ON (maps to HIGH) |
| SPABoii.Pump3 | Switch | OFF / ON (maps to HIGH) |
| SPABoii.Heater1 | Binary Sensor | Heating / Off |
| SPABoii.Heater2 | Binary Sensor | Heating / Off |
| SPABoii.Blower1 | Switch | OFF / ON |
| SPABoii.Blower2 | Switch | OFF / ON |
| SPABoii.Lights | Switch | OFF / ON |
| SPABoii.Boost | Button | Onzen / Boost activation |
| SPABoii.ClRange | Select | Low / Mid / High (ORP range) |
| SPABoii.pH | Sensor | pH level |
| SPABoii.ORP | Sensor | ORP level (mV) |
| SPABoii.ErrorStatus | Sensor | Error flags (comma-separated) |
| SPABoii.ConnectionStatus | Binary Sensor | Connected / Disconnected |
| SPABoii.FilterStatus | Sensor | Filter pump status |
| SPABoii.OzoneStatus | Sensor | Ozone generator status |
| SPABoii.CloseService | Button | Restart monitoring service |
Architecture
┌─────────────────┐ ┌──────────────────┐ ┌──────────┐
│ Home Assistant │◄──────►│ MQTT Broker │◄──────►│ Container│
│ (Auto-Discover)│ 1883 │ (Mosquitto) │ 1883 │ SPABoii │
└─────────────────┘ └──────────────────┘ └────┬─────┘
│ 65534
▼
┌──────────┐
│ SPA │
│ (Levven) │
└──────────┘
- SPABoii.py — Main process: socket I/O, packet parsing, MQTT callbacks
- HA_auto_mqtt.py — MQTT entity definitions, Home Assistant auto-discovery
- levven_packet.py — Packet serialization/deserialization (header + CRC)
- proto/ — Protocol buffer definitions (.proto + compiled _pb2.py)
- tools/spa_debug_tool/ — Interactive web debugger (Flask)
Protocol Details
The SPA uses a proprietary TCP protocol on port 65534:
- Magic bytes:
0xABAD1D3A(4 bytes) - CRC-32: Sent as
0x00000000for outgoing commands (PCAP validated) - Payload: Protobuf-encoded (see
proto/for definitions) - Heartbeat: PING packets every ~5 seconds
For full protocol documentation, see:
docs/PROTOCOL_ANALYSIS.md— PCAP reverse engineeringdocs/COMPARISON.md— Original vs improved code, CRC research
Deployment
Push to host
git push origin main
Pull and rebuild on host
ssh tech@<host-ip>
cd /home/tech/SpaBoii
git pull origin main
docker-compose down
docker-compose up -d --build
Known Limitations
- CRC validation disabled: SPA uses a non-standard CRC variant for data
packets. PING packets use standard CRC-32. See
docs/COMPARISON.md. - Onzen presets hardcoded: Cl Range uses fixed ORP values from PCAP.
- Connection status: Binary sensor (ON/OFF). No quality metrics.
- Raw socket capture: Requires host-level
tcpdump(container is non-root).
Changelog
See CHANGELOG.md for detailed change history.
Contact / Issues
Home Assistant Community Thread: https://community.home-assistant.io/t/arctic-spa-no-cloud-api-spa-boii/782040
REMEMBER: This is boilerplate code, meant to provide a basic codebase for communication with Arctic SPA's. SO USE AT OWN RISK, I TAKE NO RESPONSIBILITY FOR ANY ERRORS THIS MIGHT CAUSE.