Skip to main content

An async implementation of the EnOcean Serial Protocol Version 3.

Project description

enocean-async

A light-weight, asynchronous, fully typed Python library for communicating with EnOcean devices over a USB gateway. Based on pyserial-asyncio-fast and the EnOcean Serial Protocol Version 3 (ESP3).

Note: The API may still change (and even significantly!). Feedback and contributions are welcome.

Features

Receive pipeline — observables

Incoming radio telegrams are decoded into typed StateChange objects at the top of the pipeline. Callbacks are available at every stage for lower-level access:

# Stage 4 — semantic: one StateChange per observable per device
gateway.add_state_change_callback(lambda sc: print(sc))
# StateChange(device_address=…, observable_uid='temperature', value=21.3, unit='°C', channel=None, …)

# Stage 3 — decoded EEP message (field values + semantic entities)
gateway.add_eep_message_received_callback(lambda msg: ..., sender_filter=eurid)

# Stage 2 — parsed ERP1 telegram (RORG, sender, raw payload bits)
gateway.add_erp1_received_callback(lambda erp1: ...)

# Stage 1 — raw ESP3 packet (before any parsing)
gateway.add_esp3_received_callback(lambda pkt: ...)

Observable UIDs are stable string constants defined in ObservableUID (temperature, illumination, switch_state, position, cover_state, window_state, energy, power, …). Multi-channel actuators include a channel field so individual outputs can be distinguished.

Send pipeline — typed actions

Commands are sent to devices using typed Action objects:

from enocean_async.capabilities.cover_actions import SetCoverPositionAction
from enocean_async.capabilities.action_uid import ActionUID

await gateway.send_command(destination=device_eurid, action=SetCoverPositionAction(position=75))

Device management

gateway.add_device(address=eurid, eep=EEP.from_string("D2-05-00"), name="Living room blind")

Learning / teach-in

await gateway.start_learning(timeout_seconds=60)
# Gateway accepts UTE teach-in (with automatic response); 4BS teach-in, and 1BS teach-in are NOT YET SUPPORTED
gateway.stop_learning()

Gateway utilities

  • Retrieve EURID, Base ID and firmware version info
  • Change the Base ID
  • Auto-reconnect: when the serial connection is lost, the gateway retries for up to 1 hour

What works

  • Full receive pipeline: raw serial bytes → ESP3 → ERP1 → EEP decode → capabilities → StateChange callbacks
  • Full send pipeline: typed ActionEEPHandler.encode() → ERP1 → ESP3 → serial
  • Device registration with per-device EEP and capability instantiation
  • Learning mode: UTE teach-in (query parsing + automatic bidirectional response)
  • Auto-reconnect on connection loss
  • EURID, Base ID, firmware version retrieval; Base ID change
  • Parsing of all EEPs listed in SUPPORTED_EEPS.md
  • Sending commands for: D2-05-00 (covers), D2-20-02 (fan), A5-38-08 (dim gateway), D2-01 (switches/dimmers)

What is missing / not yet implemented

  • ECID sub-dispatch for D2-01 extended commands
  • More EEPs (contributions welcome — see SKILLS.md for the step-by-step guide)
  • Logging coverage is partial
  • 4BS teach-in, 1BS teach-in

Implemented EEPs

See SUPPORTED_EEPS.md.

Architecture

Receive pipeline (observables)

Radio signal
    │ serial bytes
    ▼
EnOceanSerialProtocol3
    │ ESP3 framing (sync, CRC, packet type)
    ▼
ESP3Packet
    │ RADIO_ERP1 detection
    ▼
ERP1Telegram      rorg, sender EURID, raw payload bits, rssi
    │ EEP profile lookup → EEPHandler.decode()
    ▼
EEPMessage
  .values    {field_id → EEPMessageValue}   ← EEP spec vocabulary: "TMP", "ILL1", "R1"
  .entities  {observable_uid → EntityValue} ← semantic vocabulary: "temperature", "illumination"
    │ Capability.decode()  (one call per capability in device.capabilities)
    ├── ScalarCapability(observable_uid=TEMPERATURE)  → reads entities["temperature"]
    ├── ScalarCapability(observable_uid=ILLUMINATION) → reads entities["illumination"]
    ├── CoverCapability     → reads entities["position"] + entities["angle"], infers "cover_state"
    ├── PushButtonCapability → reads values["R1"], values["EB"], … (stateful, no observable_uid)
    └── MetaDataCapability  → emits rssi, last_seen, telegram_count
    │ _emit()
    ▼
StateChange(device_address, observable_uid, value, unit, channel, timestamp, source)
    │ on_state_change callback
    ▼
Application

Send pipeline (actions)

Application
    │ gateway.send_command(destination, action=SetCoverPositionAction(position=75))
    ▼
Action  (typed dataclass, action_uid class variable)
    │ EEPSpecification.command_encoders[action.action_uid](action)
    ▼
EEPMessage
  .message_type  ← selects which telegram type to encode
  .values        ← {field_id → EEPMessageValue(raw)} filled in by the encoder
    │ EEPHandler.encode()
    ├── Determine buffer size from field layout
    ├── Write CMD bits at cmd_offset / cmd_size
    └── Write each field's raw value at field.offset / field.size
    ▼
ERP1Telegram(rorg, telegram_data, sender, destination)
    │ .to_esp3()
    ▼
ESP3Packet
    │ Gateway.send_esp3_packet()
    ▼
Radio signal → Device

See ARCHITECTURE.md for a detailed description of the EEP layer, the capability layer, and the key design decisions.

Contributing

See CONTRIBUTING.

Dependencies

This library has one dependency:

Technology documentation

Copyright & license

Copyright 2026 Henning Kerstan

Licensed under the Apache License, Version 2.0 (the "License"). See LICENSE file for details.

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

enocean_async-0.3.0.tar.gz (62.5 kB view details)

Uploaded Source

Built Distribution

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

enocean_async-0.3.0-py3-none-any.whl (74.3 kB view details)

Uploaded Python 3

File details

Details for the file enocean_async-0.3.0.tar.gz.

File metadata

  • Download URL: enocean_async-0.3.0.tar.gz
  • Upload date:
  • Size: 62.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for enocean_async-0.3.0.tar.gz
Algorithm Hash digest
SHA256 9e032eef7a7c80fe7117f679a7539e16170b431f141489f3c1553b93c12c06fb
MD5 1a48c9a98163db0050ddd8896ca90dba
BLAKE2b-256 4dacf852f202683913e91d4d63b64b6ea5e967ede4b90ac26a3a35a7b78a21a3

See more details on using hashes here.

Provenance

The following attestation bundles were made for enocean_async-0.3.0.tar.gz:

Publisher: publish.yml on henningkerstan/enocean-async

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file enocean_async-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: enocean_async-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 74.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for enocean_async-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 70c7b40135abad98247a15fda6d8fc8332ac273681c2e254441b88f429ea36d8
MD5 c6345640130dabb4397db5814b84fa25
BLAKE2b-256 ae0395eb7f38ec000ec9e57ebee48e62b48af9439030204c80779eb317673fca

See more details on using hashes here.

Provenance

The following attestation bundles were made for enocean_async-0.3.0-py3-none-any.whl:

Publisher: publish.yml on henningkerstan/enocean-async

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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