Skip to content

Commit

Permalink
add better flavor validation (m3_vedirect)
Browse files Browse the repository at this point in the history
  • Loading branch information
krahabb committed Nov 15, 2024
1 parent d402f3c commit a87e443
Show file tree
Hide file tree
Showing 12 changed files with 216 additions and 126 deletions.
103 changes: 59 additions & 44 deletions esphome/components/m3_vedirect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,24 @@ def validate_str_enum(enum_class: type[enum.StrEnum]):
return cv.enum({_enum.name: _enum for _enum in enum_class})


_NUMERIC_SCALE_MAP = {
1: ve_reg.SCALE.S_1,
0.1: ve_reg.SCALE.S_0_1,
0.01: ve_reg.SCALE.S_0_01,
0.001: ve_reg.SCALE.S_0_001,
0.25: ve_reg.SCALE.S_0_25,
}


def validate_numeric_scale():
"""Allows 'scale' to be set either as a typed enum from REG_DEF::SCALE or a float value.
float value must be one of the normalized."""
_scale_map = {
1: ve_reg.SCALE.S_1,
0.1: ve_reg.SCALE.S_0_1,
0.01: ve_reg.SCALE.S_0_01,
0.001: ve_reg.SCALE.S_0_001,
0.25: ve_reg.SCALE.S_0_25,
}

enum_validator = validate_mock_enum(ve_reg.SCALE)

def validator(value):
if value in _scale_map:
value = _scale_map[value].name
if value in _NUMERIC_SCALE_MAP:
value = _NUMERIC_SCALE_MAP[value].name
return enum_validator(value)

return validator
Expand Down Expand Up @@ -152,37 +154,39 @@ def vedirect_entity_schema(classes: typing.Iterable[ve_reg.CLASS], has_text):
cv.Optional(CONF_MASK): cv.uint32_t,
}

"""
# TODO: add extended validator for VEDIRECT_ENTITY_BASE_SCHEMA to check the
# various combinations of possible options
def _entity_base_validator(config):
if CONF_NAME not in config and CONF_ID not in config:
raise Invalid("At least one of 'id:' or 'name:' is required!")
if CONF_NAME not in config:
id = config[CONF_ID]
if not id.is_manual:
raise Invalid("At least one of 'id:' or 'name:' is required!")
config[CONF_NAME] = id.id
config[CONF_INTERNAL] = True
return config
if config[CONF_NAME] is None:
config[CONF_NAME] = ""
return config
#cv.Schema.add_extra(_entity_base_validator)
"""

# root schema to group (platform) entities linked to a Manager
VEDIRECT_PLATFORM_SCHEMA = cv.Schema(
{
cv.Required(CONF_VEDIRECT_ID): cv.use_id(Manager),
}
)

def vedirect_platform_schema(
vedirect_entity_base_schema: cv.Schema,
classes: typing.Iterable[ve_reg.CLASS],
has_text: bool,
platform_entities: dict[str, cv.Schema],
):
def _validate_platform_entity(config):
# Ensure CONF_TYPE is available based off Manager flavor
if CONF_VEDIRECT_ENTITIES in config:
flavor = MANAGERS_CONFIG[config[CONF_VEDIRECT_ID]][CONF_FLAVOR]
for vedirect_entity_config in config[CONF_VEDIRECT_ENTITIES]:
if CONF_TYPE in vedirect_entity_config:
entity_type = vedirect_entity_config[CONF_TYPE]
type_flavor = ve_reg.REG_DEFS[entity_type].flavor
if type_flavor not in ("ANY", *flavor):
raise cv.Invalid(
f"Entity type:{entity_type} not available for flavor:{flavor}"
)
return config

