plug_energy.py 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. """Reading a smart plug's power and state, whatever kind of plug it is."""
  2. from sqlalchemy.ext.asyncio import AsyncSession
  3. from backend.app.models.smart_plug import SmartPlug
  4. from backend.app.services.homeassistant import homeassistant_service
  5. from backend.app.services.mqtt_relay import mqtt_relay
  6. from backend.app.services.rest_smart_plug import rest_smart_plug_service
  7. from backend.app.services.tasmota import tasmota_service
  8. async def _configured_homeassistant(db: AsyncSession):
  9. from backend.app.api.routes.settings import get_homeassistant_settings
  10. ha_settings = await get_homeassistant_settings(db)
  11. homeassistant_service.configure(ha_settings["ha_url"], ha_settings["ha_token"])
  12. return homeassistant_service
  13. async def get_plug_energy(plug: SmartPlug, db: AsyncSession) -> dict | None:
  14. """Get energy from plug regardless of type (Tasmota, Home Assistant, MQTT, or REST).
  15. For HA plugs, configures the service with current settings from DB.
  16. For MQTT plugs, returns data from the subscription service.
  17. For REST plugs, polls the status URL with JSON path extraction.
  18. """
  19. if plug.plug_type == "homeassistant":
  20. return await (await _configured_homeassistant(db)).get_energy(plug)
  21. elif plug.plug_type == "mqtt":
  22. # MQTT plugs report "today" energy, not lifetime total
  23. # For per-print tracking, we use "today" as the counter (resets at midnight)
  24. mqtt_data = mqtt_relay.smart_plug_service.get_plug_data(plug.id)
  25. if mqtt_data:
  26. return {
  27. "power": mqtt_data.power,
  28. "today": mqtt_data.energy,
  29. "total": mqtt_data.energy, # Use today as total for per-print calculations
  30. }
  31. return None
  32. elif plug.plug_type == "rest":
  33. return await rest_smart_plug_service.get_energy(plug)
  34. else:
  35. return await tasmota_service.get_energy(plug)
  36. async def read_plug(plug: SmartPlug, db: AsyncSession) -> tuple[str | None, float | None]:
  37. """The plug's switch state ("ON", "OFF" or None if unknown) and power in watts.
  38. An unreachable plug reads as (None, None). MQTT plugs answer from the last
  39. message received, without contacting the device.
  40. """
  41. if plug.plug_type == "mqtt":
  42. data = mqtt_relay.smart_plug_service.get_plug_data(plug.id)
  43. if not data or not mqtt_relay.smart_plug_service.is_reachable(plug.id):
  44. return None, None
  45. return data.state, data.power
  46. if plug.plug_type == "homeassistant":
  47. service = await _configured_homeassistant(db)
  48. elif plug.plug_type == "rest":
  49. service = rest_smart_plug_service
  50. else:
  51. service = tasmota_service
  52. status = await service.get_status(plug)
  53. if not status.get("reachable"):
  54. return None, None
  55. energy = await get_plug_energy(plug, db)
  56. return status.get("state"), (energy or {}).get("power")