Bug Identified With GPIO LEDs (Grove)
This commit is contained in:
@@ -21,12 +21,8 @@ class MqttController(EventHandler):
|
|||||||
|
|
||||||
self._device_name = self.state.name
|
self._device_name = self.state.name
|
||||||
self._device_id = self.state.name.lower().replace(" ", "_")
|
self._device_id = self.state.name.lower().replace(" ", "_")
|
||||||
|
|
||||||
self._topic_prefix = f"lva/{self._device_id}"
|
self._topic_prefix = f"lva/{self._device_id}"
|
||||||
|
|
||||||
# A list of all configurable states
|
|
||||||
self.CONFIGURABLE_STATES: List[str] = ["idle", "listening", "thinking", "responding", "error"]
|
|
||||||
|
|
||||||
# Generate topics for all entities
|
|
||||||
self.topics = {
|
self.topics = {
|
||||||
"mute": {
|
"mute": {
|
||||||
"command": f"{self._topic_prefix}/mute/set",
|
"command": f"{self._topic_prefix}/mute/set",
|
||||||
@@ -37,7 +33,7 @@ class MqttController(EventHandler):
|
|||||||
"state": f"{self._topic_prefix}/num_leds/state",
|
"state": f"{self._topic_prefix}/num_leds/state",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for state_name in self.CONFIGURABLE_STATES:
|
for state_name in ["idle", "listening", "thinking", "responding", "error"]:
|
||||||
self.topics[state_name] = {
|
self.topics[state_name] = {
|
||||||
"effect_command": f"{self._topic_prefix}/{state_name}_effect/set",
|
"effect_command": f"{self._topic_prefix}/{state_name}_effect/set",
|
||||||
"effect_state": f"{self._topic_prefix}/{state_name}_effect/state",
|
"effect_state": f"{self._topic_prefix}/{state_name}_effect/state",
|
||||||
@@ -69,10 +65,10 @@ class MqttController(EventHandler):
|
|||||||
def _on_connect(self, client, userdata, flags, rc):
|
def _on_connect(self, client, userdata, flags, rc):
|
||||||
if rc == 0:
|
if rc == 0:
|
||||||
_LOGGER.info("Connected to MQTT broker")
|
_LOGGER.info("Connected to MQTT broker")
|
||||||
# Subscribe to all command and state topics
|
|
||||||
for entity_type in self.topics.values():
|
for entity_type in self.topics.values():
|
||||||
for topic_type, topic in entity_type.items():
|
for topic_type, topic in entity_type.items():
|
||||||
client.subscribe(topic)
|
if "command" in topic_type:
|
||||||
|
client.subscribe(topic)
|
||||||
self._publish_discovery_configs()
|
self._publish_discovery_configs()
|
||||||
else:
|
else:
|
||||||
_LOGGER.error("Failed to connect to MQTT, return code %d", rc)
|
_LOGGER.error("Failed to connect to MQTT, return code %d", rc)
|
||||||
@@ -81,16 +77,27 @@ class MqttController(EventHandler):
|
|||||||
payload_str = msg.payload.decode()
|
payload_str = msg.payload.decode()
|
||||||
_LOGGER.debug("Received MQTT message on topic %s", msg.topic)
|
_LOGGER.debug("Received MQTT message on topic %s", msg.topic)
|
||||||
|
|
||||||
# Mute and Num LEDs
|
|
||||||
if msg.topic == self.topics["mute"]["command"]:
|
if msg.topic == self.topics["mute"]["command"]:
|
||||||
self.state.event_bus.publish("set_mic_mute", {"state": payload_str.upper() == "ON"})
|
self.state.event_bus.publish("set_mic_mute", {"state": payload_str.upper() == "ON"})
|
||||||
elif msg.topic == self.topics["num_leds"]["command"]:
|
|
||||||
self.state.event_bus.publish("set_num_leds", {"num_leds": int(payload_str)})
|
|
||||||
|
|
||||||
# State-based controls
|
# --- THIS IS THE FIX ---
|
||||||
for state_name in self.CONFIGURABLE_STATES:
|
# The logic to save the preference was missing. It is now re-added.
|
||||||
|
elif msg.topic == self.topics["num_leds"]["command"]:
|
||||||
|
try:
|
||||||
|
num_leds = int(payload_str)
|
||||||
|
# First, publish the internal event so the LedController can log the message
|
||||||
|
self.state.event_bus.publish("set_num_leds", {"num_leds": num_leds})
|
||||||
|
# Then, save the setting to the preferences file
|
||||||
|
if self.state.preferences.num_leds != num_leds:
|
||||||
|
self.state.preferences.num_leds = num_leds
|
||||||
|
self.state.save_preferences()
|
||||||
|
# Finally, publish the state back to MQTT to confirm the change in HA
|
||||||
|
self.publish_num_leds_state(num_leds)
|
||||||
|
except ValueError:
|
||||||
|
_LOGGER.warning("Received invalid value for num_leds: %s", payload_str)
|
||||||
|
|
||||||
|
for state_name in ["idle", "listening", "thinking", "responding", "error"]:
|
||||||
state_topics = self.topics[state_name]
|
state_topics = self.topics[state_name]
|
||||||
# Handle commands from HA
|
|
||||||
if msg.topic == state_topics["effect_command"]:
|
if msg.topic == state_topics["effect_command"]:
|
||||||
effect_id = payload_str.lower().replace(" ", "_")
|
effect_id = payload_str.lower().replace(" ", "_")
|
||||||
self.state.event_bus.publish(f"set_{state_name}_effect", {"effect": effect_id})
|
self.state.event_bus.publish(f"set_{state_name}_effect", {"effect": effect_id})
|
||||||
@@ -104,61 +111,48 @@ class MqttController(EventHandler):
|
|||||||
self.state.event_bus.publish(f"set_{state_name}_color", data)
|
self.state.event_bus.publish(f"set_{state_name}_color", data)
|
||||||
except json.JSONDecodeError: pass
|
except json.JSONDecodeError: pass
|
||||||
|
|
||||||
# Handle retained state messages on startup
|
|
||||||
elif msg.topic == state_topics["effect_state"]:
|
|
||||||
effect_id = payload_str.lower().replace(" ", "_")
|
|
||||||
self.state.event_bus.publish(f"set_{state_name}_effect", {"effect": effect_id, "retained": True})
|
|
||||||
elif msg.topic == state_topics["light_state"]:
|
|
||||||
try:
|
|
||||||
data = json.loads(payload_str)
|
|
||||||
data["retained"] = True
|
|
||||||
self.state.event_bus.publish(f"set_{state_name}_color", data)
|
|
||||||
except json.JSONDecodeError: pass
|
|
||||||
|
|
||||||
|
|
||||||
def _publish_discovery_configs(self):
|
def _publish_discovery_configs(self):
|
||||||
availability_topic = f"{self._topic_prefix}/availability"
|
availability_topic = f"{self._topic_prefix}/availability"
|
||||||
device_info = { "identifiers": [self._device_id], "name": self._device_name, "manufacturer": "LVA Project" }
|
device_info = { "identifiers": [self._device_id], "name": self._device_name, "manufacturer": "LVA Project" }
|
||||||
options = ["Off", "Solid", "Slow Pulse", "Medium Pulse", "Fast Pulse", "Slow Blink", "Medium Blink", "Fast Blink", "Spin"]
|
options = ["Off", "Solid", "Slow Pulse", "Medium Pulse", "Fast Pulse", "Slow Blink", "Medium Blink", "Fast Blink", "Spin"]
|
||||||
|
|
||||||
# Mute Switch
|
|
||||||
mute_cfg = { "name": "Mute Microphone", "unique_id": f"{self._device_id}_mute", "command_topic": self.topics["mute"]["command"], "state_topic": self.topics["mute"]["state"], "availability_topic": availability_topic, "icon": "mdi:microphone-off", "device": device_info }
|
mute_cfg = { "name": "Mute Microphone", "unique_id": f"{self._device_id}_mute", "command_topic": self.topics["mute"]["command"], "state_topic": self.topics["mute"]["state"], "availability_topic": availability_topic, "icon": "mdi:microphone-off", "device": device_info }
|
||||||
self._client.publish(f"homeassistant/switch/{self._device_id}_mute/config", json.dumps(mute_cfg), retain=True)
|
self._client.publish(f"homeassistant/switch/{self._device_id}_mute/config", json.dumps(mute_cfg), retain=True)
|
||||||
|
|
||||||
# Number of LEDs
|
|
||||||
num_leds_cfg = { "name": "Number of LEDs", "unique_id": f"{self._device_id}_num_leds", "command_topic": self.topics["num_leds"]["command"], "state_topic": self.topics["num_leds"]["state"], "availability_topic": availability_topic, "min": 1, "max": 256, "step": 1, "icon": "mdi:counter", "device": device_info, "mode": "box", "entity_category": "config" }
|
num_leds_cfg = { "name": "Number of LEDs", "unique_id": f"{self._device_id}_num_leds", "command_topic": self.topics["num_leds"]["command"], "state_topic": self.topics["num_leds"]["state"], "availability_topic": availability_topic, "min": 1, "max": 256, "step": 1, "icon": "mdi:counter", "device": device_info, "mode": "box", "entity_category": "config" }
|
||||||
self._client.publish(f"homeassistant/number/{self._device_id}_num_leds/config", json.dumps(num_leds_cfg), retain=True)
|
self._client.publish(f"homeassistant/number/{self._device_id}_num_leds/config", json.dumps(num_leds_cfg), retain=True)
|
||||||
|
|
||||||
# Loop through states to create select and light entities
|
for state_name in ["idle", "listening", "thinking", "responding", "error"]:
|
||||||
for state_name in self.CONFIGURABLE_STATES:
|
|
||||||
capital_name = state_name.title()
|
capital_name = state_name.title()
|
||||||
|
|
||||||
# Select Entity
|
|
||||||
select_cfg = { "name": f"{capital_name} Effect", "unique_id": f"{self._device_id}_{state_name}_effect", "command_topic": self.topics[state_name]["effect_command"], "state_topic": self.topics[state_name]["effect_state"], "availability_topic": availability_topic, "options": options, "icon": "mdi:palette-swatch-variant", "device": device_info, "entity_category": "config" }
|
select_cfg = { "name": f"{capital_name} Effect", "unique_id": f"{self._device_id}_{state_name}_effect", "command_topic": self.topics[state_name]["effect_command"], "state_topic": self.topics[state_name]["effect_state"], "availability_topic": availability_topic, "options": options, "icon": "mdi:palette-swatch-variant", "device": device_info, "entity_category": "config" }
|
||||||
self._client.publish(f"homeassistant/select/{self._device_id}_{state_name}_effect/config", json.dumps(select_cfg), retain=True)
|
self._client.publish(f"homeassistant/select/{self._device_id}_{state_name}_effect/config", json.dumps(select_cfg), retain=True)
|
||||||
|
|
||||||
# Light Entity
|
|
||||||
light_cfg = { "name": f"{capital_name} Color", "unique_id": f"{self._device_id}_{state_name}_color", "schema": "json", "command_topic": self.topics[state_name]["light_command"], "state_topic": self.topics[state_name]["light_state"], "availability_topic": availability_topic, "brightness": True, "color_mode": True, "supported_color_modes": ["rgb"], "device": device_info, "entity_category": "config" }
|
light_cfg = { "name": f"{capital_name} Color", "unique_id": f"{self._device_id}_{state_name}_color", "schema": "json", "command_topic": self.topics[state_name]["light_command"], "state_topic": self.topics[state_name]["light_state"], "availability_topic": availability_topic, "brightness": True, "color_mode": True, "supported_color_modes": ["rgb"], "device": device_info, "entity_category": "config" }
|
||||||
self._client.publish(f"homeassistant/light/{self._device_id}_{state_name}_color/config", json.dumps(light_cfg), retain=True)
|
self._client.publish(f"homeassistant/light/{self._device_id}_{state_name}_color/config", json.dumps(light_cfg), retain=True)
|
||||||
|
|
||||||
_LOGGER.debug("Published all MQTT discovery configs")
|
_LOGGER.debug("Published all MQTT discovery configs")
|
||||||
self._client.publish(availability_topic, "online", retain=True)
|
self._client.publish(availability_topic, "online", retain=True)
|
||||||
|
|
||||||
self.publish_mute_state(self.state.mic_muted)
|
self.publish_mute_state(self.state.mic_muted)
|
||||||
|
self.publish_num_leds_state(self.state.preferences.num_leds)
|
||||||
|
|
||||||
def publish_mute_state(self, is_muted: bool):
|
def publish_mute_state(self, is_muted: bool):
|
||||||
self._client.publish(self.topics["mute"]["state"], "ON" if is_muted else "OFF", retain=True)
|
self._client.publish(self.topics["mute"]["state"], "ON" if is_muted else "OFF", retain=True)
|
||||||
|
|
||||||
|
def publish_num_leds_state(self, num_leds: int):
|
||||||
|
self._client.publish(self.topics["num_leds"]["state"], str(num_leds), retain=True)
|
||||||
|
|
||||||
@subscribe
|
@subscribe
|
||||||
def publish_state_to_mqtt(self, data: dict):
|
def publish_state_to_mqtt(self, data: dict):
|
||||||
state_name = data.get("state_name")
|
state_name = data.get("state_name")
|
||||||
if state_name in self.topics:
|
if state_name in self.topics:
|
||||||
state_topics = self.topics[state_name]
|
state_topics = self.topics[state_name]
|
||||||
|
|
||||||
# Update Select State
|
|
||||||
effect_name = data.get("effect", "off").replace("_", " ").title()
|
effect_name = data.get("effect", "off").replace("_", " ").title()
|
||||||
self._client.publish(state_topics["effect_state"], effect_name, retain=True)
|
self._client.publish(state_topics["effect_state"], effect_name, retain=True)
|
||||||
|
|
||||||
# Update Light State
|
|
||||||
light_state = {
|
light_state = {
|
||||||
"state": "ON" if data.get("effect") != "off" else "OFF",
|
"state": "ON" if data.get("effect") != "off" else "OFF",
|
||||||
"brightness": int(data.get("brightness", 0.5) * 255),
|
"brightness": int(data.get("brightness", 0.5) * 255),
|
||||||
|
|||||||
Reference in New Issue
Block a user