From eeb0b5a9b70d76753ce9a94f547de0b747af05bc Mon Sep 17 00:00:00 2001 From: AutoCoder Date: Wed, 20 May 2026 13:10:35 +0000 Subject: [PATCH] LevoitSwitch: set has_state on publish to match Select/Number behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch::publish_state in core ESPHome sets the public `state` field and fires callbacks but does not flip has_state_ on EntityBase. Select::publish_state and Number::publish_state both call set_has_state(true). The asymmetry breaks any caller that uses `if (entity->has_state())` to decide whether the entity carries a fresh user-supplied value worth preferring over a cached default — the check works for select / number but always returns false for switch, even after the user has just toggled it and even after the decoder published the device's reported value. Two paths fixed: LevoitSwitch::write_state — explicit set_has_state(true) after publish_state(state). Handles the user-toggle path (HA service → Switch::turn_on → LevoitSwitch::write_state → on_switch_command). Levoit::publish_switch — set_has_state(true) hoisted above the dedup early-return `if (sw->state == state)`. A decoder publish whose value matches the entity's default (e.g. first decoded false against default sw->state=false) would otherwise skip publish_state entirely and leave has_state_ at false for the rest of the session. No behavior change for callers that don't use has_state(): the public `state` field, callbacks, and ControllerRegistry notifications work exactly as before. --- components/levoit/levoit.cpp | 7 +++++++ components/levoit/switch/levoit_switch.cpp | 10 +++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/components/levoit/levoit.cpp b/components/levoit/levoit.cpp index 46c9979..a7ee18f 100644 --- a/components/levoit/levoit.cpp +++ b/components/levoit/levoit.cpp @@ -100,6 +100,13 @@ namespace esphome auto *sw = switches_[st_idx_(type)]; if (!sw) return; + // Flip has_state_ before the dedup early-return below: a + // decoder publish whose value matches the entity's default + // (false against default sw->state=false) would otherwise + // skip publish_state and leave has_state() returning false + // forever. See companion fix in LevoitSwitch::write_state + // for the rationale on why Switch needs this explicitly. + sw->set_has_state(true); if (sw->state == state) return; sw->publish_state(state); diff --git a/components/levoit/switch/levoit_switch.cpp b/components/levoit/switch/levoit_switch.cpp index 14412fc..8d90b63 100644 --- a/components/levoit/switch/levoit_switch.cpp +++ b/components/levoit/switch/levoit_switch.cpp @@ -12,8 +12,16 @@ namespace esphome } void LevoitSwitch::write_state(bool state) { - // Optimistic update for HA UI + // Optimistic update for HA UI. Switch::publish_state in core + // ESPHome sets the public `state` field and fires callbacks but + // does not flip has_state_ — unlike Select::publish_state and + // Number::publish_state, which both call set_has_state(true). + // Set it explicitly so `if (entity->has_state())` guards in + // callers (e.g. command builders that distinguish "user just + // toggled this" from "never published") behave uniformly across + // switch / number / select. this->publish_state(state); + this->set_has_state(true); if (!parent_) {