Skip to content

A little thing to manage balboa spas. If you have a balboa controlled wifi spa, this might help. I developed this for linux , but it should work fine under wsl and mac?

License

Notifications You must be signed in to change notification settings

cdibona/balboapal

Repository files navigation

Balboa Spa Control

A Python library and toolkit for controlling Balboa hot tubs/spas via their WiFi module.

This project wraps the excellent pybalboa library with a cleaner API, CLI tool, REST API server, and a secure web interface with OAuth authentication.

Features

  • Python Library: Clean async interface for programmatic control
  • CLI Tool: Command-line control with rich terminal output
  • REST API: Flask-based HTTP API for integration with other systems
  • Web Interface: Modern, mobile-friendly web UI with OAuth authentication
  • Discord Integration: Log commands and periodic status updates to Discord
  • Production Ready: systemd services, nginx config, Let's Encrypt SSL

Requirements

  • Python 3.10+
  • A Balboa spa with WiFi module (bwa™ Wi-Fi Module 50350)
  • The spa must be connected to your local network

Quick Start (Local Development)

# Clone or copy the project
cd balboapal

# Create virtual environment
python -m venv .venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Install dependencies
pip install -r requirements.txt

# Copy and configure environment
cp .env.template .env
# Edit .env with your spa's IP address

# Test connection
python cli.py status

Configuration

Edit .env file with your settings:

# IP address of your Balboa spa WiFi module
SPA_HOST=192.168.0.167

# Flask/Web settings
FLASK_HOST=0.0.0.0
FLASK_PORT=5055
FLASK_DEBUG=false
SECRET_KEY=your-random-secret-key-here

# Domain for the web interface
SITE_DOMAIN=spa.dibona.com

# Google OAuth 2.0 credentials
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret

# Permitted users (comma-separated email addresses)
PERMITTED_USERS=user1@gmail.com,user2@gmail.com

# Discord webhook for logging (optional)
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...

# Status report interval in minutes
STATUS_REPORT_INTERVAL=30

Generate a secure SECRET_KEY:

python3 -c "import secrets; print(secrets.token_hex(32))"

OAuth Provider Setup

The web interface uses OAuth for authentication. You can use Google, GitHub, or both.

Google OAuth Setup

  1. Go to Google Cloud Console

  2. Enable the API

    • Go to "APIs & Services" → "Library"
    • Search for "Google+ API" and enable it (or "Google Identity" for newer projects)
  3. Configure OAuth Consent Screen

    • Go to "APIs & Services" → "OAuth consent screen"
    • Choose "External" (or "Internal" if using Google Workspace)
    • Fill in required fields:
      • App name: Spa Control
      • User support email: your email
      • Developer contact: your email
    • Add scopes: email, profile, openid
    • Add test users if in testing mode
  4. Create OAuth Credentials

    • Go to "APIs & Services" → "Credentials"
    • Click "Create Credentials" → "OAuth 2.0 Client IDs"
    • Application type: Web application
    • Name: Spa Control Web
    • Authorized JavaScript origins:
      https://spa.dibona.com
      
    • Authorized redirect URIs:
      https://spa.dibona.com/authorize
      
    • Click "Create"
  5. Copy Credentials to .env

    GOOGLE_CLIENT_ID=123456789-abcdefg.apps.googleusercontent.com
    GOOGLE_CLIENT_SECRET=GOCSPX-your-secret-here

GitHub OAuth Setup

  1. Go to GitHub Developer Settings

  2. Register the Application

    • Application name: Spa Control
    • Homepage URL: https://spa.dibona.com
    • Authorization callback URL: https://spa.dibona.com/authorize/github
    • Click "Register application"
  3. Get Credentials

    • Copy the Client ID
    • Click "Generate a new client secret"
    • Copy the Client Secret (shown only once!)
  4. Add to .env

    GITHUB_CLIENT_ID=your-github-client-id
    GITHUB_CLIENT_SECRET=your-github-client-secret
  5. Update webapp.py (if using GitHub OAuth)

    Add to the OAuth setup section:

    github = oauth.register(
        name="github",
        client_id=os.getenv("GITHUB_CLIENT_ID"),
        client_secret=os.getenv("GITHUB_CLIENT_SECRET"),
        access_token_url="https://github.com/login/oauth/access_token",
        authorize_url="https://github.com/login/oauth/authorize",
        api_base_url="https://api.github.com/",
        client_kwargs={"scope": "user:email"},
    )

Discord OAuth Setup (Alternative to Google/GitHub)

  1. Go to Discord Developer Portal

  2. Configure OAuth2

    • Go to "OAuth2" → "General"
    • Add redirect: https://spa.dibona.com/authorize/discord
    • Copy Client ID and Client Secret
  3. Add to .env

    DISCORD_CLIENT_ID=your-discord-client-id
    DISCORD_CLIENT_SECRET=your-discord-client-secret

