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_) {