def vedirect_platform_schema(platform_entities: dict[str, cv.Schema]):
return VEDIRECT_PLATFORM_SCHEMA.extend(
{cv.Optional(type): schema for type, schema in platform_entities.items()}
vedirect_entity_base_schema = vedirect_entity_base_schema.extend(
vedirect_entity_schema(classes, has_text)
)
return cv.Schema(
{
cv.Required(CONF_VEDIRECT_ID): cv.use_id(Manager),
cv.Optional(CONF_VEDIRECT_ENTITIES): cv.ensure_list(
vedirect_entity_base_schema
),
}
| {cv.Optional(type): schema for type, schema in platform_entities.items()}
).add_extra(_validate_platform_entity)


def local_object_construct(id_: cpp.ID, *args):
Expand Down Expand Up @@ -270,16 +274,29 @@ async def vedirect_platform_to_code(


# main component (Manager) schema
MANAGERS_CONFIG = {}


def validate_manager(config):
# Caching the manager(s) config since we'll need that to validate
# platforms configuration. Especially the 'flavor' setting might affect
# these following steps
MANAGERS_CONFIG[config[ec.CONF_ID]] = config
return config


CONF_AUTO_CREATE_ENTITIES = "auto_create_entities"
CONF_PING_TIMEOUT = "ping_timeout"
CONF_FLAVOR = "flavor"
CONF_ON_FRAME_RECEIVED = "on_frame_received"
CONFIG_SCHEMA = cv.All(
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(Manager),
cv.Optional(ec.CONF_NAME): cv.string_strict,
cv.Optional(CONF_FLAVOR): cv.ensure_list(validate_str_enum(ve_reg.Flavor)),
cv.Optional(
CONF_FLAVOR, default=[flavor.name for flavor in ve_reg.Flavor]
): cv.ensure_list(validate_str_enum(ve_reg.Flavor)),
cv.Optional(CONF_TEXTFRAME): cv.Schema(
{
cv.Optional(CONF_AUTO_CREATE_ENTITIES): cv.boolean,
Expand All @@ -302,18 +319,16 @@ async def vedirect_platform_to_code(
)
.extend(cv.COMPONENT_SCHEMA)
.extend(uart.UART_DEVICE_SCHEMA)
.add_extra(validate_manager)
)


async def to_code(config: dict):
var = cg.new_Pvariable(config[ec.CONF_ID])
cg.add(var.set_vedirect_id(str(var.base)))
cg.add(var.set_vedirect_name(config.get(ec.CONF_NAME, str(var.base))))
if CONF_FLAVOR in config:
for flavor in config[CONF_FLAVOR]:
cg.add_define(f"VEDIRECT_FLAVOR_{flavor}")
else:
cg.add_define("VEDIRECT_FLAVOR_ALL")
for flavor in config[CONF_FLAVOR]:
cg.add_define(f"VEDIRECT_FLAVOR_{flavor}")
if config_textframe := config.get(CONF_TEXTFRAME):
if CONF_AUTO_CREATE_ENTITIES in config_textframe:
cg.add(
Expand Down
19 changes: 7 additions & 12 deletions esphome/components/m3_vedirect/binary_sensor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
from esphome.components import binary_sensor
import esphome.config_validation as cv

from .. import (
CONF_VEDIRECT_ENTITIES,
VEDIRECT_BINARY_ENTITY_BASE_SCHEMA,
m3_vedirect_ns,
new_vedirect_entity,
ve_reg,
vedirect_entity_schema,
vedirect_platform_schema,
vedirect_platform_to_code,
)
Expand All @@ -21,19 +18,17 @@
VEDirectBinarySensor = m3_vedirect_ns.class_("BinarySensor", binary_sensor.BinarySensor)
VEDIRECT_BINARY_SENSOR_SCHEMA = binary_sensor.binary_sensor_schema(
VEDirectBinarySensor
).extend(
vedirect_entity_schema(
(ve_reg.CLASS.BOOLEAN, ve_reg.CLASS.BITMASK, ve_reg.CLASS.ENUM), True
),
VEDIRECT_BINARY_ENTITY_BASE_SCHEMA,
)
).extend(VEDIRECT_BINARY_ENTITY_BASE_SCHEMA)

PLATFORM_ENTITIES = {
CONF_VEDIRECT_ENTITIES: cv.ensure_list(VEDIRECT_BINARY_SENSOR_SCHEMA),
"link_connected": _diagnostic_binary_sensor_schema,
}

CONFIG_SCHEMA = vedirect_platform_schema(PLATFORM_ENTITIES)
CONFIG_SCHEMA = vedirect_platform_schema(
VEDIRECT_BINARY_SENSOR_SCHEMA,
(ve_reg.CLASS.BOOLEAN, ve_reg.CLASS.BITMASK, ve_reg.CLASS.ENUM),
True,
PLATFORM_ENTITIES,
)


async def new_vedirect_binary_sensor(config, manager):
Expand Down
17 changes: 15 additions & 2 deletions esphome/components/m3_vedirect/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ def _generate_enums(file_data: str, ns_mock: str):
def generate():
ve_reg_py.writelines(
(
'"""\nAUTO GENERATED - See generator.py\n"""\n',
"\nimport enum\n",
'"""\nAUTO GENERATED - See generator.py\n"""\n\n',
"from collections import namedtuple\n",
"import enum\n",
"from functools import cached_property\n",
"\nimport esphome.codegen as cg\n",
'\n\nns = cg.global_ns.namespace("m3_ve_reg")\n',
Expand Down Expand Up @@ -101,6 +102,18 @@ def generate():
reg_defs[reg_def_type] = reg_def

_declare_mock_enum("TYPE", reg_defs, "REG_DEF")
ve_reg_py.writelines(
(
'\n\nREG_DEF = namedtuple("REG_DEF", ["flavor", "cls", "register_id", "access"])\n',
"REG_DEFS = {\n",
)
)
for reg_def_type, reg_def in reg_defs.items():
ve_reg_py.write(
f'{INDENT}TYPE.{reg_def_type}.name: REG_DEF("{reg_def[0]}", CLASS.{reg_def[1].split("_")[0]}, {reg_def[2]}, ACCESS.{reg_def[4]}),\n'
)

ve_reg_py.write("}\n")


if __name__ == "__main__":
Expand Down
27 changes: 10 additions & 17 deletions esphome/components/m3_vedirect/number/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,26 @@
import esphome.const as ec

from .. import (
CONF_VEDIRECT_ENTITIES,
m3_vedirect_ns,
new_vedirect_entity,
ve_reg,
vedirect_entity_schema,
vedirect_platform_schema,
vedirect_platform_to_code,
)

VEDirectNumber = m3_vedirect_ns.class_("Number", number.Number)
VEDIRECT_NUMBER_SCHEMA = (
number.number_schema(VEDirectNumber)
.extend(vedirect_entity_schema((ve_reg.CLASS.NUMERIC,), False))
.extend(
{
cv.Required(ec.CONF_MIN_VALUE): cv.float_,
cv.Required(ec.CONF_MAX_VALUE): cv.float_,
cv.Required(ec.CONF_STEP): cv.positive_float,
}
)
VEDIRECT_NUMBER_SCHEMA = number.number_schema(VEDirectNumber).extend(
{
cv.Required(ec.CONF_MIN_VALUE): cv.float_,
cv.Required(ec.CONF_MAX_VALUE): cv.float_,
cv.Required(ec.CONF_STEP): cv.positive_float,
}
)

PLATFORM_ENTITIES = {
CONF_VEDIRECT_ENTITIES: cv.ensure_list(VEDIRECT_NUMBER_SCHEMA),
}

CONFIG_SCHEMA = vedirect_platform_schema(PLATFORM_ENTITIES)
PLATFORM_ENTITIES = {}
CONFIG_SCHEMA = vedirect_platform_schema(
VEDIRECT_NUMBER_SCHEMA, (ve_reg.CLASS.NUMERIC,), False, PLATFORM_ENTITIES
)


async def new_vedirect_number(config, manager):
Expand Down
16 changes: 5 additions & 11 deletions esphome/components/m3_vedirect/select/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
from esphome.components import select
import esphome.config_validation as cv

from .. import (
CONF_VEDIRECT_ENTITIES,
m3_vedirect_ns,
new_vedirect_entity,
ve_reg,
vedirect_entity_schema,
vedirect_platform_schema,
vedirect_platform_to_code,
)
Expand All @@ -15,15 +12,12 @@

# m3_vedirect::Select mapped to HEX/TEXT data
VEDirectSelect = m3_vedirect_ns.class_("Select", select.Select)
VEDIRECT_SELECT_SCHEMA = select.select_schema(VEDirectSelect).extend(
vedirect_entity_schema((ve_reg.CLASS.ENUM,), False),
)

PLATFORM_ENTITIES = {
CONF_VEDIRECT_ENTITIES: cv.ensure_list(VEDIRECT_SELECT_SCHEMA),
}
VEDIRECT_SELECT_SCHEMA = select.select_schema(VEDirectSelect)

CONFIG_SCHEMA = vedirect_platform_schema(PLATFORM_ENTITIES)
PLATFORM_ENTITIES = {}
CONFIG_SCHEMA = vedirect_platform_schema(
VEDIRECT_SELECT_SCHEMA, (ve_reg.CLASS.ENUM,), False, PLATFORM_ENTITIES
)


async def new_vedirect_select(config, manager):
Expand Down
12 changes: 4 additions & 8 deletions esphome/components/m3_vedirect/sensor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
from esphome.components import sensor
import esphome.config_validation as cv
import esphome.const as ec

from .. import (
CONF_VEDIRECT_ENTITIES,
m3_vedirect_ns,
new_vedirect_entity,
ve_reg,
vedirect_entity_schema,
vedirect_platform_schema,
vedirect_platform_to_code,
)
Expand All @@ -16,20 +13,19 @@
# CONF_TEXT_SCALE = "text_scale"
# CONF_HEX_SCALE = "hex_scale"
VEDirectSensor = m3_vedirect_ns.class_("Sensor", sensor.Sensor)
VEDIRECT_SENSOR_SCHEMA = sensor.sensor_schema(VEDirectSensor).extend(
vedirect_entity_schema((ve_reg.CLASS.NUMERIC,), True)
)
VEDIRECT_SENSOR_SCHEMA = sensor.sensor_schema(VEDirectSensor)

PLATFORM_ENTITIES = {
CONF_VEDIRECT_ENTITIES: cv.ensure_list(VEDIRECT_SENSOR_SCHEMA),
"run_time": sensor.sensor_schema(
entity_category="diagnostic",
device_class=ec.DEVICE_CLASS_DURATION,
unit_of_measurement=ec.UNIT_SECOND,
),
}

CONFIG_SCHEMA = vedirect_platform_schema(PLATFORM_ENTITIES)
CONFIG_SCHEMA = vedirect_platform_schema(
VEDIRECT_SENSOR_SCHEMA, (ve_reg.CLASS.NUMERIC,), True, PLATFORM_ENTITIES
)


async def new_vedirect_sensor(config, manager):
Expand Down
21 changes: 8 additions & 13 deletions esphome/components/m3_vedirect/switch/__init__.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,28 @@
from esphome.components import switch
import esphome.config_validation as cv

from .. import (
CONF_VEDIRECT_ENTITIES,
VEDIRECT_BINARY_ENTITY_BASE_SCHEMA,
m3_vedirect_ns,
new_vedirect_entity,
ve_reg,
vedirect_entity_schema,
vedirect_platform_schema,
vedirect_platform_to_code,
)

VEDirectSwitch = m3_vedirect_ns.class_("Switch", switch.Switch)
VEDIRECT_SWITCH_SCHEMA = switch.switch_schema(
VEDirectSwitch, default_restore_mode="DISABLED"
).extend(
vedirect_entity_schema(
(ve_reg.CLASS.BOOLEAN, ve_reg.CLASS.BITMASK, ve_reg.CLASS.ENUM), False
),
VEDIRECT_BINARY_ENTITY_BASE_SCHEMA,
)
).extend(VEDIRECT_BINARY_ENTITY_BASE_SCHEMA)


PLATFORM_ENTITIES = {
CONF_VEDIRECT_ENTITIES: cv.ensure_list(VEDIRECT_SWITCH_SCHEMA),
}
PLATFORM_ENTITIES = {}

CONFIG_SCHEMA = vedirect_platform_schema(PLATFORM_ENTITIES)
CONFIG_SCHEMA = vedirect_platform_schema(
VEDIRECT_SWITCH_SCHEMA,
(ve_reg.CLASS.BOOLEAN, ve_reg.CLASS.BITMASK, ve_reg.CLASS.ENUM),
False,
PLATFORM_ENTITIES,
)


async def new_vedirect_switch(config, manager):
Expand Down
Loading

0 comments on commit a87e443

Please sign in to comment.