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>
153 lines
4.4 KiB
Python
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
|