2021-08-23 20:49:19 +02:00
|
|
|
import logging
|
|
|
|
|
2021-03-07 20:03:16 +01:00
|
|
|
from esphome.const import (
|
2021-10-10 10:37:05 +02:00
|
|
|
CONF_DISABLED_BY_DEFAULT,
|
2021-11-07 19:24:52 +01:00
|
|
|
CONF_ENTITY_CATEGORY,
|
2021-10-10 10:37:05 +02:00
|
|
|
CONF_ICON,
|
|
|
|
CONF_INTERNAL,
|
|
|
|
CONF_NAME,
|
2021-03-07 20:03:16 +01:00
|
|
|
CONF_SETUP_PRIORITY,
|
|
|
|
CONF_UPDATE_INTERVAL,
|
|
|
|
CONF_TYPE_ID,
|
2023-03-07 22:29:45 +01:00
|
|
|
CONF_OTA,
|
|
|
|
CONF_SAFE_MODE,
|
|
|
|
KEY_PAST_SAFE_MODE,
|
2021-03-07 20:03:16 +01:00
|
|
|
)
|
|
|
|
|
2021-06-17 21:54:14 +02:00
|
|
|
from esphome.core import coroutine, ID, CORE
|
2023-03-07 22:29:45 +01:00
|
|
|
from esphome.coroutine import FakeAwaitable
|
2022-10-12 04:15:03 +02:00
|
|
|
from esphome.types import ConfigType, ConfigFragmentType
|
2021-09-20 11:47:51 +02:00
|
|
|
from esphome.cpp_generator import add, get_variable
|
|
|
|
from esphome.cpp_types import App
|
2019-12-07 18:28:55 +01:00
|
|
|
from esphome.util import Registry, RegistryEntry
|
2023-03-28 11:00:34 +02:00
|
|
|
from esphome.helpers import snake_case, sanitize
|
2018-12-05 21:22:06 +01:00
|
|
|
|
|
|
|
|
2021-08-23 20:49:19 +02:00
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2021-05-23 22:10:30 +02:00
|
|
|
async def gpio_pin_expression(conf):
|
2019-04-22 21:56:30 +02:00
|
|
|
"""Generate an expression for the given pin option.
|
|
|
|
|
2021-05-24 21:45:31 +02:00
|
|
|
This is a coroutine, you must await it with a 'await' expression!
|
2019-04-22 21:56:30 +02:00
|
|
|
"""
|
2018-12-05 21:22:06 +01:00
|
|
|
if conf is None:
|
2021-09-20 11:47:51 +02:00
|
|
|
return None
|
2019-04-17 12:06:00 +02:00
|
|
|
from esphome import pins
|
2021-03-07 20:03:16 +01:00
|
|
|
|
2019-04-22 21:56:30 +02:00
|
|
|
for key, (func, _) in pins.PIN_SCHEMA_REGISTRY.items():
|
2019-04-17 12:06:00 +02:00
|
|
|
if key in conf:
|
2021-05-23 22:10:30 +02:00
|
|
|
return await coroutine(func)(conf)
|
2021-09-20 11:47:51 +02:00
|
|
|
return await coroutine(pins.PIN_SCHEMA_REGISTRY[CORE.target_platform][0])(conf)
|
2018-12-05 21:22:06 +01:00
|
|
|
|
|
|
|
|
2021-05-23 22:10:30 +02:00
|
|
|
async def register_component(var, config):
|
2019-04-22 21:56:30 +02:00
|
|
|
"""Register the given obj as a component.
|
|
|
|
|
2021-05-24 21:45:31 +02:00
|
|
|
This is a coroutine, you must await it with a 'await' expression!
|
2019-04-22 21:56:30 +02:00
|
|
|
|
|
|
|
:param var: The variable representing the component.
|
|
|
|
:param config: The configuration for the component.
|
|
|
|
"""
|
2021-08-23 20:49:19 +02:00
|
|
|
import inspect
|
|
|
|
|
2019-12-07 18:28:55 +01:00
|
|
|
id_ = str(var.base)
|
2019-05-21 12:23:38 +02:00
|
|
|
if id_ not in CORE.component_ids:
|
2021-03-07 20:03:16 +01:00
|
|
|
raise ValueError(
|
2021-09-19 19:22:28 +02:00
|
|
|
f"Component ID {id_} was not declared to inherit from Component, or was registered twice. Please create a bug report with your configuration."
|
2021-03-07 20:03:16 +01:00
|
|
|
)
|
2019-05-21 12:23:38 +02:00
|
|
|
CORE.component_ids.remove(id_)
|
2019-04-17 12:06:00 +02:00
|
|
|
if CONF_SETUP_PRIORITY in config:
|
2019-04-22 21:56:30 +02:00
|
|
|
add(var.set_setup_priority(config[CONF_SETUP_PRIORITY]))
|
|
|
|
if CONF_UPDATE_INTERVAL in config:
|
|
|
|
add(var.set_update_interval(config[CONF_UPDATE_INTERVAL]))
|
2021-08-23 20:49:19 +02:00
|
|
|
|
|
|
|
# Set component source by inspecting the stack and getting the callee module
|
|
|
|
# https://stackoverflow.com/a/1095621
|
|
|
|
name = None
|
|
|
|
try:
|
|
|
|
for frm in inspect.stack()[1:]:
|
|
|
|
mod = inspect.getmodule(frm[0])
|
|
|
|
if mod is None:
|
|
|
|
continue
|
|
|
|
name = mod.__name__
|
|
|
|
if name.startswith("esphome.components."):
|
|
|
|
name = name[len("esphome.components.") :]
|
|
|
|
break
|
|
|
|
if name == "esphome.automation":
|
|
|
|
name = "automation"
|
|
|
|
# continue looking further up in stack in case we find a better one
|
|
|
|
if name == "esphome.coroutine":
|
|
|
|
# Only works for async-await coroutine syntax
|
|
|
|
break
|
|
|
|
except (KeyError, AttributeError, IndexError) as e:
|
|
|
|
_LOGGER.warning(
|
|
|
|
"Error while finding name of component, please report this", exc_info=e
|
|
|
|
)
|
|
|
|
if name is not None:
|
|
|
|
add(var.set_component_source(name))
|
|
|
|
|
2019-04-22 21:56:30 +02:00
|
|
|
add(App.register_component(var))
|
2021-05-23 22:10:30 +02:00
|
|
|
return var
|
2019-04-22 21:56:30 +02:00
|
|
|
|
|
|
|
|
2021-05-23 22:10:30 +02:00
|
|
|
async def register_parented(var, value):
|
2019-05-11 12:31:00 +02:00
|
|
|
if isinstance(value, ID):
|
2021-05-23 22:10:30 +02:00
|
|
|
paren = await get_variable(value)
|
2019-05-11 12:31:00 +02:00
|
|
|
else:
|
|
|
|
paren = value
|
|
|
|
add(var.set_parent(paren))
|
|
|
|
|
|
|
|
|
2021-10-10 10:37:05 +02:00
|
|
|
async def setup_entity(var, config):
|
|
|
|
"""Set up generic properties of an Entity"""
|
|
|
|
add(var.set_name(config[CONF_NAME]))
|
2023-03-28 11:00:34 +02:00
|
|
|
if not config[CONF_NAME]:
|
|
|
|
add(var.set_object_id(sanitize(snake_case(CORE.friendly_name))))
|
|
|
|
else:
|
|
|
|
add(var.set_object_id(sanitize(snake_case(config[CONF_NAME]))))
|
2021-10-10 10:37:05 +02:00
|
|
|
add(var.set_disabled_by_default(config[CONF_DISABLED_BY_DEFAULT]))
|
|
|
|
if CONF_INTERNAL in config:
|
|
|
|
add(var.set_internal(config[CONF_INTERNAL]))
|
|
|
|
if CONF_ICON in config:
|
|
|
|
add(var.set_icon(config[CONF_ICON]))
|
2021-11-07 19:24:52 +01:00
|
|
|
if CONF_ENTITY_CATEGORY in config:
|
|
|
|
add(var.set_entity_category(config[CONF_ENTITY_CATEGORY]))
|
2021-10-10 10:37:05 +02:00
|
|
|
|
|
|
|
|
2022-10-05 09:09:27 +02:00
|
|
|
def extract_registry_entry_config(
|
2022-10-12 04:15:03 +02:00
|
|
|
registry: Registry,
|
|
|
|
full_config: ConfigType,
|
|
|
|
) -> tuple[RegistryEntry, ConfigFragmentType]:
|
2019-04-22 21:56:30 +02:00
|
|
|
key, config = next((k, v) for k, v in full_config.items() if k in registry)
|
|
|
|
return registry[key], config
|
2018-12-05 21:22:06 +01:00
|
|
|
|
|
|
|
|
2021-05-23 22:10:30 +02:00
|
|
|
async def build_registry_entry(registry, full_config):
|
2019-04-22 21:56:30 +02:00
|
|
|
registry_entry, config = extract_registry_entry_config(registry, full_config)
|
|
|
|
type_id = full_config[CONF_TYPE_ID]
|
|
|
|
builder = registry_entry.coroutine_fun
|
2021-05-23 22:10:30 +02:00
|
|
|
return await builder(config, type_id)
|
2018-12-05 21:22:06 +01:00
|
|
|
|
|
|
|
|
2021-05-23 22:10:30 +02:00
|
|
|
async def build_registry_list(registry, config):
|
2019-04-17 12:06:00 +02:00
|
|
|
actions = []
|
|
|
|
for conf in config:
|
2021-05-23 22:10:30 +02:00
|
|
|
action = await build_registry_entry(registry, conf)
|
2019-04-17 12:06:00 +02:00
|
|
|
actions.append(action)
|
2021-05-23 22:10:30 +02:00
|
|
|
return actions
|
2023-03-07 22:29:45 +01:00
|
|
|
|
|
|
|
|
|
|
|
async def past_safe_mode():
|
|
|
|
safe_mode_enabled = (
|
|
|
|
CONF_OTA in CORE.config and CORE.config[CONF_OTA][CONF_SAFE_MODE]
|
|
|
|
)
|
|
|
|
if not safe_mode_enabled:
|
|
|
|
return
|
|
|
|
|
|
|
|
def _safe_mode_generator():
|
|
|
|
while True:
|
|
|
|
if CORE.data.get(CONF_OTA, {}).get(KEY_PAST_SAFE_MODE, False):
|
|
|
|
return
|
|
|
|
yield
|
|
|
|
|
|
|
|
return await FakeAwaitable(_safe_mode_generator())
|