Discord Webhook Setup (for Logging)

The Discord webhook is separate from OAuth - it's used to log spa commands and status updates to a Discord channel.

  1. Open Discord and go to your server

  2. Create a Webhook

    • Click on the server name → "Server Settings"
    • Go to "Integrations" → "Webhooks"
    • Click "New Webhook"
    • Name: Spa Control
    • Channel: Select where you want notifications
    • Copy the Webhook URL
  3. Add to .env

    DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/1234567890/abcdefghijklmnop
  4. What Gets Logged

    • User logins/logouts
    • All spa control commands (with user attribution)
    • Periodic status reports (every 30 minutes by default)
    • Errors and service start/stop events

Production Deployment

Quick Deploy

# Run the installation script
sudo ./deploy/install.sh

# Configure the application
sudo nano /opt/balboapal/.env

# Set up SSL certificate
sudo ./deploy/setup-ssl.sh spa.dibona.com your@email.com

# Start services
sudo systemctl start spa-control spa-status-reporter

# Enable on boot
sudo systemctl enable spa-control spa-status-reporter

Manual Deployment Steps

  1. Install System Dependencies

    sudo apt update
    sudo apt install -y python3 python3-venv python3-pip nginx certbot python3-certbot-nginx
  2. Copy Application Files

    sudo mkdir -p /opt/balboapal
    sudo cp *.py requirements.txt /opt/balboapal/
    sudo cp .env /opt/balboapal/
  3. Create Virtual Environment

    cd /opt/balboapal
    sudo python3 -m venv .venv
    sudo .venv/bin/pip install -r requirements.txt
  4. Set Permissions

    sudo chown -R www-data:www-data /opt/balboapal
    sudo chmod 600 /opt/balboapal/.env
    sudo mkdir -p /var/log/spa-control
    sudo chown www-data:www-data /var/log/spa-control
  5. Install systemd Services

    sudo cp deploy/spa-control.service /etc/systemd/system/
    sudo cp deploy/spa-status-reporter.service /etc/systemd/system/
    sudo systemctl daemon-reload
  6. Configure nginx

    sudo cp deploy/nginx-spa.conf /etc/nginx/sites-available/spa.dibona.com
    sudo ln -s /etc/nginx/sites-available/spa.dibona.com /etc/nginx/sites-enabled/
    sudo nginx -t
    sudo systemctl reload nginx
  7. Obtain SSL Certificate

    sudo certbot --nginx -d spa.dibona.com
  8. Start Services

    sudo systemctl start spa-control spa-status-reporter
    sudo systemctl enable spa-control spa-status-reporter

Service Management

# Check status
sudo systemctl status spa-control
sudo systemctl status spa-status-reporter

# View logs
sudo journalctl -u spa-control -f
sudo journalctl -u spa-status-reporter -f

# Restart after config changes
sudo systemctl restart spa-control spa-status-reporter

# Stop services
sudo systemctl stop spa-control spa-status-reporter

SSL Certificate Renewal

Certbot automatically renews certificates. Verify with:

# Check renewal timer
sudo systemctl status certbot.timer

# Test renewal
sudo certbot renew --dry-run

# Check certificate expiry
echo | openssl s_client -connect spa.dibona.com:443 2>/dev/null | openssl x509 -noout -dates

CLI Tool

# Get spa status
python cli.py status

# Set temperature
python cli.py set-temp 102

# Control pumps (0-indexed: pump 0 = Pump 1)
python cli.py pump 0 toggle      # Toggle pump 1
python cli.py pump 0 high        # Set pump 1 to high
python cli.py pump 1 off         # Turn off pump 2

# Toggle lights
python cli.py light              # Toggle light 1
python cli.py light --index 1    # Toggle light 2

# Toggle blower
python cli.py blower

# Set heat mode
python cli.py heat-mode ready    # Ready mode (maintains temp)
python cli.py heat-mode rest     # Rest mode (heats during filtration only)

# Set temperature range
python cli.py temp-range high    # High range (up to 104°F)
python cli.py temp-range low     # Low range (economy mode)

# Specify spa host directly
python cli.py --host 192.168.1.50 status

REST API

Start the API server:

python api.py

Or with gunicorn for production:

gunicorn -w 1 -b 0.0.0.0:5000 api:app

API Endpoints:

# Get status
curl http://localhost:5000/api/status

# Set temperature
curl -X POST http://localhost:5000/api/temperature \
  -H "Content-Type: application/json" \
  -d '{"temp": 102}'

# Toggle pump (0-indexed)
curl -X POST http://localhost:5000/api/pump/0/toggle

# Set pump state (0=off, 1=low, 2=high)
curl -X POST http://localhost:5000/api/pump/0/state \
  -H "Content-Type: application/json" \
  -d '{"state": 2}'

# Toggle light
curl -X POST http://localhost:5000/api/light/toggle

# Toggle blower
curl -X POST http://localhost:5000/api/blower/toggle

# Set heat mode
curl -X POST http://localhost:5000/api/heat-mode \
  -H "Content-Type: application/json" \
  -d '{"mode": "ready"}'

# Set temperature range
curl -X POST http://localhost:5000/api/temp-range \
  -H "Content-Type: application/json" \
  -d '{"range": "high"}'

Python Library

import asyncio
from spa_control import BalboaSpa

async def main():
    async with BalboaSpa("192.168.1.100") as spa:
        # Get current status
        status = await spa.get_status()
        print(f"Current temp: {status.current_temp}°{status.temp_unit}")
        print(f"Target temp: {status.target_temp}°{status.temp_unit}")
        print(f"Heating: {status.heating}")

        # Set temperature
        await spa.set_temperature(102)

        # Control pumps
        await spa.toggle_pump(0)  # Toggle pump 1
        await spa.set_pump(1, 2)  # Set pump 2 to high

        # Control lights
        await spa.toggle_light(0)

        # Change modes
        await spa.set_heat_mode("ready")
        await spa.set_temp_range("high")

asyncio.run(main())

Running Tests

# Run all tests
pytest

# Run with coverage
pytest --cov=. --cov-report=html

# Run specific test file
pytest test_spa_control.py -v

Architecture

                    ┌─────────────┐
                    │   Client    │
                    │  (Browser)  │
                    └──────┬──────┘
                           │ HTTPS
                           ▼
                    ┌─────────────┐
                    │   nginx     │
                    │  (SSL/TLS)  │
                    └──────┬──────┘
                           │ HTTP (localhost)
                           ▼
                    ┌─────────────┐
                    │  gunicorn   │
                    │  (webapp)   │
                    └──────┬──────┘
                           │
              ┌────────────┴────────────┐
              │                         │
              ▼                         ▼
       ┌─────────────┐          ┌─────────────┐
       │  Balboa Spa │          │   Discord   │
       │  (WiFi)     │          │   Webhook   │
       └─────────────┘          └─────────────┘

       ┌─────────────────────────────────────┐
       │       status_reporter.py            │
       │  (Separate service for periodic     │
       │   status updates to Discord)        │
       └─────────────────────────────────────┘

File Structure

balboapal/
├── spa_control.py          # Core library for spa communication
├── webapp.py               # Web application with OAuth
├── api.py                  # REST API (basic, no auth)
├── cli.py                  # Command-line interface
├── discord_logger.py       # Discord webhook integration
├── status_reporter.py      # Periodic status reporting daemon
├── test_spa_control.py     # Unit tests
├── requirements.txt        # Python dependencies
├── .env                    # Configuration (create from template)
├── .env.template           # Configuration template
├── README.md               # This file
├── DEPLOYMENT.md           # Detailed deployment guide
└── deploy/
    ├── install.sh          # Installation script
    ├── setup-ssl.sh        # Let's Encrypt setup
    ├── nginx-spa.conf      # nginx configuration
    ├── spa-control.service # systemd service (web app)
    └── spa-status-reporter.service  # systemd service (reporter)

Troubleshooting

Cannot connect to spa

  1. Verify the spa's IP address (check your router's DHCP leases)
  2. Ensure the WiFi module is connected (LED should be solid)
  3. Try the official Balboa app first to verify connectivity
  4. The spa must be on your local network (not cloud-only)

Temperature shows as None

This is normal when no water is flowing. The spa needs flow to measure temperature. Run a pump briefly or wait for a filter cycle.

OAuth redirect errors

  1. Verify redirect URI matches exactly (including https://)
  2. Check that SITE_DOMAIN in .env matches your actual domain
  3. Ensure SSL is working properly
  4. For Google: make sure the OAuth consent screen is configured

403 Access Denied after login

Your email is not in the PERMITTED_USERS list. Add your email to .env:

PERMITTED_USERS=your.email@gmail.com,another@example.com

Then restart the service.

Discord notifications not working

  1. Check DISCORD_WEBHOOK_URL is set correctly
  2. Test the webhook manually:
    curl -X POST "YOUR_WEBHOOK_URL" \
      -H "Content-Type: application/json" \
      -d '{"content": "Test message"}'

Heat Modes

  • Ready: Spa maintains set temperature continuously
  • Rest: Spa only heats during filtration cycles (economy mode)

Temperature Ranges

  • High: Full temperature range (typically up to 104°F / 40°C)
  • Low: Economy range (typically up to 99°F / 36°C)

Credits

  • pybalboa - The underlying Python library
  • balboa_worldwide_app - Protocol documentation
  • The Home Assistant community for reverse-engineering efforts

License

MIT License - feel free to use and modify as needed.

About

A little thing to manage balboa spas. If you have a balboa controlled wifi spa, this might help. I developed this for linux , but it should work fine under wsl and mac?

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published