Files
esphome-lsm6ds/esphome/components/emontx/__init__.py
T
14bcdfe700 [emontx] emonTx component (#9027)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-04-07 22:29:55 +00:00

153 lines
4.4 KiB
Python

from dataclasses import dataclass, field
from esphome import automation
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import (
CONF_COMMAND,
CONF_ID,
CONF_ON_DATA,
CONF_RX_BUFFER_SIZE,
CONF_UART_ID,
)
from esphome.core import CORE
import esphome.final_validate as fv
from esphome.types import ConfigType
AUTO_LOAD = ["json"]
CODEOWNERS = ["@FredM67", "@TrystanLea", "@glynhudson"]
DEPENDENCIES = ["uart"]
emontx_ns = cg.esphome_ns.namespace("emontx")
EmonTx = emontx_ns.class_("EmonTx", cg.Component, uart.UARTDevice)
# Action to send command to emonTx
EmonTxSendCommandAction = emontx_ns.class_("EmonTxSendCommandAction", automation.Action)
CONF_EMONTX_ID = "emontx_id"
CONF_TAG_NAME = "tag_name"
CONF_ON_JSON = "on_json"
DOMAIN = "emontx"
MINIMUM_RX_BUFFER_SIZE = 2048
@dataclass
class EmonTxData:
sensor_counts: dict[str, int] = field(default_factory=dict)
def _get_data() -> EmonTxData:
if DOMAIN not in CORE.data:
CORE.data[DOMAIN] = EmonTxData()
return CORE.data[DOMAIN]
# Main configuration schema
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(EmonTx),
cv.Optional(CONF_ON_JSON): automation.validate_automation({}),
cv.Optional(CONF_ON_DATA): automation.validate_automation({}),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(uart.UART_DEVICE_SCHEMA)
)
def final_validate(config: ConfigType) -> ConfigType:
full_config = fv.full_config.get()
# Count sensors registered to this hub (IDs are resolved at final_validate stage)
hub_id = str(config[CONF_ID])
sensor_count = sum(
1
for s in full_config.get("sensor", [])
if s.get("platform") == "emontx" and str(s.get(CONF_EMONTX_ID)) == hub_id
)
_get_data().sensor_counts[hub_id] = sensor_count
# Ensure UART RX buffer size is large enough to handle data bursts from firmware
for uart_conf in full_config["uart"]:
if uart_conf[CONF_ID] == config[CONF_UART_ID]:
current_buffer_size = uart_conf[CONF_RX_BUFFER_SIZE]
if current_buffer_size < MINIMUM_RX_BUFFER_SIZE:
raise cv.Invalid(
f"Component emontx requires UART '{config[CONF_UART_ID]}' to have "
f"rx_buffer_size of at least {MINIMUM_RX_BUFFER_SIZE} bytes "
f"(currently set to {current_buffer_size} bytes). "
f"Please add 'rx_buffer_size: {MINIMUM_RX_BUFFER_SIZE}' to your uart configuration.",
path=[CONF_UART_ID],
)
break
# Validate UART settings
schema = uart.final_validate_device_schema(
"emontx",
baud_rate=115200,
require_tx=False,
require_rx=True,
data_bits=8,
parity="NONE",
stop_bits=1,
)
return schema(config)
FINAL_VALIDATE_SCHEMA = final_validate
_CALLBACK_AUTOMATIONS = (
automation.CallbackAutomation(
CONF_ON_JSON,
"add_on_json_callback",
[(cg.JsonObject, "json"), (cg.std_string, "raw_json")],
),
automation.CallbackAutomation(
CONF_ON_DATA, "add_on_data_callback", [(cg.std_string, "data")]
),
)
async def to_code(config: ConfigType) -> None:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
# Initialize sensor storage with count from final_validate
sensor_count = _get_data().sensor_counts.get(str(config[CONF_ID]), 0)
if sensor_count > 0:
cg.add(var.init_sensors(sensor_count))
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
# Action: emontx.send_command
EMONTX_SEND_COMMAND_ACTION_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.use_id(EmonTx),
cv.Required(CONF_COMMAND): cv.templatable(cv.string),
}
)
@automation.register_action(
"emontx.send_command",
EmonTxSendCommandAction,
EMONTX_SEND_COMMAND_ACTION_SCHEMA,
synchronous=True,
)
async def emontx_send_command_action_to_code(
config: ConfigType, action_id, template_arg, args
) -> None:
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.std_string)
cg.add(var.set_command(template_))
return var