AirConditioner: coalesce rapid control() calls instead of silently dropping them#32
Open
t-jonesy wants to merge 1 commit intodudanov:mainfrom
Open
AirConditioner: coalesce rapid control() calls instead of silently dropping them#32t-jonesy wants to merge 1 commit intodudanov:mainfrom
t-jonesy wants to merge 1 commit intodudanov:mainfrom
Conversation
control() previously short-circuited with `if (m_sendControl) return;` while a previous control was in flight. Any control() call arriving in that ~1-2s window was silently discarded with no log or error. This breaks the common Home Assistant pattern where an automation fires several climate service calls in quick succession. ESPHome's midea wrapper translates each HA set_hvac_mode / set_preset_mode / set_temperature / set_fan_mode into a separate control() call. With four calls within ~100 ms, only the first lands; the others vanish. The gate was conflating two concerns -- preventing stale-diff against mid-update internal state, and preventing reentrancy from the response callback chain -- by also throwing away the user's input. Replace the silent drop with coalescing: when control() arrives while a request is in flight, merge its set fields into m_pendingControl (latest-writer-wins per field). After the in-flight request's onSuccess / onError fires (by which time m_readStatus has refreshed m_mode / m_preset / etc. from the AC's response), m_flushPending() re-invokes control() with the merged struct. The follow-up therefore diffs against fresh state, preserving both original guarantees while no longer dropping input. Coalesces N rapid calls into at most 2 UART frames (initial + atomic merged follow-up).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
AirConditioner::control()short-circuits withif (m_sendControl) return;while a previous control is in flight (m_sendControlis set when a frame is queued and only cleared when the AC's response handler fires, ~1–2 s round-trip). Anycontrol()call arriving inside that window is silently discarded — no log, no error, no retry.This is fine for slow interactive use (UI clicks seconds apart) but is broken for the common Home Assistant pattern where an automation fires several climate service calls in quick succession. The ESPHome
mideawrapper translates each HAset_hvac_mode/set_preset_mode/set_temperature/set_fan_modeinto a separatecontrol()call. With four calls within ~100 ms, only the first lands; the other three vanish.I observed this on a Midea AC controlled by ESPHome 2026.4.4 driving this library — an automation that set
mode=cool, preset=none, target=67, fan=highwould settle tocool/67/auto/ecoinstead ofcool/67/high/none. Adding 5–8 sdelay:blocks between every action in the HA automation is a workaround but is brittle, slow, and shouldn't be the user's responsibility.Root cause
m_sendControlwas conflating two separate concerns:Controlagainstm_status/m_mode/m_presetwhile they're mid-update from a pending response.control()from inside the response callback chain.Both are legitimate, but neither argues for dropping the user's intent. The gate solved (1) and (2) by also throwing away the input.
Fix
Replace the silent drop with coalescing. When
control()is called while a request is in flight:Control's set fields into am_pendingControlmember (latest-writer-wins per field).onSuccess/onErrorfires (i.e. oncem_readStatushas refreshedm_mode/m_preset/ etc. from the AC's response),m_flushPending()re-invokescontrol()with the merged struct.This preserves both original guarantees:
control()runs only after fresh state is loaded — no stale diff.control().It also collapses N rapid calls into at most 2 UART frames (initial + atomic merged follow-up), which is strictly nicer for the protocol.
Testing
Tested on an ESP8266 (ESP12-E) controlling a Midea split AC via ESPHome's `midea` platform with `period: 3s, timeout: 2s, num_attempts: 1`:
No behavior change for the existing single-call usage. `library.json` not bumped — leaving versioning to you.