Duplicate MQTT event handlers accumulate on integration reload, causing multiple service calls per button press#174
Open
Marco1971Repo wants to merge 1 commit into
Open
Conversation
fvanroie
approved these changes
May 23, 2026
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.
I am not very experienced with Python or Home Assistant internals, and I found this fix with the help of AI. It seems to solve the problem on my setup. I had previously mentioned this issue in the openHASP firmware discussions (#986) but received no response. I hope this can be of some help to the project.
Bug: Duplicate MQTT event handlers accumulate on integration reload, causing multiple service calls per button press
Summary
When the openHASP integration is reloaded in Home Assistant (without a full restart), MQTT event handlers for plate objects are registered multiple times. As a result, a single button press on the display triggers the associated action N times, where N equals the number of times the integration has been reloaded since the last full HA restart.
Environment
openhasp.yamlSteps to Reproduce
openhasp.yamlThe number of duplicate invocations increases by one with each reload.
Root Cause
The bug is in
async_added_to_hass()inside theSwitchPlateclass, specifically in thelwt_message_receivedcallback (__init__.py).When the integration is reloaded:
async_will_remove_from_hass()is called on the oldSwitchPlateinstance, which correctly unsubscribes all plate-level MQTT topics (/LWT,/state/page, etc.)SwitchPlateinstance is created with fresh_objects, each with empty_subscriptions = []async_added_to_hass()is called on the new instance, which re-subscribes to/LWTThe problem arises at step 3: the physical plate never went offline during the HA reload, so the MQTT broker still holds the retained
onlineLWT message. The moment the new instance subscribes to the/LWTtopic, the broker immediately replays the retained message, triggeringlwt_message_receivedwithHASP_ONLINE.This causes
enable_object()to be called on every object, registering a new MQTT subscription for each one.However, on the previous full restart cycle,
enable_object()was already called (also triggered by the retained LWT message), and those subscriptions were never cleaned up — becauseasync_will_remove_from_hass()only unsubscribes the plate-level topics stored inself._subscriptions, not the per-object subscriptions stored in eachHASPObject._subscriptions.The per-object subscriptions are only cleaned up when the plate goes offline (the
elsebranch oflwt_message_received), which never happens during a simple reload.Relevant code (before fix)
Note that
disable_object()already exists and works correctly — it is called in theHASP_OFFLINEbranch and inasync_will_remove_from_hass(). It was simply not being called beforeenable_object()in the online branch.Fix
Call
disable_object()on all objects before callingenable_object()when aHASP_ONLINELWT message is received. This guarantees that any previously registered subscriptions are always removed before new ones are added, making the operation idempotent regardless of how many times the integration is reloaded.Since
disable_object()clears_subscriptions = []after unsubscribing, calling it on a freshly created object (with no existing subscriptions) is completely safe and has no side effects.Fix applied to
__init__.pyImpact
Testing
custom_components/openhasp/__init__.py