Skip to main content

A library to communicate with Ooler Sleep System Bluetooth devices.

Project description

ooler-ble-client

PyPI Python License

A Python library to communicate with Ooler Sleep System Bluetooth devices via BLE GATT characteristics. Built on bleak and bleak-retry-connector.

Designed for use with the Home Assistant Ooler integration, but can be used standalone.

Installation

pip install ooler-ble-client

Usage

import asyncio
from bleak import BleakScanner
from ooler_ble_client import OolerBLEDevice

async def main():
    # Discover the device
    device = await BleakScanner.find_device_by_name("OOLER")

    # Create client and connect
    client = OolerBLEDevice(model="OOLER")
    client.set_ble_device(device)
    await client.connect()

    # Read state
    print(client.state)

    # Control the device
    await client.set_power(True)
    await client.set_temperature(72)
    await client.set_mode("Regular")

    # Listen for state changes
    def on_state_change(state):
        print(f"State changed: {state}")

    unsubscribe = client.register_callback(on_state_change)

    # Clean up
    unsubscribe()
    await client.stop()

asyncio.run(main())

API

OolerBLEDevice(model: str)

Main client class.

  • set_ble_device(device) -- set the BLE device to connect to
  • connect() -- establish BLE connection, read initial state, subscribe to notifications
  • stop() -- unsubscribe from notifications and disconnect
  • is_connected -- whether the device is currently connected
  • state -- current OolerBLEState
  • register_callback(fn) -- register a state change callback, returns an unsubscribe function
  • async_poll() -- read all characteristics from the device
  • set_power(bool) -- turn device on/off (re-sends mode and temperature on power-on)
  • set_mode(OolerMode) -- set pump mode: "Silent", "Regular", or "Boost"
  • set_temperature(int) -- set target temperature in the current display unit
  • set_clean(bool) -- start/stop clean cycle (automatically powers on)
  • set_temperature_unit(TemperatureUnit) -- set device display unit: "C" or "F"
  • address -- BLE device address
  • register_connection_event_callback(fn) -- register a connectivity event callback, returns an unsubscribe function

Sleep schedule methods

  • read_sleep_schedule() -- read the schedule from the device (updates cache)
  • set_sleep_schedule(nights) -- write a structured schedule (list of SleepScheduleNight)
  • set_sleep_schedule_events(events) -- write a flat event list directly
  • clear_sleep_schedule() -- clear the schedule on the device
  • sync_clock(now=None) -- sync the device's internal clock (used for schedule execution). Pass a timezone-aware datetime, or omit to use the system timezone.
  • sleep_schedule -- cached OolerSleepSchedule (or None if not yet read)
  • sleep_schedule_events -- cached schedule as a flat list[SleepScheduleEvent]

OolerBLEState

Dataclass with fields: power, mode, set_temperature, actual_temperature, water_level, clean, temperature_unit.

Sleep Schedule Types

  • OolerSleepSchedule -- weekly schedule containing a list of SleepScheduleNight and a sequence counter
  • SleepScheduleNight -- one night's program: day (0=Mon), temperature steps, off time, optional warm wake
  • SleepScheduleEvent -- a single event in the flat wire format (minute of week + temperature). Minute of week is minutes elapsed since Monday 00:00 (e.g. Tuesday 6:00am = 1800).
  • WarmWake -- warm wake configuration: target temperature and duration in minutes
  • build_sleep_schedule(bedtime, wake_time, temp_f, ...) -- convenience builder for uniform schedules (same program across selected days, with optional warm wake and extra temperature steps)

Connection Events

  • ConnectionEvent -- a connectivity event with type, timestamp, and optional detail
  • ConnectionEventType -- enum: CONNECTED, DISCONNECTED, SUBSCRIPTION_MISMATCH, SUBSCRIPTION_RECOVERED, FORCED_RECONNECT

Other Types

  • OolerMode -- Literal["Silent", "Regular", "Boost"]
  • TemperatureUnit -- Literal["C", "F"]
  • OolerConnectionError -- raised when all retry attempts are exhausted (inherits from BleakError)

