Skip to main content
Part of MajorDom — the next-gen smart home

integration-esphome

A MajorDom integration — bridges ESPHome devices into the MajorDom language.

Built for the MajorDom Hub, but it doesn't need it: this is a standalone, standardized library for ESPHome that you can use on its own (see Run it standalone below). Built on the MajorDom Integration SDK. The entry point is ESPHomeController (majordom_esphome/controller.py), which the Hub — or the SDK's dev runner — instantiates and drives through its lifecycle: discovery → pairing → commands → teardown.

Documentation

Full integration-author docs — the controller lifecycle, data models, storing data, discovery, and a worked example — live at docs.majordom.io.

Development

poetry install && poetry run poe install
Task Description
poe check Full quality pipeline (ruff, ty, pytest, poetry build/check)
poe check --ci Same, plus git diff --exit-code

Work lands on develop; master is protected and released via Actions → Release.

Tests drive the controller with the SDK's test doubles against a virtual ESPHome device: a native binary built from tests/virtual/sketches/*.yaml (ESPHome host platform, one entity of every supported kind, with and without API encryption) — no physical hardware required. The binaries are built on first use and need the ESPHome CLI:

pipx install esphome && poetry run python -m tests.virtual.build

Run it standalone (without the Hub)

majordom-esphome is a standalone library — import it into your own app, or run just this integration interactively (discover, pair, control, and inspect devices from a prompt) with no Hub. It needs ESPHome devices reachable on the local network (Wi-Fi or Ethernet).

See Standalone mode for the interactive CLI, watch mode, and the programmatic API.

Supported transports

The integration speaks the ESPHome native API over TCP (port 6053 by default), with or without Noise encryption. Devices must have the api: component enabled in their YAML; an API-less device (e.g. web server only) is not supported.

About this integration

  • Protocol / platform: ESPHome native API (aioesphomeapi).
  • Transport(s): TCP/IP (Wi-Fi, Ethernet); devices are found via mDNS (_esphomelib._tcp).
  • Supported entities: light (on/off, brightness, RGB), switch, sensor, binary sensor, text sensor, number, select, button, cover (position), fan (on/off), climate (mode, targets). Every other entity the library knows (lock, valve, text, alarm panel, update, ...) is mapped generically from the library's own types (see below); cameras and events are skipped.
  • Credentials needed to pair: the API encryption key (type secret) if the node has one configured; otherwise none.

Required harness

  • Hardware adapters: none — ESPHome devices are self-contained and connect over the network.
  • Third-party software services: none — the integration speaks directly to ESPHome devices.
  • OS / permissions: network access to the ESPHome devices (same LAN or routable subnet); mDNS/SSDP may be required for discovery depending on configuration.

Protocol stack (OSI)

Every integration is two things stacked: the MajorDom integration layer — mapping the protocol to MajorDom's domain model — sitting on top of the protocol stack it bridges. The top layer is always this repo.

Layer Protocol Implemented by
MajorDom integration maps ESPHome ↔ MajorDom domain model this repo, always
Application (7) ESPHome Native API aioesphomeapi
Transport (4) TCP OS / device firmware
Network (3) IP (IPv4 / IPv6) OS / device firmware
Data link / Physical (1–2) Wi-Fi (802.11) or Ethernet device hardware

Progress

Two checklists. An item is ticked only when a test in tests/ proves it.

Implementation — makes the integration functional:

  • Discovery service registered via self.dependencies.zeroconf_discovery_service; the cancel closure is called in stop
  • Discovery listener methods fire when nodes appear, change and leave, and the controller calls the matching self.dependencies.output.controller_did_*_discovery
  • Paired devices reconnect when the Hub restarts (controller_did_connect_device is called)
  • start_pairing_window: not needed, mDNS discovery is always on (SDK default is a no-op)
  • Device pairing, including the encryption key (CredentialsType.secret)
  • Device schema is mapped from the entities the device reports: parameters, units, limits and valid values
  • Hub → Device control (send_command)
  • Device → Hub event subscription (controller_did_receive_events on every state change)
  • identify: ESPHome has no identify action, so it is a documented no-op
  • unpair
  • fetch reports the current value of every parameter in one batch
  • Paired devices going offline / coming back online while the Hub is running update device.available and last_error, and call controller_did_lose_device / controller_did_connect_device
  • Graceful shutdown in stop: discovery stopped, connections closed, no tasks left running
  • Tests pass against a virtual ESPHome device (tests/)
  • README matches the code

Quality — makes it reliable and maintainable (the bar for release):

  • Recovers automatically from connection loss / offline device / restarted device: retried with exponential backoff (1 s up to 30 s), no manual restart
  • No exception escapes discovery handlers, state callbacks or background tasks
  • Failures are surfaced on the device (available / last_error) in plain language and cleared on recovery; a rejected encryption key is reported as such
  • Fully asynchronous: no blocking I/O on the event loop
  • Stable identity: device and parameter UUIDs derive through the SDK helpers (device_uuid, parameter_uuid) from the node's MAC address and the entity's object_id
  • End-to-end tests drive discovery (over a stubbed zeroconf, never the real network) → pair → state → command → fetch → restart → outage → unpair against a virtual device, with and without encryption
  • Failure paths tested: unreachable device, wrong or missing key, unsupported credentials, command on a read-only parameter, out-of-range values, offline device
  • Broad device coverage: the entities above by hand, every other entity generically (tested with a lock, valve, text, date and alarm panel)
  • Readable & structured: conversion logic in a mapper, models separated
  • Efficient: subscriptions instead of polling; states are delivered in order
  • Diagnosable: connection failures and unexpected errors are logged with their cause
  • Parameter metadata: visibility follows the entity category, and main_parameter is a one-tap toggle
  • Owned: a listed maintainer who keeps it working as ESPHome and the library evolve

