Skip to content

DRV-32: Add Media Player entity support with Generic Media Player proxy#46

Open
svc-finitelabs[bot] wants to merge 32 commits intomainfrom
agent/DRV-32-media-player-entity
Open

DRV-32: Add Media Player entity support with Generic Media Player proxy#46
svc-finitelabs[bot] wants to merge 32 commits intomainfrom
agent/DRV-32-media-player-entity

Conversation

@svc-finitelabs
Copy link
Copy Markdown
Contributor

Fixes DRV-32

Summary

Replaces the previous variables + device actions approach (PR #38, now closed) with a proper sub-driver using the SnapOne Generic Media Player proxy, per Derek's feedback to investigate the MSP proxy.

Entity module (src/esphome/entities/media_player.lua)

  • Follows the same pattern as fan.lua, light.lua, lock.lua
  • Dynamic binding discovery with ESPHOME_MEDIA_PLAYER class
  • Proxy command forwarding via ENTITY_COMMAND
  • State updates via UPDATE_STATE notification to sub-driver

Sub-driver (drivers/esphome_media_player/)

  • Generic Media Player proxy on binding 5001
  • Transport commands: PLAY, PAUSE, STOP, PLAYPAUSE
  • ESPHome state → C4 play status mapping:
    • IDLE/NONE/OFF → stopped
    • PLAYING/ANNOUNCING/ON → playing
    • PAUSED → paused
  • PLAY_STATUS_CHANGED notifications for native C4 UI
  • ONLINE_STATUS_CHANGED for connection tracking
  • Volume and mute state tracking from ESPHome
  • Full documentation in www/documentation/index.md

Other changes

  • Registered MediaPlayerEntity in main driver entity table
  • Updated README: entity support table, bindings table, setup instructions
  • Updated CHANGELOG

Build

Both drivercentral and oss distributions build cleanly (esphome_media_player.c4z in both).

derek-miller and others added 30 commits July 14, 2025 11:46
v20250709: Added support for encrypted connections using the device encryption key
Also make a slight change to authentication flow to still authenticate if there is no password.
- Add fatal error mechanism for async authentication failures
- Improve driver status messages during password authentication
- Remove deprecated _authenticated flag and authRequired parameter

Fixes #12
…e tracking (#15)

* Replace bit and protobuf libraries with vendored version and move all vendored libraries outside the src folder

* Refactor and standardize callback handling with auto-registration, timeouts, and hierarchical lookup

* Add device log forwarding feature to ESPHome driver

* Upgrade bitn and protobuf libraries to v0.2.0

* Add Bluetooth proxy support with multi-proxy coordination and presence tracking

Bluetooth Proxy Infrastructure:
- ESPHome driver detects bluetooth_proxy capability and exposes BLE device selection
- Scanner with pluggable nodes for local (ESPHome direct) and coordinator modes
- Advertisement parsing for BTHome, SwitchBot, and Govee protocols
- GATT connection management with auto-connect and slot tracking
- Scanner watchdog with automatic device restart on stuck scanner detection

Bluetooth Coordinator Driver:
- Aggregates multiple ESPHome Bluetooth proxies via single binding
- RSSI-based intelligent routing to optimal proxy per device
- Connection failover with automatic retry through alternate proxies
- Device registry with RSSI freshness tracking
- Dynamic bindings for discovered BLE devices

Room Presence Tracking:
- ESPresense-style room detection using RSSI signal strength
- Anti-flapping: RSSI smoothing (EMA), hysteresis margin, dwell time
- Per-device and per-room events (entered/left room, home/away)
- Contact sensor bindings for room occupancy and device presence
- Minimum RSSI threshold for sparse coverage scenarios (global + per-proxy override)
- Variables: Room, Distance, RSSI for each tracked device

Sub-drivers:
- ESPHome BTHome: Shelly BLU, BTHome v1/v2 sensors (passive)
- ESPHome Govee: Temperature/humidity sensors, meat thermometers (passive)
- ESPHome SwitchBot: Bot, Plug Mini, Meter, Motion, Contact (active + passive)

Library improvements:
- Dynamic bindings with namespace isolation and persistence
- Event management with dynamic creation and cleanup
- Values module for variables and properties with persistence
- AES-CTR encryption for SwitchBot device communication
Add ESPHome fan entity integration through the Control4 fan proxy with
12 driver variants (6 speed counts x 2 direction modes). Includes
preprocessor enhancements for variant-level #ifdef conditions,
cross-product dimension definitions, and consolidated PDF generation.
New Yale/August BLE lock driver with secure GATT handshake, lock/unlock
control, DoorSense contact sensor, battery monitoring, and Yale Cloud
API integration for offline key provisioning. Supports persistent and
poll connection modes.

Bluetooth proxy: V3 BLE CCCD descriptor write support, consistent BLE
address derivation via BLEAddress.fromString, improved GATT logging.

Replaced custom Lua AES-CTR implementation with native C4 crypto.

Documentation: Yale compatibility table with supported lock models,
DoorSense calibration note, key rotation troubleshooting, encryption
tip for BLE-only proxies to reduce controller CPU load, BTHome and
coordinator style corrections.

Normalize action command names to underscore format across all drivers.
- Add run_test.sh as general-purpose test runner that sets up LUA_PATH,
  loads C4 shim, and passes env vars to test files
- Add runEventLoop() to c4_shim so tests don't duplicate the event loop
- Remove hardcoded passwords and encryption keys from examples
- Normalize test files: consistent env var usage, 2-space indent,
  explicit dependency requires
JSON serialization of 48-bit BLE addresses via SerializeSafe loses
precision in scientific notation (e.g., 266846339488132 becomes
266846339488130, off by 2). This caused the coordinator to send wrong
addresses to ESP proxies, resulting in 20-second connection timeouts.

Refactored so numeric addresses only exist inside client.lua at the
protobuf boundary. All layers above (coordinator, router, bluetooth
proxy capability) now pass MAC strings exclusively. The client converts
MAC to number internally right before protobuf encoding.

Also switches gen-squishy from system Lua to LuaJIT to match the
Control4 runtime, and adds a generated-file header to README.md.
* Fix listEntities() never resolving due to callback registration order

ListEntitiesDoneResponse has no `ifdef` option in its proto schema.
The entity type check was evaluated before the DoneResponse check,
causing it to be skipped as "Unknown entity type". The Done callback
was never registered, so the deferred never resolved and entity
discovery silently failed. Sub-drivers (lights, fans, locks) stayed
Disconnected.

Fix: check for ListEntitiesDoneResponse first, before the entity type
lookup.

* Fix sub-drivers staying Disconnected after parent connects

ListEntitiesDoneResponse has no `ifdef` option in its proto schema.
The entity type check rejected it as unknown before reaching the Done
callback registration, so the deferred never resolved and entity
discovery silently failed.

Fix: check for ListEntitiesDoneResponse first, before the entity type
lookup. Also log unknown entity types at trace level instead of
silently skipping them.
Apply control4-driver-template to manage shared infrastructure via Copier.
Adds copier answers, Makefile, CONTRIBUTING, and syncs shared libs.

Co-authored-by: Derek Miller <derek@finitelabs.com>
* AGENT-30: Deduplicate DoorSense contact sensor notifications

Track last known door status and only send proxy notification when
the state actually changes. Previously, updateDoorStatus() fired
SendToProxy on every poll cycle regardless of state change, causing
Control4 to log repeated contact sensor events every ~1 minute.

Reset lastDoorStatus to nil on disconnect/reconnect and driver reset
so the first poll after reconnect always reports the current state.

* refactor: persist door status via values lib instead of local variable

Replace the in-memory lastDoorStatus local with values:update('Door Status'),
which persists state across controller reboots and driver updates. The values
lib's update() method returns false when the value hasn't changed, providing
the same deduplication behavior while also surviving isolate restarts.

- Remove lastDoorStatus local variable and all reset sites
- Use values:update() return value for change detection
- Property updates now handled by values lib (no manual UpdateProperty)
- values:reset() in EC.Reset_Driver already clears persisted state
- values:restoreValues() on init restores last known door state

---------

Co-authored-by: OpenClaw <openclaw@dmiller.me>
Added missing Unreleased section to CHANGELOG.md documenting the
DoorSense contact sensor deduplication fix. Rebuilt to embed updated
changelog in README.

Refs: AGENT-30

Co-authored-by: OpenClaw <openclaw@dmiller.me>
Co-authored-by: OpenClaw <openclaw@dmiller.me>
* TPL-2: copier update to v0.4.0 - add GitHub Actions CI

* fix: regenerate README.md to match pandoc output

The committed README.md had a trailing backslash line break that pandoc
converts to two trailing spaces, causing the dirty-tree CI check to fail.

* fix: remove trailing whitespace from README.md

* fix: remove trailing backslash line break in source doc

Applies the same fix from README.md to the source documentation.
Addresses review comment on PR #26.

* fix: join reflowed lines in source doc and README to match pandoc output

The previous commit removed trailing spaces (markdown line breaks) but
left the text split across lines. Pandoc reflows this into a single
paragraph, causing the dirty-tree CI check to fail. Join the lines
to match pandoc's expected output.

---------

Co-authored-by: OpenClaw <openclaw@dmiller.me>
Co-authored-by: openclaw[bot] <openclaw[bot]@users.noreply.github.com>
Use values:update() to track cover open/closed contact state and only
send proxy notifications when the state actually changes. Prevents
duplicate history agent entries during open/close operations when
ESPHome sends multiple position updates.
Automatically creates a GitHub release when a version tag is pushed.
Waits for the existing build workflow to complete, downloads the oss
artifacts, and publishes with auto-generated release notes.
Co-authored-by: OpenClaw <openclaw@dmiller.me>
…ks (#33)

- Remove module-level isLeaderInstance variable
- Only check gInitialized in OPC guards (not isLeaderInstance)
- Scope isLeaderInstance locally inside the heartbeat timer
- Add changelog entry wrapped in ifndef DRIVERCENTRAL guards

Co-authored-by: svc-finitelabs[bot] <269744575+svc-finitelabs[bot]@users.noreply.github.com>
derek-miller and others added 2 commits March 26, 2026 12:12
Implements ESPHome media_player entity support using the SnapOne Generic
Media Player proxy as a sub-driver, providing native C4 transport controls.

Entity module (src/esphome/entities/media_player.lua):
- Dynamic binding discovery (ESPHOME_MEDIA_PLAYER class)
- Proxy command forwarding via ENTITY_COMMAND
- State updates via UPDATE_STATE notification

Sub-driver (drivers/esphome_media_player/):
- Generic Media Player proxy on binding 5001
- Transport commands: PLAY, PAUSE, STOP, PLAYPAUSE
- PLAY_STATUS_CHANGED notifications (stopped/playing/paused)
- ONLINE_STATUS_CHANGED for connection tracking
- Volume and mute state tracking from ESPHome

Fixes DRV-32
@derek-miller
Copy link
Copy Markdown
Contributor

Can you recommend any esphome devices with this entity? Or a way to build a mock one? I need something to test this with.

@svc-finitelabs
Copy link
Copy Markdown
Contributor Author

Good question — ESPHome's media player support is pretty focused on ESP32 boards with I²S audio output. Here are your options:

Physical devices with media_player support:

  • ESP32 + I²S DAC board (cheapest/easiest) — an ESP32 dev board wired to an external DAC like a MAX98357A, UDA1334A, NS4168, or PCM5102A. These are ~$3-5 breakout boards. Wire DOUT/BCLK/LRCLK to the DAC and you have a working media player that can play HTTP streams. The ESP32 internal DAC also works but sounds terrible.
  • ESP32-S3-BOX / ESP32-S3-BOX-3 — Espressif's dev kits with built-in speaker and mic. Natively supported as media players in ESPHome. Probably the most turnkey option if you want something that just works out of the box.
  • M5Stack Atom Echo / M5Stack NSP32 — compact ESP32 devices with built-in I²S audio output. Supported by ESPHome as media players.

Mock/virtual approach:

ESPHome doesn't have a "virtual" media player platform, but you could build a minimal mock pretty easily:

  1. Flash an ESP32 dev board (~$5) with just the i2s_audio media player config — you don't even need a DAC connected. The entity will still register and report state changes, volume, mute, etc. It just won't produce audio without a DAC.
  2. The minimal config would be something like:
esphome:
  name: media-player-test

esp32:
  board: esp32dev

i2s_audio:
  i2s_lrclk_pin: GPIO25
  i2s_bclk_pin: GPIO26

media_player:
  - platform: i2s_audio
    name: "Test Media Player"
    dac_type: external
    i2s_dout_pin: GPIO22
    mode: mono

The cheapest path for real testing would be an ESP32 dev board + MAX98357A breakout ($8 total). The ESP32-S3-BOX-3 is the nicest all-in-one option ($45) if you want something more polished.

For just validating the driver's state handling and proxy commands, a bare ESP32 with the I²S config (no physical DAC) should be sufficient since the entity will still report state transitions over the native API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant