Skip to content

Commit

Permalink
refactor TEXT_DEF to streamline yaml register configuration (m3_vedir…
Browse files Browse the repository at this point in the history
…ect)
  • Loading branch information
krahabb committed Nov 7, 2024
1 parent 7f3e918 commit 7cc0a96
Show file tree
Hide file tree
Showing 29 changed files with 702 additions and 477 deletions.
209 changes: 183 additions & 26 deletions esphome/components/m3_vedirect/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,50 @@
from enum import StrEnum
from functools import partial
import typing

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_ID, CONF_NAME, CONF_PAYLOAD, CONF_TRIGGER_ID
import esphome.cpp_generator as cpp

CODEOWNERS = ["@krahabb"]
DEPENDENCIES = ["binary_sensor", "select", "sensor", "switch", "text_sensor", "uart"]
AUTO_LOAD = ["binary_sensor", "select", "sensor", "switch", "text_sensor"]
MULTI_CONF = True

m3_ve_reg_ns = cg.global_ns.namespace("m3_ve_reg")
HEXFRAME_struct = m3_ve_reg_ns.struct("HEXFRAME")
HEXFRAME_DATATYPE_enum = HEXFRAME_struct.enum("DATA_TYPE")


class DATA_TYPE(StrEnum):
STRING = "STRING"
UN8 = "UN8"
UN16 = "UN16"
UN32 = "UN32"
SN8 = "SN8"
SN16 = "SN16"
SN32 = "SN32"


REG_DEF_struct = m3_ve_reg_ns.struct("REG_DEF")
REG_DEF_CLASS_enum = REG_DEF_struct.enum("CLASS")


class CLASS(StrEnum):
BITMASK = "BITMASK"
BOOLEAN = "BOOLEAN"
ENUM = "ENUM"
NUMERIC = "NUMERIC"
STRING = "STRING"


ENUM_DEF_struct = m3_ve_reg_ns.struct("ENUM_DEF")
ENUM_DEF_LOOKUP_DEF_struct = ENUM_DEF_struct.struct("LOOKUP_DEF")


m3_vedirect_ns = cg.esphome_ns.namespace("m3_vedirect")
Manager = m3_vedirect_ns.class_("Manager", uart.UARTDevice, cg.Component)
HexFrame = m3_vedirect_ns.class_("HexFrame")
Expand All @@ -34,26 +68,100 @@ def validate_register_id():
return cv.hex_int_range(min=0, max=65535)


def validate_enum_lookup_def(value):
if not isinstance(value, dict) or len(value) != 1:
raise cv.Invalid(
f"Expected a single item dict with {{value}}: {{label}}, got {value}"
)
for enum_value, enum_label in value.items():
return int(enum_value), enum_label