Parameter mapping

Parameters are built from what the device reports (list_entities_services), never from fixed defaults:

Entity Parameters
switch, fan state (bool)
light state, brightness (%) if the light dims, color_hue (°) and color_saturation (%) if it supports RGB; settings: transition_length (s, kept by the integration and added to every later command of the light) and flash (a struct command with its length as argument)
cover position (%), operation (IDLE / IS_OPENING / IS_CLOSING, read-only)
climate mode (enum of the modes it supports), current_temperature, target_temperature or target_temperature_low/high
number state with the device's min / max / step
select state as the option index (enum, valid_values labels are the options)
sensor, binary sensor, text sensor state (read-only)
button state (fires the press)

Brightness, positions and saturation are percentages (0–100, ESPHome reports 0–1); colour is hue in degrees plus saturation, the model the Matter and Zigbee integrations use, converted from ESPHome's RGB. Enums are integers with string labels in valid_values, labelled by the ESPHome enum names. Units come from the entity's own unit string, else from its device class. Entities in the config / diagnostic category are setting, and entities disabled by default are system.

Remaining fields

The hand-written mapping above gives the everyday parameters. Every other state or command field an entity supports is added by the generic mapping as a setting parameter (majordom_esphome/generic.py), so nothing the library reports is dropped silently: a fan's speed level, direction, oscillation and presets; a cover's tilt and stop; a light's colour temperature, white and effects; a climate's action, fan, swing and preset modes and humidity. A water heater's state bit mask becomes on and away switches; a date or time is one struct parameter whose fields are its parts; a command that needs a code (a lock or alarm panel that requires one) is a command_with_code struct command with the command and the code as arguments, next to a code setting holding a default code: a code typed with the command wins, else the default is sent with every command of the entity, so one-tap actions and automations work once the user opts in. Commands with arguments are struct parameters with the arguments as fields, as the SDK describes. A field is only added when the entity says it supports it, enums are limited to the supported members, option lists (effects, presets) become enums like a select, and the limits the entity reports (speed count, mireds, humidity) are used. The few fields that are exposed under another name or not at all (RGB channels, deprecated fields) are listed with the reason in UNMAPPED in the catalogue, which fails when a new field appears that is neither mapped nor explained.

Generic mapping

Entities without a hand-written mapping get parameters from aioesphomeapi's own types (majordom_esphome/generic.py): one parameter per field of the entity's state (read-only, unless the entity's command accepts it) and one per command argument that can be sent on its own. bool, int, float and str map directly, the library's integer enums become enum parameters labelled with their member names, fractions such as a position or volume become percentages, and a command-only flag (a valve's stop) becomes a button. Commands with several required arguments (a date) are read-only. A hand-written mapping wins wherever the generic result is not good enough for the user.

Entity catalogue

tests/test_catalogue.py and tests/virtual/catalogue.py mirror the Matter integration's virtual-device catalogue. The list of entity kinds is whatever the installed aioesphomeapi knows. Fast checks (always run) fail when a kind is neither mapped by the integration nor skipped on purpose, or has neither a recipe nor a reason it cannot run on the host node. Recipes are written by hand, never guessed: each one switches on every capability of its entity and comes with the exact parameters it must produce (EXPECTED) and every state or command field that is deliberately not exposed, with the reason (UNMAPPED), so a field ESPHome adds later fails the sweep until it is mapped or explained. The sweep (poetry run pytest -m catalogue) builds one virtual node per recipe in parallel, pairs it, checks its parameters against EXPECTED and UNMAPPED, reads its states, sends a valid command to every control parameter and checks that the connection survives and every value has the declared type. .github/workflows/canary.yml runs everything monthly against the latest ESPHome and aioesphomeapi and goes red when upstream adds something unsupported.

Notes

The device id derives from the node's MAC address (mDNS mac TXT record) via the SDK's UUID helpers, so ids are stable across restarts and namespaced per integration.

ESPHome devices must have the api: component enabled in their YAML.

Connecting: a node is reached through its hostname first, then through the IP addresses it announced. The first address that works moves to the front and the ones that failed move to the end, so the next connection starts with what worked last time. When a paired node announces itself again (e.g. after a DHCP change) its address list is refreshed.

License

See LICENSE. For commercial licensing or partnership inquiries regarding MajorDom, contact us via parker-industries.org/partnership.

Metadata

Release files for majordom-esphome 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for majordom-esphome 0.1.2
File Size Uploaded
majordom_esphome-0.1.2.tar.gz 32.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for majordom-esphome 0.1.2
File Interpreter ABI Platform
majordom_esphome-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 62.9 kB

Release files / majordom_esphome-0.1.2.tar.gz

Download URL majordom_esphome-0.1.2.tar.gz
Size 32.3 kB
Tags Source
SHA-256 checksum
How to use checksums
a357fc66b0ba10256c6c8855768a42b927f06dfd756e096262069402ca3c1238
BLAKE2b-256 checksum
How to use checksums
87d559c18b438c2f8cb62a845bbc657a9747625099fa0caee0e97dd5ac3973ff
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release files / majordom_esphome-0.1.2-py3-none-any.whl

Download URL majordom_esphome-0.1.2-py3-none-any.whl
Size 30.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bed40f5d8c5de7938be54165740eb55966e17024152936c9581fc8ab4cf07afc
BLAKE2b-256 checksum
How to use checksums
b8f50822b1a99648c40d7a350ce2aa7595a68c782f23d95e9d1076b6c52cd23b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page