OWNd is an asynchronous Python library and daemon for the Legrand / BTicino OpenWebNet home automation protocol.
It powers the Home Assistant MyHOME integration and serves as a standalone Python client for discovering, monitoring, and controlling OpenWebNet bus devices over TCP/IP gateways and serial USB interfaces.
Tip
🚀 V2 Phase 2 Architecture Now Live: Phase 2 architecture is active across OWNd and MyHOME! Featuring strongly typed CEN / CEN+ scenario command builders and device triggers (P2), Thermoregulation Central Unit (3550 / 4695) master mode and zone coordination (P4), Multi-Gateway routing and plant isolation (P6), DALI Tunable White support, and 100.0% test coverage verified against the OpenWebNet Golden Corpus.
- Hardened Dual-Session Architecture: Decouples real-time bus event monitoring (
OWNEventSession) from command and query execution (OWNCommandSession), preventing command bursts from interrupting event monitoring. - Strongly Typed CEN / CEN+ Command Builders (P2): Dedicated fluent builders (
OWNCenCommand,OWNCenPlusCommand) with strict OpenWebNet golden corpus frame parity for short press, start pressure, still held, and release actions across pushbuttons and rotary encoders. - Thermoregulation Central Unit Coordination (P4): Dedicated builder support for 3550 (
#0) and 4695 (#0#1) central units (OWNHeatingCommand.set_central_mode,set_central_temperature,set_central_antifreeze,set_central_thermal_protection,set_central_off), enabling master heating/cooling state distribution. - DALI Tunable White & Color Temperature: Built-in support for DALI DT8 ballasts (F429 / F461) with Dimension 14 color temperature encoding and bidirectional Kelvin/mireds conversion.
- OpenWebNet Golden Corpus Validation: Cross-checked and validated against the community OpenWebNet Golden Corpus (75+ real-world captured frame scenarios) ensuring exact frame encodings, dimensions, and edge cases.
- Serial & USB Dongle Support: Built-in single-channel serial transport (
AsyncSerialTransport) for the Legrand 3578 USB/ZigBee interface with in-band event and command-reply demultiplexing. - Connection Resilience:
- Fail-closed SHA-1 and HMAC-SHA2 gateway authentication with constant-time signature verification.
- OS-level TCP keepalive (
SO_KEEPALIVE) with aggressive probing (30s idle / 10s interval / 3 count) to detect silent network drops (power loss, cable unplugged) in ~60s. - Periodic application-level keepalives and passive watchdogs.
- Non-blocking bounded timeouts on handshakes and commands to prevent event loop stalls.
- Multi-frame response collection for large bus status sweeps (up to 256 frames).
- Declarative Hardware Profiles: Tailored queue pacing, session concurrency, and subsystem limits for known Legrand/BTicino hardware (F454, F455, MH200N, MH201, MH202, MyHomeServer1, and conservative generic fallbacks).
- Modern Python & Strict 100% Test Coverage: Designed for Python 3.11+, tested continuously against Python 3.11, 3.12, 3.13, and 3.14 with strict 100.0% line coverage unconditionally enforced across all core modules.
Install the latest stable release from PyPI:
pip install OWNdTo test preview releases or beta builds:
pip install --pre OWNd- Serial / USB support (required for Legrand 3578 USB dongles):
pip install "OWNd[serial]" - Development & test suite:
pip install "OWNd[test]"
OWNd parses OpenWebNet frames and dispatches typed commands and events across the full MyHOME spectrum:
| WHO | Subsystem | Description & Capabilities | Event / Command Classes |
|---|---|---|---|
| 1 | Lighting | On/off switching, dimming level (0–100%), DALI Tunable White (Dimension 14, 2000K–6535K / mireds), status queries | OWNLightingCommand, OWNLightingEvent |
| 2 | Automation | Shutters, blinds, motorized curtains, tilt angles, short & full replies | OWNAutomationCommand, OWNAutomationEvent |
| 3 | Load Control | Load shedding status, circuit priority management | OWNCommand, OWNEvent |
| 4 | Thermoregulation / Climate | Multi-zone temperature readouts, target adjustments, HVAC modes (Heat/Cool/Auto/Off), local offsets, fan coil speeds, valve states, Central Unit 3550/4695 master coordination | OWNHeatingCommand, OWNHeatingEvent |
| 5 | Burglar Alarm | Zone status, system arming / disarming states | OWNAlarmCommand, OWNAlarmEvent |
| 13 | Gateway Diagnostics & Clock | Gateway date/time synchronization, timezone offsets, firmware metadata | OWNGatewayCommand, OWNGatewayEvent |
| 15 | CEN Scenarios | Scenario control, pushbutton push/release/extended press events, strongly typed command builders | OWNCenCommand, OWNCENEvent, OWNScenarioEvent |
| 16 / 22 | Sound Diffusion | Multi-source selection, zone activation, volume adjustment, F441 matrix | OWNSoundCommand, OWNSoundEvent, OWNAVCommand |
| 17 | Scenario Programmer | MH200N / MH202 scenario activation and state monitoring | OWNSceneEvent |
| 18 | Energy Management | Active power (W), hourly/daily/monthly consumption (kWh), Stop & Go breaker diagnostics | OWNEnergyCommand, OWNEnergyEvent |
| 25 | CEN+ & Dry Contacts | 32-button keypads, rotary knob encoders (CW/CCW), dry contacts, PIR sensors, strongly typed command builders | OWNCenPlusCommand, OWNCENPlusEvent, OWNDryContactCommand, OWNDryContactEvent |
Gateways have varying processing limitations, socket budgets, and pacing requirements. OWNd uses declarative profiles to protect your hardware:
| Gateway Model | Concurrency | Queue Delay | Keepalive | Features |
|---|---|---|---|---|
| MyHomeServer1 | 4 sessions (2 default) | 20 ms | Profile | HMAC-SHA2, Native transitions, Extended frames |
| F454 / F455 | 4 sessions | 50 ms | 90 s | HMAC-SHA2, Native transitions, Extended frames |
| MH202 | 2 sessions | 100 ms | Profile | HMAC-SHA2, Extended frames |
| MH201 | 1 session | 100 ms | Profile | Extended frames, Clock diagnostics |
| MH200N | 1 session | 150 ms | 90 s | Safe pacing, Legacy password auth |
| Generic Gateway | 1 session | 50 ms | Profile | Conservative fallback |
Profiles can be resolved automatically using get_gateway_profile(model_name):
from OWNd.profiles import get_gateway_profile
profile = get_gateway_profile("F454")
print(f"Max concurrent sessions: {profile.max_command_sessions}")
print(f"Command queue delay: {profile.command_queue_delay}s")import asyncio
from OWNd.connection import OWNGateway
from OWNd.transport.tcp import AsyncTcpTransport
from OWNd.message import OWNMessage
async def main():
# Configure gateway credentials
gateway = OWNGateway({
"address": "192.168.1.50",
"port": 20000,
"password": "12345",
})
transport = AsyncTcpTransport(gateway)
# Register an event listener for bus notifications
def on_event(msg: OWNMessage | str):
if isinstance(msg, OWNMessage) and msg.is_event:
print(f"Bus Event: {msg.human_readable_log}")
transport.register_listener(on_event)
# Connect both event and command channels
if await transport.connect():
print("Connected to OpenWebNet gateway!")
# Send a command: Turn ON light at address 12 (*1*1*12##)
response = await transport.send("*1*1*12##")
print(f"Command response: {response}")
# Keep listening for events
await asyncio.sleep(10)
await transport.disconnect()
if __name__ == "__main__":
asyncio.run(main())For fine-grained control, OWNEventSession and OWNCommandSession can be operated independently:
import asyncio
from OWNd.connection import OWNGateway, OWNEventSession, OWNCommandSession
async def main():
gateway = OWNGateway({"address": "192.168.1.50", "port": 20000, "password": "12345"})
# Event listening session
event_session = OWNEventSession(gateway=gateway)
await event_session.connect()
# Command session
command_session = OWNCommandSession(gateway=gateway)
await command_session.connect()
# Query status of zone 1 climate: *#4*1*0##
status = await command_session.send("*#4*1*0##", is_status_request=True)
print(f"Status response: {status}")
await event_session.close()
await command_session.close()
asyncio.run(main())import asyncio
from OWNd.transport.serial import AsyncSerialTransport
async def main():
transport = AsyncSerialTransport(port="/dev/ttyUSB0")
transport.register_listener(lambda msg: print(f"Serial Inbound: {msg}"))
await transport.connect()
# Send OpenWebNet frame over serial
await transport.send("*1*1*12##")
await asyncio.sleep(5)
await transport.disconnect()
asyncio.run(main())from OWNd.message import OWNCenCommand, OWNCenPlusCommand, OWNHeatingCommand
# CEN (WHO=15): Button 2 short press on scenario controller 12 -> *15*02#2*12##
frame_cen = OWNCenCommand.short_press(where="12", button=2)
# CEN+ (WHO=25): Button 5 start pressure on keypad 01 -> *25*21#5*01##
frame_cenplus_press = OWNCenPlusCommand.start_pressure(where="01", button=5)
# CEN+ (WHO=25): Button 5 still held event -> *25*23#5*01##
frame_cenplus_held = OWNCenPlusCommand.still_held(where="01", button=5)
# CEN+ (WHO=25): Button 5 short release -> *25*24#5*01##
frame_cenplus_rel = OWNCenPlusCommand.release_from_short(where="01", button=5)
# Central Unit (WHO=4): Set 3550 (#0) master mode to Heating at 21.5°C -> *4*1#0215*#0##
frame_heat = OWNHeatingCommand.set_central_mode(where="#0", mode="heating", temperature=21.5)
# Central Unit (WHO=4): Set 4695 (#0#1) master mode to Cooling at 24.0°C -> *4*2#0240*#0#1##
frame_cool = OWNHeatingCommand.set_central_mode(where="#0#1", mode="cooling", temperature=24.0)
# Central Unit (WHO=4): Turn Central Unit OFF -> *4*303#0215*#0##
frame_off = OWNHeatingCommand.set_central_off(where="#0", mode="heating", temperature=21.5)OWNd includes a built-in CLI for discovering gateways and inspecting live bus events:
Scan the local network for OpenWebNet gateways and listen for events:
python -m OWNdConnect to a known gateway IP address:
python -m OWNd --address 192.168.1.50 --port 20000 --password 12345 --verbose 2Available options:
-a,--address: IP address of the gateway-p,--port: Gateway TCP port (default:20000)-P,--password: Numeric OPEN password or HMAC secret (default:12345)-m,--mac: MAC address (used as unique identifier when skipping SSDP)-v,--verbose: Verbosity level (0= WARNING,1= INFO,2= DEBUG)
Clone the repository and install development dependencies:
git clone https://github.com/OpenWebNet-HA/OWNd.git
cd OWNd
pip install -e ".[test,serial]" ruff mypy types-python-dateutil types-pytzExecute the test suite across all subsystems:
python -m pytest -qVerify type safety and coding standards:
ruff check OWNd tests setup.py
mypy OWNdThis project is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0-only). See the LICENSE file for details.
OWNd maintains an automated test suite with strict 100.0% line coverage (3,087 / 3,087 statements covered with 0 missing lines across all 9 core modules) verified continuously in CI across Python 3.11, 3.12, 3.13, and 3.14:
| Component / Module | Coverage | Notes |
|---|---|---|
OWNd/__init__.py |
100% | Package initialization and version metadata |
OWNd/connection.py |
100% | Hardened dual-session TCP engine, SHA-1/HMAC auth, keepalives & bounded read loops |
OWNd/discovery.py |
100% | SSDP multicast and UPnP XML gateway discovery and descriptor parsing |
OWNd/message.py |
100% | OpenWebNet frame parsers, encoders, and WHO dimension decoders |
OWNd/profiles.py |
100% | Declarative hardware gateway models (F454, MH200N, MH201, MH202, MyHomeServer1) |
OWNd/transport/__init__.py |
100% | Transport subpackage exports |
OWNd/transport/base.py |
100% | Abstract transport layer and event listener notification contracts |
OWNd/transport/serial.py |
100% | Async Serial/USB transport for Legrand 3578 interface with in-band demux |
OWNd/transport/tcp.py |
100% | Dual-session TCP transport linking event and command channels |
Live Test Execution: View detailed line-by-line coverage and test history on Codecov (OpenWebNet-HA/OWNd) or download the interactive coverage report from the CI GitHub Actions run.