Skip to content

Latest commit

 

History

207 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OWNd

PyPI version Python versions CI License: LGPL-3.0 Ruff Coverage Codecov

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.


Key Features

  • 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.

Installation

Install the latest stable release from PyPI:

pip install OWNd

To test preview releases or beta builds:

pip install --pre OWNd

Optional Extras

  • Serial / USB support (required for Legrand 3578 USB dongles):
    pip install "OWNd[serial]"
  • Development & test suite:
    pip install "OWNd[test]"

Supported Subsystems (WHO Catalog)

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

Hardware Gateway Profiles

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")

Quick Start

1. High-Level TCP Transport (Dual-Session)

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())

2. Direct Session Management

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())

3. Serial / USB Dongle (Legrand 3578)

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())

4. Strongly Typed CEN / CEN+ & Central Unit Commands (Phase 2)

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)

Command Line Interface (CLI)

OWNd includes a built-in CLI for discovering gateways and inspecting live bus events:

Auto-Discovery (SSDP)

Scan the local network for OpenWebNet gateways and listen for events:

python -m OWNd

Direct Connection

Connect to a known gateway IP address:

python -m OWNd --address 192.168.1.50 --port 20000 --password 12345 --verbose 2

Available 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)

Development

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-pytz

Running Tests

Execute the test suite across all subsystems:

python -m pytest -q

Static Analysis & Linting

Verify type safety and coding standards:

ruff check OWNd tests setup.py
mypy OWNd

License

This project is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0-only). See the LICENSE file for details.

📊 Code Coverage & Quality Assurance

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.

About

OpenWebNet daemon

Resources

Stars

35 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages