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 (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.4.2.tar.gz (66.9 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.4.2-py3-none-any.whl (79.8 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: enocean_async-0.4.2.tar.gz
  • Upload date:
  • Size: 66.9 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.4.2.tar.gz
Algorithm Hash digest
SHA256 3d645f436d731c1a633122d6c78908e859423531a9faa52351cce72055cc883f
MD5 a8d3ddfb9c67a3ade5960073182434cb
BLAKE2b-256 a4b20d67ca6d1a7cc57c605eb717b518ce078f495f850b62f9240820537b0d31

See more details on using hashes here.

Provenance

The following attestation bundles were made for enocean_async-0.4.2.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.4.2-py3-none-any.whl.

File metadata

  • Download URL: enocean_async-0.4.2-py3-none-any.whl
  • Upload date:
  • Size: 79.8 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.4.2-py3-none-any.whl
Algorithm Hash digest
SHA256 2345b8f5aaecb44dd5f06329082a03d72d8c0e6ac452a7403112cfd66279d774
MD5 f72cca44e1eaec4f951a87f5bae841e1
BLAKE2b-256 d948771eef0dd9472ea769f00993e88f310c3cd34cc7e8ffb0a3ac2003ec583d

See more details on using hashes here.

Provenance

The following attestation bundles were made for enocean_async-0.4.2-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