mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 03:54:17 +02:00
Enhanced MQTT support with separate topics and multipliers (#173)
- Add separate MQTT topics for power, energy, and state monitoring - mqtt_power_topic, mqtt_power_path, mqtt_power_multiplier - mqtt_energy_topic, mqtt_energy_path, mqtt_energy_multiplier - mqtt_state_topic, mqtt_state_path, mqtt_state_on_value - Support different MQTT topics per data type (e.g., Zigbee2MQTT with separate power/energy/state topics) - Individual multipliers for power and energy (e.g., mW→W, Wh→kWh) - Configurable ON value for state monitoring (e.g., "ON", "true", "1") - Maintain backward compatibility with legacy mqtt_topic/mqtt_multiplier - Database migration auto-copies legacy fields to new fields - Update backup/restore to handle new MQTT fields - Add backend tests for new MQTT configurations - Update frontend form with organized Power/Energy/State sections Closes #173
This commit is contained in:
@@ -360,12 +360,21 @@ async def export_backup(
|
||||
"ha_power_entity": plug.ha_power_entity,
|
||||
"ha_energy_today_entity": plug.ha_energy_today_entity,
|
||||
"ha_energy_total_entity": plug.ha_energy_total_entity,
|
||||
# MQTT plug fields
|
||||
# MQTT plug fields (legacy)
|
||||
"mqtt_topic": plug.mqtt_topic,
|
||||
"mqtt_power_path": plug.mqtt_power_path,
|
||||
"mqtt_energy_path": plug.mqtt_energy_path,
|
||||
"mqtt_state_path": plug.mqtt_state_path,
|
||||
"mqtt_multiplier": plug.mqtt_multiplier,
|
||||
# MQTT power fields
|
||||
"mqtt_power_topic": plug.mqtt_power_topic,
|
||||
"mqtt_power_path": plug.mqtt_power_path,
|
||||
"mqtt_power_multiplier": plug.mqtt_power_multiplier,
|
||||
# MQTT energy fields
|
||||
"mqtt_energy_topic": plug.mqtt_energy_topic,
|
||||
"mqtt_energy_path": plug.mqtt_energy_path,
|
||||
"mqtt_energy_multiplier": plug.mqtt_energy_multiplier,
|
||||
# MQTT state fields
|
||||
"mqtt_state_topic": plug.mqtt_state_topic,
|
||||
"mqtt_state_path": plug.mqtt_state_path,
|
||||
"mqtt_state_on_value": plug.mqtt_state_on_value,
|
||||
"printer_serial": printer_id_to_serial.get(plug.printer_id) if plug.printer_id else None,
|
||||
"enabled": plug.enabled,
|
||||
"auto_on": plug.auto_on,
|
||||
@@ -1270,10 +1279,16 @@ async def import_backup(
|
||||
result = await db.execute(select(SmartPlug).where(SmartPlug.ha_entity_id == plug_data["ha_entity_id"]))
|
||||
existing = result.scalar_one_or_none()
|
||||
plug_identifier = plug_data["ha_entity_id"]
|
||||
elif plug_type == "mqtt" and plug_data.get("mqtt_topic"):
|
||||
result = await db.execute(select(SmartPlug).where(SmartPlug.mqtt_topic == plug_data["mqtt_topic"]))
|
||||
elif plug_type == "mqtt" and (plug_data.get("mqtt_power_topic") or plug_data.get("mqtt_topic")):
|
||||
# Check by mqtt_power_topic first (new format), fall back to mqtt_topic (legacy)
|
||||
power_topic = plug_data.get("mqtt_power_topic") or plug_data.get("mqtt_topic")
|
||||
result = await db.execute(
|
||||
select(SmartPlug).where(
|
||||
(SmartPlug.mqtt_power_topic == power_topic) | (SmartPlug.mqtt_topic == power_topic)
|
||||
)
|
||||
)
|
||||
existing = result.scalar_one_or_none()
|
||||
plug_identifier = plug_data["mqtt_topic"]
|
||||
plug_identifier = power_topic
|
||||
elif plug_data.get("ip_address"):
|
||||
result = await db.execute(select(SmartPlug).where(SmartPlug.ip_address == plug_data["ip_address"]))
|
||||
existing = result.scalar_one_or_none()
|
||||
@@ -1290,12 +1305,21 @@ async def import_backup(
|
||||
existing.ha_power_entity = plug_data.get("ha_power_entity")
|
||||
existing.ha_energy_today_entity = plug_data.get("ha_energy_today_entity")
|
||||
existing.ha_energy_total_entity = plug_data.get("ha_energy_total_entity")
|
||||
# MQTT fields
|
||||
# MQTT fields (legacy)
|
||||
existing.mqtt_topic = plug_data.get("mqtt_topic")
|
||||
existing.mqtt_power_path = plug_data.get("mqtt_power_path")
|
||||
existing.mqtt_energy_path = plug_data.get("mqtt_energy_path")
|
||||
existing.mqtt_state_path = plug_data.get("mqtt_state_path")
|
||||
existing.mqtt_multiplier = plug_data.get("mqtt_multiplier", 1.0)
|
||||
# MQTT power fields
|
||||
existing.mqtt_power_topic = plug_data.get("mqtt_power_topic")
|
||||
existing.mqtt_power_path = plug_data.get("mqtt_power_path")
|
||||
existing.mqtt_power_multiplier = plug_data.get("mqtt_power_multiplier", 1.0)
|
||||
# MQTT energy fields
|
||||
existing.mqtt_energy_topic = plug_data.get("mqtt_energy_topic")
|
||||
existing.mqtt_energy_path = plug_data.get("mqtt_energy_path")
|
||||
existing.mqtt_energy_multiplier = plug_data.get("mqtt_energy_multiplier", 1.0)
|
||||
# MQTT state fields
|
||||
existing.mqtt_state_topic = plug_data.get("mqtt_state_topic")
|
||||
existing.mqtt_state_path = plug_data.get("mqtt_state_path")
|
||||
existing.mqtt_state_on_value = plug_data.get("mqtt_state_on_value")
|
||||
existing.printer_id = printer_id
|
||||
existing.enabled = plug_data.get("enabled", True)
|
||||
existing.auto_on = plug_data.get("auto_on", True)
|
||||
@@ -1325,12 +1349,21 @@ async def import_backup(
|
||||
ha_power_entity=plug_data.get("ha_power_entity"),
|
||||
ha_energy_today_entity=plug_data.get("ha_energy_today_entity"),
|
||||
ha_energy_total_entity=plug_data.get("ha_energy_total_entity"),
|
||||
# MQTT fields
|
||||
# MQTT fields (legacy)
|
||||
mqtt_topic=plug_data.get("mqtt_topic"),
|
||||
mqtt_power_path=plug_data.get("mqtt_power_path"),
|
||||
mqtt_energy_path=plug_data.get("mqtt_energy_path"),
|
||||
mqtt_state_path=plug_data.get("mqtt_state_path"),
|
||||
mqtt_multiplier=plug_data.get("mqtt_multiplier", 1.0),
|
||||
# MQTT power fields
|
||||
mqtt_power_topic=plug_data.get("mqtt_power_topic"),
|
||||
mqtt_power_path=plug_data.get("mqtt_power_path"),
|
||||
mqtt_power_multiplier=plug_data.get("mqtt_power_multiplier", 1.0),
|
||||
# MQTT energy fields
|
||||
mqtt_energy_topic=plug_data.get("mqtt_energy_topic"),
|
||||
mqtt_energy_path=plug_data.get("mqtt_energy_path"),
|
||||
mqtt_energy_multiplier=plug_data.get("mqtt_energy_multiplier", 1.0),
|
||||
# MQTT state fields
|
||||
mqtt_state_topic=plug_data.get("mqtt_state_topic"),
|
||||
mqtt_state_path=plug_data.get("mqtt_state_path"),
|
||||
mqtt_state_on_value=plug_data.get("mqtt_state_on_value"),
|
||||
printer_id=printer_id,
|
||||
enabled=plug_data.get("enabled", True),
|
||||
auto_on=plug_data.get("auto_on", True),
|
||||
|
||||
@@ -96,17 +96,44 @@ async def create_smart_plug(
|
||||
await db.commit()
|
||||
await db.refresh(plug)
|
||||
|
||||
# Subscribe MQTT plugs to their topic
|
||||
if plug.plug_type == "mqtt" and plug.mqtt_topic:
|
||||
mqtt_relay.smart_plug_service.subscribe(
|
||||
plug_id=plug.id,
|
||||
topic=plug.mqtt_topic,
|
||||
power_path=plug.mqtt_power_path,
|
||||
energy_path=plug.mqtt_energy_path,
|
||||
state_path=plug.mqtt_state_path,
|
||||
multiplier=plug.mqtt_multiplier or 1.0,
|
||||
)
|
||||
logger.info(f"Created MQTT plug '{plug.name}' subscribed to {plug.mqtt_topic}")
|
||||
# Subscribe MQTT plugs to their topics
|
||||
if plug.plug_type == "mqtt":
|
||||
# Determine effective topics (new fields take priority, fall back to legacy)
|
||||
power_topic = plug.mqtt_power_topic or plug.mqtt_topic
|
||||
energy_topic = plug.mqtt_energy_topic or plug.mqtt_topic
|
||||
state_topic = plug.mqtt_state_topic or plug.mqtt_topic
|
||||
|
||||
# Only subscribe if at least one data source is configured
|
||||
if (
|
||||
(power_topic and plug.mqtt_power_path)
|
||||
or (energy_topic and plug.mqtt_energy_path)
|
||||
or (state_topic and plug.mqtt_state_path)
|
||||
):
|
||||
mqtt_relay.smart_plug_service.subscribe(
|
||||
plug_id=plug.id,
|
||||
# Power source
|
||||
power_topic=power_topic if plug.mqtt_power_path else None,
|
||||
power_path=plug.mqtt_power_path,
|
||||
power_multiplier=plug.mqtt_power_multiplier or plug.mqtt_multiplier or 1.0,
|
||||
# Energy source
|
||||
energy_topic=energy_topic if plug.mqtt_energy_path else None,
|
||||
energy_path=plug.mqtt_energy_path,
|
||||
energy_multiplier=plug.mqtt_energy_multiplier or plug.mqtt_multiplier or 1.0,
|
||||
# State source
|
||||
state_topic=state_topic if plug.mqtt_state_path else None,
|
||||
state_path=plug.mqtt_state_path,
|
||||
state_on_value=plug.mqtt_state_on_value,
|
||||
)
|
||||
topics = [
|
||||
t
|
||||
for t in [
|
||||
power_topic if plug.mqtt_power_path else None,
|
||||
energy_topic if plug.mqtt_energy_path else None,
|
||||
state_topic if plug.mqtt_state_path else None,
|
||||
]
|
||||
if t
|
||||
]
|
||||
logger.info(f"Created MQTT plug '{plug.name}' subscribed to {', '.join(set(topics))}")
|
||||
elif plug.plug_type == "homeassistant":
|
||||
logger.info(f"Created Home Assistant plug '{plug.name}' ({plug.ha_entity_id})")
|
||||
else:
|
||||
@@ -338,9 +365,19 @@ async def update_smart_plug(
|
||||
if result.scalar_one_or_none():
|
||||
raise HTTPException(400, "This printer already has a smart plug assigned")
|
||||
|
||||
# Check if MQTT topic is changing - need to resubscribe
|
||||
old_topic = plug.mqtt_topic
|
||||
# Track old MQTT settings for comparison
|
||||
old_plug_type = plug.plug_type
|
||||
old_mqtt_config = {
|
||||
"power_topic": plug.mqtt_power_topic or plug.mqtt_topic,
|
||||
"power_path": plug.mqtt_power_path,
|
||||
"power_multiplier": plug.mqtt_power_multiplier,
|
||||
"energy_topic": plug.mqtt_energy_topic or plug.mqtt_topic,
|
||||
"energy_path": plug.mqtt_energy_path,
|
||||
"energy_multiplier": plug.mqtt_energy_multiplier,
|
||||
"state_topic": plug.mqtt_state_topic or plug.mqtt_topic,
|
||||
"state_path": plug.mqtt_state_path,
|
||||
"state_on_value": plug.mqtt_state_on_value,
|
||||
}
|
||||
|
||||
for field, value in update_data.items():
|
||||
setattr(plug, field, value)
|
||||
@@ -353,20 +390,50 @@ async def update_smart_plug(
|
||||
# Changed away from MQTT - unsubscribe
|
||||
mqtt_relay.smart_plug_service.unsubscribe(plug.id)
|
||||
elif plug.plug_type == "mqtt":
|
||||
# Is now MQTT - check if topic changed or newly MQTT
|
||||
if old_plug_type != "mqtt" or old_topic != plug.mqtt_topic:
|
||||
# Unsubscribe from old topic first
|
||||
# Check if any MQTT config changed
|
||||
new_mqtt_config = {
|
||||
"power_topic": plug.mqtt_power_topic or plug.mqtt_topic,
|
||||
"power_path": plug.mqtt_power_path,
|
||||
"power_multiplier": plug.mqtt_power_multiplier,
|
||||
"energy_topic": plug.mqtt_energy_topic or plug.mqtt_topic,
|
||||
"energy_path": plug.mqtt_energy_path,
|
||||
"energy_multiplier": plug.mqtt_energy_multiplier,
|
||||
"state_topic": plug.mqtt_state_topic or plug.mqtt_topic,
|
||||
"state_path": plug.mqtt_state_path,
|
||||
"state_on_value": plug.mqtt_state_on_value,
|
||||
}
|
||||
|
||||
mqtt_changed = old_plug_type != "mqtt" or old_mqtt_config != new_mqtt_config
|
||||
|
||||
if mqtt_changed:
|
||||
# Unsubscribe from old topics first
|
||||
if old_plug_type == "mqtt":
|
||||
mqtt_relay.smart_plug_service.unsubscribe(plug.id)
|
||||
# Subscribe to new topic
|
||||
if plug.mqtt_topic:
|
||||
|
||||
# Subscribe to new topics
|
||||
power_topic = plug.mqtt_power_topic or plug.mqtt_topic
|
||||
energy_topic = plug.mqtt_energy_topic or plug.mqtt_topic
|
||||
state_topic = plug.mqtt_state_topic or plug.mqtt_topic
|
||||
|
||||
if (
|
||||
(power_topic and plug.mqtt_power_path)
|
||||
or (energy_topic and plug.mqtt_energy_path)
|
||||
or (state_topic and plug.mqtt_state_path)
|
||||
):
|
||||
mqtt_relay.smart_plug_service.subscribe(
|
||||
plug_id=plug.id,
|
||||
topic=plug.mqtt_topic,
|
||||
# Power source
|
||||
power_topic=power_topic if plug.mqtt_power_path else None,
|
||||
power_path=plug.mqtt_power_path,
|
||||
power_multiplier=plug.mqtt_power_multiplier or plug.mqtt_multiplier or 1.0,
|
||||
# Energy source
|
||||
energy_topic=energy_topic if plug.mqtt_energy_path else None,
|
||||
energy_path=plug.mqtt_energy_path,
|
||||
energy_multiplier=plug.mqtt_energy_multiplier or plug.mqtt_multiplier or 1.0,
|
||||
# State source
|
||||
state_topic=state_topic if plug.mqtt_state_path else None,
|
||||
state_path=plug.mqtt_state_path,
|
||||
multiplier=plug.mqtt_multiplier or 1.0,
|
||||
state_on_value=plug.mqtt_state_on_value,
|
||||
)
|
||||
|
||||
logger.info(f"Updated smart plug '{plug.name}'")
|
||||
|
||||
Reference in New Issue
Block a user