Skip to main content

aiocometwifi

Async Python library for Eurotronic Comet WiFi radiator thermostats, talking to them over MQTT through a transport the caller owns. The library never opens a connection itself: hand it the publish, subscribe and unsubscribe functions of your MQTT client, and drive one Thermostat instance per device.

It was written for the Home Assistant integration, whose MQTT integration already owns the broker connection, but works with any client (the quick start below uses paho-mqtt).

  • Zero runtime dependencies, fully typed (py.typed), Python ≥ 3.14.
  • Reads setpoint, ambient temperature, offset, battery level and configuration flags.
  • Sets the setpoint, turns heating off or fully on, toggles key lock, key lock plus, mirrored display and DST.
  • Answers the device's connection test so it stays online.

✅ Prerequisites

  1. An MQTT broker that accepts anonymous clients on your LAN (the thermostats do not support authentication). The official Home Assistant Mosquitto add-on does not qualify. See Broker.
  2. Each thermostat must be reconfigured for LAN-only operation, pointing at that broker, using the setup-thermostat command of comet-wifi-communicator. See Device setup.
  3. Python ≥ 3.14 and an MQTT client of your own (paho-mqtt, aiomqtt, Home Assistant's MQTT integration, …), the library only adapts to it.

📡 Broker

The thermostats connect to an MQTT broker anonymously and cannot be given credentials. Run a broker with allow_anonymous true. Restrict what anonymous clients may access with an ACL (the devices only need 01/#) and keep the broker off the public internet. The official Home Assistant Mosquitto add-on refuses anonymous clients by design, so a separate broker, or a bridge to it, is required.

🔧 Device setup

Out of the box a Comet WiFi talks to the manufacturer's cloud. For this library it has to be reconfigured for LAN-only operation, pointed at your own broker. This has to be done once and with the setup-thermostat command of comet-wifi-communicator:

pip install comet-wifi-communicator
setup-thermostat --wifi-ssid <ssid> --wifi-password <password> --mqtt-server-ip <broker ip>

aiocometwifi takes over once the device is on the broker.

🚀 Quick start

import asyncio

import paho.mqtt.client as mqtt
from aiocometwifi import MqttClient, SubscribeCallback, SubState, Thermostat


class PahoTransport:
    """Adapt a running paho client to the callables MqttClient expects."""

    def __init__(self, client: mqtt.Client, loop: asyncio.AbstractEventLoop) -> None:
        self._client = client
        self._loop = loop

    async def publish(self, topic: str, payload: str, qos: int, retain: bool) -> None:
        self._client.publish(topic, payload, qos, retain)

    async def subscribe(
        self, sub_state: SubState | None, topic: str, callback: SubscribeCallback
    ) -> SubState:
        def on_message(
            _client: mqtt.Client, _userdata: object, msg: mqtt.MQTTMessage
        ) -> None:
            # paho calls this on its network thread; the library runs on the event loop.
            self._loop.call_soon_threadsafe(callback, msg.topic, msg.payload.decode())

        self._client.message_callback_add(topic, on_message)
        self._client.subscribe(topic)
        return {"topic": topic}

    async def unsubscribe(self, sub_state: SubState) -> None:
        self._client.message_callback_remove(sub_state["topic"])
        self._client.unsubscribe(sub_state["topic"])


async def main() -> None:
    client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
    client.connect("broker.local")
    client.loop_start()

    transport = PahoTransport(client, asyncio.get_running_loop())
    mqtt_client = MqttClient(
        transport.publish, transport.subscribe, transport.unsubscribe
    )
    thermostat = Thermostat(mqtt_client, "AA:BB:CC:DD:EE:FF")

    await thermostat.connect()  # subscribes; nothing is sent yet
    await thermostat.update_heating_values()
    await asyncio.sleep(5)  # replies arrive asynchronously, see below
    print(thermostat.temperature_ambient, thermostat.setpoint, thermostat.is_heating)

    await thermostat.set_setpoint_temperature(21.0)

    await thermostat.disconnect()
    client.loop_stop()


asyncio.run(main())

Requests and replies are separate MQTT messages. update_*() publishes a request and returns immediately; the reply updates the thermostat's properties when it arrives, typically within a few seconds. Awaiting the reply itself is planned for a later release.

Commands (set_setpoint_temperature, turn_off, turn_fully_on, enable_*/disable_*) raise CometWifiConnectionError until the device has been heard from. A reply or one of its periodic connection tests sets connected. The device's last will clears it again.

set_setpoint_temperature clamps to 8.0–28.0 °C (TEMPERATURE_SETPOINT_MIN/_MAX) and truncates to the device's 0.5 °C steps.

⚠️ Errors

Everything raised across the API derives from CometWifiError:

Exception Raised when
CometWifiConnectionError A command is issued before the device has answered, or after its last will.
CometWifiValueError An argument is rejected (malformed MAC, negative temperature, bad hex). Also a ValueError.
CometWifiTimeoutError Reserved for awaited replies (later release).
CometWifiInvalidResponseError Reserved. Malformed replies are currently logged, not raised.

Errors from the transport itself (publish failing, broker gone) propagate unchanged from the call that triggered them. One exception is the automatic ping reply, where they are logged.

📖 Communication Protocol

Topics are 01/<MAC>/<group>/<code> with the MAC as twelve uppercase hex digits and the group S for commands (host → device) or V for replies (device → host). Payloads are ASCII hex with a # prefix; temperatures are half-degrees (#2A = 21.0 °C).

Topic Direction Content
S/AF Host → Device Value request, 32-bit mask of what to report
S/A0 Host → Device Write setpoint (#0F = off, #39 = fully on)
S/A3 Host → Device Write configuration flags
S/XX Host ↔ Device Connection test: the device publishes a ping, the host must answer #COMM-TEST on the same topic
V/A0, V/A1, V/A2 Host ← Device Setpoint, ambient temperature, offset
V/A3 Host ← Device Configuration (key lock, key lock plus, mirrored display, DST)
V/A6 Host ← Device Battery level
V/XX Host ← Device Last will, the device disconnected

Because the pong goes out on the very topic the library is subscribed to, the broker echoes it back. The library recognizes its own echoes and rate-limits pongs, so it never loops.

🔗 Relation to comet-wifi-communicator

comet-wifi-communicator is the same protocol with a bundled paho client, for scripts and standalone use, and it provides the setup-thermostat command above. aiocometwifi is for hosts that already have an MQTT connection and an event loop, Home Assistant first of all.

🛠️ Development

python -m venv .venv && source .venv/bin/activate
pip install -e . -r requirements_dev.txt
pre-commit install
pytest --cov --cov-fail-under=90 --cov-report=term-missing
ruff check . && ruff format --check . && mypy

Tests run against a fake transport (tests/conftest.py) so no broker is needed. Commits follow Conventional Commits. Releases are cut from main by python-semantic-release.

⚖️ Disclaimer

This is an independent, community-developed project. It is not affiliated with, endorsed by or supported by Eurotronic. Eurotronic and Comet WiFi are trademarks or trade names of their respective owners and are used here only to identify the devices this library communicates with. Use at your own risk. See the license for the warranty disclaimer.

While I am somewhat skeptical towards blindly allowing AI for coding and do not support the way major companies are selling and training their models, the usefulness for reviewing and improving software cannot be denied. Therefore, for enhancing the quality of parts of this library and finding issues, AI (mostly Claude Opus 5) was utilized.

📜 License

GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007, see the license file.

Release files for aiocometwifi 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for aiocometwifi 0.1.0
File Size Uploaded
aiocometwifi-0.1.0.tar.gz 35.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aiocometwifi 0.1.0
File Interpreter ABI Platform
aiocometwifi-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 61.5 kB

Release files / aiocometwifi-0.1.0.tar.gz

Download URL aiocometwifi-0.1.0.tar.gz
Size 35.1 kB
Tags Source
SHA-256 checksum
How to use checksums
2091cbca08ab2080594da8a420f02d7b945994593d4644852b2a7ae532ac869f
BLAKE2b-256 checksum
How to use checksums
fab11d9780ae73358088587651c4e1994e23079dbebf170c42134511bc386e33
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.

Transparency log

Release files / aiocometwifi-0.1.0-py3-none-any.whl

Download URL aiocometwifi-0.1.0-py3-none-any.whl
Size 26.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b50fa5f565563d7d1b9085fc685889332ddc1c782e4341f3d1bcfdfabdfb17d5
BLAKE2b-256 checksum
How to use checksums
e86c717b764030c68c550829c8677f38a9dc484dbce59d143c3be9c06c83695d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page