Skip to content

About

Automatic smart-fan speed control from heart rate zones for indoor cycling (Philips CoAP and Dyson MQTT fans, BLE and ANT+ heart rate).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

Indoor cyclist on a trainer facing a fan, with a glowing red heart and ECG pulse line — heart-rate-driven fan control

Heart Rate Fan Controller for Indoor Cycling

Automatic fan speed control based on heart rate zones for indoor cycling. Connects to your smart fan via WiFi and adjusts speed based on real-time heart rate data from a Bluetooth LE or ANT+ heart rate monitor.

Supported Fans

  • Philips CX3350/CX3550 - via encrypted CoAP protocol
  • Dyson Pure Cool (TP04/DP04) - via local MQTT (credentials from WiFi sticker or cloud)

Features

  • Multi-brand fan support via pluggable controller architecture
  • Automatic fan speed control based on HR zones
  • BLE heart rate monitor support (Polar H10, Garmin HRM, Wahoo TICKR, etc.)
  • ANT+ heart rate monitor support (requires a USB dongle and the ant extra)
  • Smart speed transitions:
    • Hysteresis prevents flapping at zone boundaries
    • Step-down delay allows recovery during rest intervals
    • Immediate step-up when HR rises
  • Persistent configuration - saves your max HR and HR device
  • Cross-platform - works on macOS, Linux (including Raspberry Pi)

HR Zone to Fan Speed Mapping

HR Zone % Max HR Fan Speed
Zone 1-2 < 70% Low (~33%)
Zone 3 70-80% Medium (~66%)
Zone 4-5 > 80% High (100%)

Fan speeds are normalized to percentages and mapped to each fan's native range (1-3 for Philips, 1-10 for Dyson).

Requirements

  • Python 3.13+
  • macOS or Linux (including Raspberry Pi)
  • Supported smart fan on your local network
  • Heart rate monitor (Bluetooth LE or ANT+ with USB dongle)

Installation

# Clone the repository
cd philips

# Install with uv (Philips fan + BLE support)
uv sync

# Install with Dyson support
uv sync --extra dyson

# Install with ANT+ support (requires USB dongle)
uv sync --extra ant

# Install all optional dependencies
uv sync --extra dyson --extra ant

Quick Start

Philips Fan

uv run python -m fan_controller

On first run, you'll be prompted to:

  1. Select your fan brand (Philips)
  2. Enter your fan's IP address
  3. Enter your maximum heart rate
  4. Select your HR monitor from discovered devices

Dyson Fan

Dyson requires device credentials for local MQTT control. Two methods are available:

Method 1: LOCAL (Recommended) - Extract credentials from device WiFi sticker

  • Look for a sticker on your Dyson showing WiFi SSID (DYSON-xxx-xxx) and password
  • No cloud authentication required

Method 2: CLOUD - Authenticate with Dyson account (requires 2FA)

  • Use if you can't find the device sticker
  • Requires Dyson account email, password, and email OTP code
# Step 1: Run setup wizard (one-time)
uv run python scripts/setup_dyson.py

# Step 2: Run the fan controller
uv run python -m fan_controller

Configuration is saved to ~/.config/fan-controller/config.json.

Usage

Run the Fan Controller

uv run python -m fan_controller

Test the Fan Directly

# Turn fan on/off
uv run python scripts/test_fan.py on
uv run python scripts/test_fan.py off

# Set specific speed
uv run python scripts/test_fan.py speed 2

# Cycle through all speeds
uv run python scripts/test_fan.py cycle

# Set special modes (Philips only)
uv run python scripts/test_fan.py sleep
uv run python scripts/test_fan.py natural

test_fan.py uses your saved config for the fan IP. To target a different fan, set the FAN_IP environment variable, for example FAN_IP=192.168.1.100 uv run python scripts/test_fan.py on.

Test the Heart Rate Monitor

# BLE heart rate monitor
uv run python scripts/test_hr.py scan      # Scan for devices
uv run python scripts/test_hr.py connect   # Connect and monitor
uv run python scripts/test_hr.py test      # Quick 30-second test

# ANT+ heart rate monitor (requires USB dongle)
uv run python scripts/test_hr_ant.py connect
uv run python scripts/test_hr_ant.py test

Configuration