# CONF_REGISTER_ID = "register_id"
# CONF_CLASS = "class"
CONF_TEXT_LABEL = "text_label"
CONF_REGISTER_ID = "register_id"
# (HEX) Register schema
CONF_REG_DEF_ID = "reg_def_id"
CONF_ENUM_DEF_ID = "enum_def_id"
CONF_ADDRESS = "address"
CONF_DATA_TYPE = "data_type"
HexDataType = HexFrame.enum("DataType")
DATA_TYPES = {
"uint8": HexDataType.enum("u8"),
"uint16": HexDataType.enum("u16"),
"int16": HexDataType.enum("i16"),
"uint32": HexDataType.enum("u32"),
VEDIRECT_REGISTER_SCHEMA = {
cv.GenerateID(CONF_REG_DEF_ID): cv.declare_id(REG_DEF_struct),
cv.GenerateID(CONF_ENUM_DEF_ID): cv.declare_id(ENUM_DEF_struct),
# binds to the corresponding HEX register
cv.Optional(CONF_ADDRESS): validate_register_id(),
# configures the format of the HEX register
cv.Optional(CONF_DATA_TYPE): cv.enum(
{_dt.name: HEXFRAME_DATATYPE_enum.enum(_dt.name) for _dt in DATA_TYPE}
),
}
VEDIRECT_ENTITY_SCHEMA = cv.Schema(
{
# binds to the corresponding TEXT frame field
cv.Optional(CONF_TEXT_LABEL): cv.string,
# binds to the corresponding HEX register
cv.Optional(CONF_REGISTER_ID): validate_register_id(),
# configures the format of the HEX register
cv.Optional(CONF_DATA_TYPE): cv.enum(DATA_TYPES),
CONF_TEXT_SCALE = "text_scale"
CONF_SCALE = "scale"
CONF_UNIT = "unit"
VEDIRECT_REGISTER_CLASS_SCHEMAS = {
CLASS.BOOLEAN: cv.Schema({}),
CLASS.BITMASK: cv.ensure_list(validate_enum_lookup_def),
CLASS.ENUM: cv.ensure_list(validate_enum_lookup_def),
CLASS.NUMERIC: cv.Schema(
{
cv.Optional(CONF_SCALE): cv.float_,
cv.Optional(CONF_TEXT_SCALE): cv.float_,
cv.Optional(CONF_UNIT): cv.string,
}
),
CLASS.STRING: cv.Schema({}),
}
# TEXT record schema
VEDIRECT_TEXTRECORD_SCHEMA = {
# binds to the corresponding TEXT frame field
cv.Optional(CONF_TEXT_LABEL): cv.string,
}


CONF_TYPE = "type"
CONF_REGISTER = "register"


def vedirect_entity_schema(classes: typing.Iterable[CLASS], has_text):
register_schema = dict(VEDIRECT_REGISTER_SCHEMA)
for _cls in classes:
register_schema |= {
cv.Exclusive(_cls.name.lower(), "class"): VEDIRECT_REGISTER_CLASS_SCHEMAS[
_cls
]
}
if has_text:
register_schema |= VEDIRECT_TEXTRECORD_SCHEMA
return {
cv.Exclusive(CONF_TYPE, "type"): cv.string,
cv.Exclusive(CONF_REGISTER, "type"): cv.Schema(register_schema),
}
)


# Basic schema for binary-like entities: binary_sensor, switch
CONF_MASK = "mask"
VEDIRECT_BINARY_ENTITY_BASE_SCHEMA = {
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(
Expand All @@ -71,21 +179,69 @@ def vedirect_platform_schema(
)


def local_object_construct(id_: cpp.ID, *args):
obj = cpp.MockObj(id_, ".")
expression = cpp.RawStatement(f"{id_.type} {id_}({cpp.ExpressionList(*args)});")
cg.add(expression)
return obj


async def new_vedirect_entity(config, manager):
var = cg.new_Pvariable(config[CONF_ID], manager)
valid = False
if CONF_TEXT_LABEL in config:
valid = True
cg.add(var.set_text_label(manager, config[CONF_TEXT_LABEL]))
if CONF_REGISTER_ID in config:

if CONF_REGISTER in config:
valid = True
cg.add(var.set_register_id(manager, config[CONF_REGISTER_ID]))
if CONF_DATA_TYPE in config:
cg.add(var.set_hex_data_type(config[CONF_DATA_TYPE]))
register_config = config[CONF_REGISTER]
if CONF_ADDRESS in register_config:
reg_def = local_object_construct(
register_config[CONF_REG_DEF_ID], register_config[CONF_ADDRESS]
)

for _cls in CLASS:
_cls_key = _cls.name.lower()
if _cls_key in register_config:
cg.add(
cpp.AssignmentExpression(
None, "", reg_def.cls, REG_DEF_CLASS_enum.enum(_cls.name)
)
)
match _cls:
case CLASS.BITMASK | CLASS.ENUM:
enum_def = local_object_construct(
register_config[CONF_ENUM_DEF_ID],
register_config[_cls_key],
)
cg.add(
cpp.AssignmentExpression(
None,
"",
reg_def.enum_def,
cpp.UnaryOpExpression("&", enum_def),
)
)
case CLASS.NUMERIC:
pass
break

if CONF_DATA_TYPE in register_config:
cg.add(
cpp.AssignmentExpression(
None, "", reg_def.data_type, register_config[CONF_DATA_TYPE]
)
)

cg.add(var.set_reg_def(manager, cpp.UnaryOpExpression("&", reg_def)))

if CONF_TEXT_LABEL in register_config:
cg.add(var.set_text_label(manager, register_config[CONF_TEXT_LABEL]))

# configure binary-like entities
if CONF_MASK in config:
cg.add(var.set_mask(config[CONF_MASK]))

if not valid:
raise cv.Invalid(
f"Either {CONF_TEXT_LABEL} or {CONF_REGISTER_ID} must be provided"
)
raise cv.Invalid(f"Either {CONF_TYPE} or {CONF_REGISTER} must be provided")
return var


Expand Down Expand Up @@ -207,6 +363,7 @@ async def action_to_code(


CONF_COMMAND = "command"
CONF_REGISTER_ID = "register_id"
CONF_DATA = "data"
CONF_DATA_SIZE = "data_size"
MANAGER_ACTIONS = {
Expand Down
22 changes: 9 additions & 13 deletions esphome/components/m3_vedirect/binary_sensor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import esphome.codegen as cg
from esphome.components import binary_sensor
import esphome.config_validation as cv

from .. import (
CLASS,
CONF_VEDIRECT_ENTITIES,
VEDIRECT_ENTITY_SCHEMA,
VEDIRECT_BINARY_ENTITY_BASE_SCHEMA,
m3_vedirect_ns,
new_vedirect_entity,
vedirect_entity_schema,
vedirect_platform_schema,
vedirect_platform_to_code,
)
Expand All @@ -16,16 +17,13 @@
entity_category="diagnostic"
)

CONF_MASK = "mask"

VEDirectBinarySensor = m3_vedirect_ns.class_("BinarySensor", binary_sensor.BinarySensor)
VEDIRECT_BINARY_SENSOR_SCHEMA = (
binary_sensor.binary_sensor_schema(VEDirectBinarySensor)
.extend(VEDIRECT_ENTITY_SCHEMA)
.extend(
{
cv.Optional(CONF_MASK): cv.uint32_t,
}
)
VEDIRECT_BINARY_SENSOR_SCHEMA = binary_sensor.binary_sensor_schema(
VEDirectBinarySensor
).extend(
vedirect_entity_schema((CLASS.BOOLEAN, CLASS.BITMASK, CLASS.ENUM), True),
VEDIRECT_BINARY_ENTITY_BASE_SCHEMA,
)

PLATFORM_ENTITIES = {
Expand All @@ -38,8 +36,6 @@

async def new_vedirect_binary_sensor(config, manager):
var = await new_vedirect_entity(config, manager)
if CONF_MASK in config:
cg.add(var.set_mask(config[CONF_MASK]))
await binary_sensor.register_binary_sensor(var, config)
return var

Expand Down
24 changes: 4 additions & 20 deletions esphome/components/m3_vedirect/binary_sensor/binary_sensor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,20 @@ void BinarySensor::init_reg_def_() {
switch (this->reg_def_->cls) {
case REG_DEF::CLASS::BITMASK:
this->parse_hex_ = parse_hex_bitmask_;
this->parse_text_ = parse_text_bitmask_;
break;
case REG_DEF::CLASS::ENUM:
this->parse_hex_ = parse_hex_enum_;
this->parse_text_ = parse_text_enum_;
break;
default:
// defaults if nothing better
this->parse_hex_ = parse_hex_default_;
break;
}
}

void BinarySensor::parse_hex_default_(HexRegister *hex_register, const RxHexFrame *hex_frame) {
static_assert(RxHexFrame::ALLOCATED_DATA_SIZE >= 1, "HexFrame storage might lead to access overflow");
// By default considering the register as a BOOLEAN
static_cast<BinarySensor *>(hex_register)->publish_state(hex_frame->data_u8());
}

Expand All @@ -45,23 +46,8 @@ void BinarySensor::parse_hex_enum_(HexRegister *hex_register, const RxHexFrame *
static_cast<BinarySensor *>(hex_register)->parse_enum_(hex_frame->data_u8());
}

void BinarySensor::init_text_def_(const TEXT_DEF *text_def) {
switch (text_def->cls) {
// When installing a specialized parse_text ensure the correct 'reg_def_' is in place
case REG_DEF::CLASS::BITMASK:
this->parse_text_ = parse_text_bitmask_;
break;
case REG_DEF::CLASS::ENUM:
this->parse_text_ = parse_text_enum_;
break;
default:
this->parse_text_ = parse_text_default_;
break;
}
}

void BinarySensor::parse_text_default_(HexRegister *hex_register, const char *text_value) {
static_cast<BinarySensor *>(hex_register)->parse_string_(text_value);
static_cast<BinarySensor *>(hex_register)->publish_state(!strcasecmp(text_value, "ON"));
}

void BinarySensor::parse_text_bitmask_(HexRegister *hex_register, const char *text_value) {
Expand All @@ -86,7 +72,5 @@ void BinarySensor::parse_bitmask_(BITMASK_DEF::bitmask_t bitmask_value) {

void BinarySensor::parse_enum_(ENUM_DEF::enum_t enum_value) { this->publish_state(enum_value == this->mask_); }

void BinarySensor::parse_string_(const char *string_value) { this->publish_state(!strcasecmp(string_value, "ON")); }

} // namespace m3_vedirect
} // namespace esphome
4 changes: 1 addition & 3 deletions esphome/components/m3_vedirect/binary_sensor/binary_sensor.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace m3_vedirect {

class BinarySensor final : public Entity, public esphome::binary_sensor::BinarySensor {
public:
BinarySensor(Manager *Manager) {}
BinarySensor(Manager *Manager) : Entity(parse_hex_default_, parse_text_default_) {}

void set_mask(uint32_t mask) { this->mask_ = mask; }

Expand All @@ -24,14 +24,12 @@ class BinarySensor final : public Entity, public esphome::binary_sensor::BinaryS
static void parse_hex_bitmask_(HexRegister *hex_register, const RxHexFrame *hex_frame);
static void parse_hex_enum_(HexRegister *hex_register, const RxHexFrame *hex_frame);

void init_text_def_(const TEXT_DEF *text_def) override;
static void parse_text_default_(HexRegister *hex_register, const char *text_value);
static void parse_text_bitmask_(HexRegister *hex_register, const char *text_value);
static void parse_text_enum_(HexRegister *hex_register, const char *text_value);

inline void parse_bitmask_(BITMASK_DEF::bitmask_t bitmask_value) override;
inline void parse_enum_(ENUM_DEF::enum_t enum_value) override;
inline void parse_string_(const char *string_value) override;
};

} // namespace m3_vedirect
Expand Down
Loading

0 comments on commit 7cc0a96

Please sign in to comment.