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.1.tar.gz (66.8 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.1-py3-none-any.whl (79.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: enocean_async-0.4.1.tar.gz
  • Upload date:
  • Size: 66.8 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.1.tar.gz
Algorithm Hash digest
SHA256 ee0a9df1551ef1b2ce3d6d2f0a5c48c64e737040a67af6ee91f5b4c83e89e720
MD5 146fc7a423e6bfe15fa4b8da55bc95b2
BLAKE2b-256 187d9269d4a1fe9b7d09fe365036e0044fea7ed22074698387b66112ffb52e30

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: enocean_async-0.4.1-py3-none-any.whl
  • Upload date:
  • Size: 79.7 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 aee8c536299e2a603310d166d0783d7ef842f00a527333629670b900b22a2e82
MD5 984c2beaa297cc99fa2448f32304facc
BLAKE2b-256 0938d0f5a6864949a61d5cf571f4a6f755c5c3f2368d53cbac642e3ad9162fd4

See more details on using hashes here.

Provenance

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