Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Support for local power state reporting of entities by emulating TP-Link Kasa smart plugs."""
2 
3 import logging
4 
5 from sense_energy import PlugInstance, SenseLink
6 import voluptuous as vol
7 
8 from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
9 from homeassistant.const import (
10  CONF_ENTITIES,
11  CONF_NAME,
12  CONF_UNIQUE_ID,
13  EVENT_HOMEASSISTANT_STARTED,
14  EVENT_HOMEASSISTANT_STOP,
15  STATE_ON,
16 )
17 from homeassistant.core import HomeAssistant
18 from homeassistant.helpers import entity_registry as er
20 from homeassistant.helpers.template import Template, is_template_string
21 from homeassistant.helpers.typing import ConfigType
22 
23 from .const import CONF_POWER, CONF_POWER_ENTITY, DOMAIN
24 
25 _LOGGER = logging.getLogger(__name__)
26 
27 CONFIG_ENTITY_SCHEMA = vol.Schema(
28  {
29  vol.Optional(CONF_NAME): cv.string,
30  vol.Optional(CONF_POWER): vol.Any(
31  vol.Coerce(float),
32  cv.template,
33  ),
34  vol.Optional(CONF_POWER_ENTITY): cv.string,
35  }
36 )
37 
38 CONFIG_SCHEMA = vol.Schema(
39  {
40  DOMAIN: vol.Schema(
41  {
42  vol.Required(CONF_ENTITIES): vol.Schema(
43  {cv.entity_id: CONFIG_ENTITY_SCHEMA}
44  ),
45  }
46  )
47  },
48  extra=vol.ALLOW_EXTRA,
49 )
50 
51 
52 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
53  """Set up the emulated_kasa component."""
54  if not (conf := config.get(DOMAIN)):
55  return True
56  entity_configs = conf[CONF_ENTITIES]
57 
58  def devices():
59  """Devices to be emulated."""
60  yield from get_plug_devices(hass, entity_configs)
61 
62  server = SenseLink(devices)
63 
64  async def stop_emulated_kasa(event):
65  await server.stop()
66 
67  async def start_emulated_kasa(event):
68  await validate_configs(hass, entity_configs)
69  try:
70  await server.start()
71  except OSError as error:
72  _LOGGER.error("Failed to create UDP server at port 9999: %s", error)
73  else:
74  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_emulated_kasa)
75 
76  hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, start_emulated_kasa)
77 
78  return True
79 
80 
81 async def validate_configs(hass, entity_configs):
82  """Validate that entities exist and ensure templates are ready to use."""
83  entity_registry = er.async_get(hass)
84  for entity_id, entity_config in entity_configs.items():
85  if (state := hass.states.get(entity_id)) is None:
86  _LOGGER.debug("Entity not found: %s", entity_id)
87  continue
88 
89  if entity := entity_registry.async_get(entity_id):
90  entity_config[CONF_UNIQUE_ID] = get_system_unique_id(entity)
91  else:
92  entity_config[CONF_UNIQUE_ID] = entity_id
93 
94  if CONF_POWER in entity_config:
95  power_val = entity_config[CONF_POWER]
96  if isinstance(power_val, str) and is_template_string(power_val):
97  entity_config[CONF_POWER] = Template(power_val, hass)
98  elif CONF_POWER_ENTITY in entity_config:
99  power_val = entity_config[CONF_POWER_ENTITY]
100  if hass.states.get(power_val) is None:
101  _LOGGER.debug("Sensor Entity not found: %s", power_val)
102  else:
103  entity_config[CONF_POWER] = power_val
104  elif state.domain == SENSOR_DOMAIN:
105  pass
106  else:
107  _LOGGER.debug("No power value defined for: %s", entity_id)
108 
109 
110 def get_system_unique_id(entity: er.RegistryEntry):
111  """Determine the system wide unique_id for an entity."""
112  return f"{entity.platform}.{entity.domain}.{entity.unique_id}"
113 
114 
115 def get_plug_devices(hass, entity_configs):
116  """Produce list of plug devices from config entities."""
117  for entity_id, entity_config in entity_configs.items():
118  if (state := hass.states.get(entity_id)) is None:
119  continue
120  name = entity_config.get(CONF_NAME, state.name)
121 
122  if state.state == STATE_ON or state.domain == SENSOR_DOMAIN:
123  if CONF_POWER in entity_config:
124  power_val = entity_config[CONF_POWER]
125  if isinstance(power_val, (float, int)):
126  power = float(power_val)
127  elif isinstance(power_val, str):
128  power = float(hass.states.get(power_val).state)
129  elif isinstance(power_val, Template):
130  power = float(power_val.async_render())
131  elif state.domain == SENSOR_DOMAIN:
132  power = float(state.state)
133  else:
134  power = 0.0
135  last_changed = state.last_changed.timestamp()
136  yield PlugInstance(
137  entity_config[CONF_UNIQUE_ID],
138  start_time=last_changed,
139  alias=name,
140  power=power,
141  )
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:52
def get_plug_devices(hass, entity_configs)
Definition: __init__.py:115
def get_system_unique_id(er.RegistryEntry entity)
Definition: __init__.py:110
def validate_configs(hass, entity_configs)
Definition: __init__.py:81
dict[str, dict[str, Any]] devices(HomeAssistant hass)
Definition: __init__.py:237
bool is_template_string(str maybe_template)
Definition: template.py:272