Configuration is stored in ~/.config/fan-controller/config.json:

{
  "fan_brand": "philips",
  "philips": {
    "ip": "192.168.1.100"
  },
  "dyson": {
    "serial": "",
    "credentials": "",
    "device_type": "",
    "ip": null
  },
  "max_hr": 185,
  "hr_source": "ble",
  "hr_device_address": "12345678-1234-...",
  "hr_ant_device_id": null,
  "smoothing_window": 5,
  "hysteresis_bpm": 5,
  "step_down_delay": 5,
  "zone_thresholds": [0.70, 0.80]
}
Setting Description Default
fan_brand Fan type ("philips" or "dyson") philips
philips.ip IP address of Philips fan -
dyson.serial Dyson device serial (from setup) -
dyson.credentials Dyson device credentials (from setup) -
dyson.device_type Dyson device type code (e.g., "475") -
dyson.ip IP address for local connection (required) null
max_hr Your maximum heart rate 180
hr_source HR monitor type ("ble" or "ant") ble
hr_device_address Saved BLE address of HR monitor None
hr_ant_device_id Saved ANT+ device ID None
smoothing_window Number of HR readings to average 5
hysteresis_bpm BPM buffer at zone boundaries 5
step_down_delay Readings before stepping down speed 5
zone_thresholds HR % thresholds for zone 3 and 4 [0.70, 0.80]

Raspberry Pi Setup

This project works on Raspberry Pi. For best results:

  1. Use a virtual environment (uv handles this automatically)
  2. Install dependencies via pip, not apt, to avoid ARM-specific issues:
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh

# Clone and install
git clone <repo>
cd philips
uv sync

# For Dyson support
uv sync --extra dyson

# For ANT+ support (install udev rules)
uv sync --extra ant
sudo python -m openant.udev_rules

Development

Running Tests

# Run all tests
uv run pytest

# Run with coverage
uv run pytest --cov=src/fan_controller --cov-report=term-missing

Code Quality

# Format code
uv run ruff format src tests

# Lint code
uv run ruff check src tests --fix

# Type checking
uv run pyright src

Architecture

                       FanController (ABC)
                       ├── connect()
                       ├── disconnect()
                       ├── set_speed_percent()
                       └── ...
                              │
             ┌────────────────┼────────────────┐
             │                │                │
      PhilipsFan        DysonFan         [Future]
      (aioairctrl)      (libdyson)
      speeds: 1-3      speeds: 1-10

[HR Monitor] ---(BLE)---> [Python Service] ---(Protocol)---> [Smart Fan]
                                |
                         [Zone Mapper]
                         - Smoothing
                         - Hysteresis
                         - Step-down delay
  1. BLE Heart Rate: Connects to your HR monitor using the standard Heart Rate Service (0x180D)
  2. Zone Mapping: Converts HR readings to fan speed percentage based on configured zones
  3. Fan Control: Each fan implementation converts percentage to its native speed range

Adding New Fan Brands

To add support for a new fan brand:

  1. Create a new file src/fan_controller/<brand>_fan_control.py
  2. Implement the FanController ABC from fan_interface.py
  3. Add config class in config.py
  4. Register in fan_factory.py
  5. Update main.py prompts

See dyson_fan_control.py as an example.

Troubleshooting

Fan not connecting

  • Ensure the fan is on the same network as your computer
  • Verify the IP address in config matches your fan
  • Try power cycling the fan

Dyson authentication fails

  • Try LOCAL method first: Use the WiFi sticker on your device (no cloud needed)
  • For cloud auth: Verify your Dyson account email and password
  • Check your email for the OTP code (may be in spam folder)
  • Common country codes: AU (Australia), US, GB, DE, FR, CA, NZ
  • If OTP requested too frequently, wait a few minutes

HR monitor not found

  • Make sure your HR strap is on and active
  • Check that Bluetooth is enabled
  • Try running the scan multiple times

Speed changes too frequently

  • Increase hysteresis_bpm (default: 5)
  • Increase smoothing_window (default: 5)
  • Increase step_down_delay (default: 5)

Credits

About

Automatic smart-fan speed control from heart rate zones for indoor cycling (Philips CoAP and Dyson MQTT fans, BLE and ANT+ heart rate).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages