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 Observation objects. Callbacks are available at every stage for lower-level access:

# Stage 4 — semantic: one Observation per entity per device
gateway.add_observation_callback(lambda obs: print(obs))
# Observation(device_id=…, entity_id='temperature', values={Observable.TEMPERATURE: 21.3}, …)

# 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 members are stable string constants (Observable.TEMPERATURE, Observable.ILLUMINATION, Observable.SWITCH_STATE, Observable.POSITION, Observable.COVER_STATE, …). Each member carries its native unit as Observable.TEMPERATURE.unit == "°C".

Send pipeline — typed instructions

Instructions are sent to devices using typed Instruction subclasses:

from enocean_async import SetCoverPosition, StopCover, SetSwitchOutput

await gateway.send_command(destination=device_eurid, command=SetCoverPosition(position=75))
await gateway.send_command(destination=device_eurid, command=StopCover())
await gateway.send_command(destination=device_eurid, command=SetSwitchOutput(state="on"))

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 → observers → Observation callbacks
  • Full send pipeline: typed InstructionEEPHandler.encode() → ERP1 → ESP3 → serial
  • Device registration with per-device EEP and observer 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 instructions 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 → EntityValue}     ← semantic vocabulary: TEMPERATURE, ILLUMINATION
    │ Observer.decode()  (one call per observer in device.capabilities)
    ├── ScalarObserver(observable=TEMPERATURE)  → reads entities[TEMPERATURE]
    ├── ScalarObserver(observable=ILLUMINATION) → reads entities[ILLUMINATION]
    ├── CoverObserver    → reads entities[POSITION] + entities[ANGLE], infers COVER_STATE
    ├── PushButtonObserver → reads values["R1"], values["EB"], … (stateful)
    └── MetaDataObserver → emits rssi, last_seen, telegram_count
    │ _emit()
    ▼
Observation(device_id, entity_id, values, timestamp, source)
    │ add_observation_callback
    ▼
Application

Send pipeline (instructions)

Application
    │ gateway.send_command(destination, command=SetCoverPosition(position=75))
    ▼
Instruction subclass  (typed dataclass with ClassVar[Instructable] action)
    │ spec.encoders[command.action](command)
    ▼
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 semantics 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.5.0.tar.gz (69.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.5.0-py3-none-any.whl (83.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: enocean_async-0.5.0.tar.gz
  • Upload date:
  • Size: 69.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.5.0.tar.gz
Algorithm Hash digest
SHA256 37fde3d9aaa78577f6fa2ca1e31d005a442b42de743f2e307c3d9c85de38afdc
MD5 28697e93c1cbb3fdc8a618f264904222
BLAKE2b-256 778cfa8e9281bc1c038040f0c663e1bcb1a54d761f284619c8eca7eaf769b015

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: enocean_async-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 83.0 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.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9770dd22c54360bf99d5b24170d6c9318e43db99eb017629cea6d523f2d7c58f
MD5 7697868cd03f5a581b5c427fe40dcdac
BLAKE2b-256 c1409d591f775b37f08d2849a5b5e0c3fc7f61bcb9acea505b5523148c22d30e

See more details on using hashes here.

Provenance

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