Concurrency & Reconnection

Connection serialization

All connection attempts are serialized through an internal asyncio.Lock. If connect() is called while another connection is already in progress, the second caller waits for the first to complete and then returns immediately if the connection succeeded. This prevents duplicate connections and race conditions.

Two-level retry

GATT write operations use a two-level retry strategy:

  1. Immediate retry -- if a write fails with a transient BLE error (e.g., ESP32 proxy hiccup), the operation is retried immediately without reconnecting.
  2. Reconnect + retry -- if the immediate retry also fails, the library forces a full disconnect/reconnect cycle (with a 0.5s backoff) and retries the operation once more.

If all three attempts fail, an OolerConnectionError is raised.

async_poll() uses a similar pattern: if the poll fails, it reconnects and retries once.

Handled exception types

The library catches BleakError, EOFError, BrokenPipeError, and asyncio.TimeoutError during GATT operations. These cover the common failure modes seen with ESP32 BLE proxies.

Disconnect handling

When the BLE connection drops unexpectedly, the internal client reference is cleared immediately so is_connected returns False. Registered callbacks are fired to notify consumers of the state change. The library does not automatically reconnect -- the consumer (e.g., a Home Assistant integration) is responsible for triggering reconnection on the next advertisement or poll cycle.

ESP32 BLE Proxy Considerations

Notification slots

ESP32 BLE proxies (ESPHome) have a global limit of 12 notification registrations across all connected devices. This library subscribes to 4 notification characteristics per device:

  • Power, Mode, Set Temperature, Actual Temperature

Water level and clean status are polled (via async_poll()) rather than subscribed to notifications. This means two Ooler devices use 8 of 12 available slots, leaving headroom for other BLE devices.

Connection slots

ESP32 proxies support 3 simultaneous BLE connections by default. Each Ooler device holds one connection slot for as long as it's connected.

Temperature Behavior

The Ooler has a quirk in how it handles temperature units:

  • Set temperature (SETTEMP_CHAR) is always stored and reported in Fahrenheit by the device, regardless of the display unit setting.
  • Actual temperature (ACTUALTEMP_CHAR) is reported in whatever unit the device display is set to.

The library handles this automatically:

  • state.set_temperature is converted to the current display unit on read.
  • set_temperature(value) accepts a value in the current display unit and converts to Fahrenheit before writing to the device.
  • state.actual_temperature is passed through as-is from the device.

The display unit is read once on connect and cached. It can be changed via set_temperature_unit().

License

Apache-2.0

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ooler_ble_client-1.0.0.tar.gz (24.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ooler_ble_client-1.0.0-py3-none-any.whl (24.7 kB view details)

Uploaded Python 3

File details

Details for the file ooler_ble_client-1.0.0.tar.gz.

File metadata

  • Download URL: ooler_ble_client-1.0.0.tar.gz
  • Upload date:
  • Size: 24.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.14.3 Darwin/25.3.0

File hashes

Hashes for ooler_ble_client-1.0.0.tar.gz
Algorithm Hash digest
SHA256 356c9111d6d814e4f5081933063cad2d6ab1283a2010de328873e806bbb17c6b
MD5 c77b92ee4087216a027ee0e91204881b
BLAKE2b-256 27442296a3f5ac04740808c2c45c0d6860055beb067c410a54ebbad9a42297ce

See more details on using hashes here.

File details

Details for the file ooler_ble_client-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: ooler_ble_client-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 24.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.14.3 Darwin/25.3.0

File hashes

Hashes for ooler_ble_client-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9d67fa0f8287494994f5ec79ab4805c62f3d0fb44fa16d2a3d2e14527c0b7a24
MD5 7cd98f6cc45c7acd7d2a0684689b7ab9
BLAKE2b-256 89fd22dc300d9781b5bbbef2c04fbdb7f3f19b8778bddcb32da46beaf32515b7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page