From 7cc0a96ddb8f5831c7ab80ea1bea2997043f114e Mon Sep 17 00:00:00 2001 From: krahabb <13969600+krahabb@users.noreply.github.com> Date: Thu, 7 Nov 2024 16:22:40 +0100 Subject: [PATCH] refactor TEXT_DEF to streamline yaml register configuration (m3_vedirect) --- esphome/components/m3_vedirect/__init__.py | 209 +++++++++++++++--- .../m3_vedirect/binary_sensor/__init__.py | 22 +- .../binary_sensor/binary_sensor.cpp | 24 +- .../m3_vedirect/binary_sensor/binary_sensor.h | 4 +- esphome/components/m3_vedirect/entity.cpp | 32 +-- esphome/components/m3_vedirect/entity.h | 19 +- .../components/m3_vedirect/hexregister.cpp | 2 +- esphome/components/m3_vedirect/hexregister.h | 14 +- esphome/components/m3_vedirect/manager.cpp | 16 +- .../components/m3_vedirect/select/__init__.py | 5 +- .../components/m3_vedirect/select/select.cpp | 47 ++-- .../components/m3_vedirect/select/select.h | 5 +- .../components/m3_vedirect/sensor/__init__.py | 21 +- .../components/m3_vedirect/sensor/sensor.cpp | 76 ++++--- .../components/m3_vedirect/sensor/sensor.h | 18 +- .../components/m3_vedirect/switch/__init__.py | 24 +- .../components/m3_vedirect/switch/switch.cpp | 52 ++++- .../components/m3_vedirect/switch/switch.h | 9 +- .../m3_vedirect/text_sensor/__init__.py | 5 +- .../m3_vedirect/text_sensor/text_sensor.cpp | 39 +--- .../m3_vedirect/text_sensor/text_sensor.h | 3 +- esphome/components/m3_vedirect/ve_hexframe.h | 2 +- esphome/components/m3_vedirect/ve_reg.h | 24 +- .../components/m3_vedirect/ve_reg_defs.cpp | 84 +++---- esphome/components/m3_vedirect/ve_reg_defs.h | 164 +++++--------- esphome/components/m3_vedirect/ve_reg_enums.h | 3 + esphome/components/m3_vedirect/ve_reg_macro.h | 137 ++++++++++++ .../components/m3_vedirect/ve_reg_registers.h | 46 +++- esphome/components/m3_vedirect/ve_reg_text.h | 73 +++--- 29 files changed, 702 insertions(+), 477 deletions(-) create mode 100644 esphome/components/m3_vedirect/ve_reg_macro.h diff --git a/esphome/components/m3_vedirect/__init__.py b/esphome/components/m3_vedirect/__init__.py index 7f0f7a155042..5753bd34a0d8 100644 --- a/esphome/components/m3_vedirect/__init__.py +++ b/esphome/components/m3_vedirect/__init__.py @@ -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") @@ -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( @@ -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 @@ -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 = { diff --git a/esphome/components/m3_vedirect/binary_sensor/__init__.py b/esphome/components/m3_vedirect/binary_sensor/__init__.py index 6940d0afc1eb..d508faaea99a 100644 --- a/esphome/components/m3_vedirect/binary_sensor/__init__.py +++ b/esphome/components/m3_vedirect/binary_sensor/__init__.py @@ -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, ) @@ -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 = { @@ -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 diff --git a/esphome/components/m3_vedirect/binary_sensor/binary_sensor.cpp b/esphome/components/m3_vedirect/binary_sensor/binary_sensor.cpp index 77a5a7d00747..d10d42d40b3a 100644 --- a/esphome/components/m3_vedirect/binary_sensor/binary_sensor.cpp +++ b/esphome/components/m3_vedirect/binary_sensor/binary_sensor.cpp @@ -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(hex_register)->publish_state(hex_frame->data_u8()); } @@ -45,23 +46,8 @@ void BinarySensor::parse_hex_enum_(HexRegister *hex_register, const RxHexFrame * static_cast(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(hex_register)->parse_string_(text_value); + static_cast(hex_register)->publish_state(!strcasecmp(text_value, "ON")); } void BinarySensor::parse_text_bitmask_(HexRegister *hex_register, const char *text_value) { @@ -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 diff --git a/esphome/components/m3_vedirect/binary_sensor/binary_sensor.h b/esphome/components/m3_vedirect/binary_sensor/binary_sensor.h index 9ab2f3cd2ee2..4cb4ba2c8292 100644 --- a/esphome/components/m3_vedirect/binary_sensor/binary_sensor.h +++ b/esphome/components/m3_vedirect/binary_sensor/binary_sensor.h @@ -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; } @@ -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 diff --git a/esphome/components/m3_vedirect/entity.cpp b/esphome/components/m3_vedirect/entity.cpp index 0bb256ae5cfe..5c61d57d8390 100644 --- a/esphome/components/m3_vedirect/entity.cpp +++ b/esphome/components/m3_vedirect/entity.cpp @@ -10,34 +10,22 @@ namespace m3_vedirect { static const char *const TAG = "m3_vedirect.entity"; -const char *Entity::UNIT_TO_DEVICE_CLASS[] = { - "current", "voltage", "apparent_power", "power", nullptr, "energy", "battery", "duration", "temperature", -}; -const sensor::StateClass Entity::UNIT_TO_STATE_CLASS[] = { - sensor::StateClass::STATE_CLASS_MEASUREMENT, sensor::StateClass::STATE_CLASS_MEASUREMENT, - sensor::StateClass::STATE_CLASS_MEASUREMENT, sensor::StateClass::STATE_CLASS_MEASUREMENT, - sensor::StateClass::STATE_CLASS_TOTAL, sensor::StateClass::STATE_CLASS_TOTAL_INCREASING, - sensor::StateClass::STATE_CLASS_MEASUREMENT, sensor::StateClass::STATE_CLASS_MEASUREMENT, - sensor::StateClass::STATE_CLASS_MEASUREMENT, -}; - +/// @brief Called by the yaml generator. +/// @param manager +/// @param label void Entity::set_text_label(Manager *manager, const char *label) { auto text_def = TEXT_DEF::find_label(label); if (text_def) { - this->init_text_def_(text_def); - auto reg_def = REG_DEF::find_type(text_def->register_type); - if (reg_def) - this->set_reg_def(manager, reg_def); - } else { - this->init_text_def_(&TEXT_DEF_UNDEFINED); + if (this->reg_def_ == ®_DEF_UNDEFINED) { + // only set reg_def from our presets (if any) if the yaml generated code + // didn't set a custom configuration + auto reg_def = REG_DEF::find_type(text_def->register_type); + if (reg_def) + this->set_reg_def(manager, reg_def); + } } manager->text_entities_.emplace(label, this); } -void Entity::set_register_id(Manager *manager, register_id_t register_id) { - auto reg_def = REG_DEF::find_register_id(register_id); - this->set_reg_def(manager, reg_def ? reg_def : new REG_DEF(register_id)); -} - } // namespace m3_vedirect } // namespace esphome diff --git a/esphome/components/m3_vedirect/entity.h b/esphome/components/m3_vedirect/entity.h index a19d1a18da07..4e7fd8386193 100644 --- a/esphome/components/m3_vedirect/entity.h +++ b/esphome/components/m3_vedirect/entity.h @@ -14,25 +14,18 @@ namespace m3_vedirect { class Entity : public HexRegister { public: - typedef REG_DEF::CLASS CLASS; - - // configuration symbols for numeric sensors - typedef REG_DEF::UNIT UNIT; - static const char *UNIT_TO_DEVICE_CLASS[]; - static const sensor::StateClass UNIT_TO_STATE_CLASS[]; - - typedef REG_DEF::DIGITS DIGITS; - /// @brief Binds the entity to a TEXT FRAME field label so that text frame parsing /// will be automatically routed. This method is part of the public interface /// called by yaml generaed code /// @param label the name of the TEXT FRAME record to bind void set_text_label(Manager *manager, const char *label); - void set_register_id(Manager *manager, register_id_t register_id); - protected: friend class Manager; + + Entity(parse_hex_func_t parse_hex_func = parse_hex_empty_, parse_text_func_t parse_text_func = parse_text_empty_) + : HexRegister(parse_hex_func, parse_text_func) {} + /// @brief Called when an entity is dynamically initialized by the Manager loop. /// This will in turn call the proper register function against App/api virtual void dynamic_register_(){}; @@ -43,7 +36,9 @@ class Entity : public HexRegister { class ConfigEntity : public Entity { public: Manager *const manager; - ConfigEntity(Manager *manager) : manager(manager) {} + ConfigEntity(Manager *manager, parse_hex_func_t parse_hex_func = parse_hex_empty_, + parse_text_func_t parse_text_func = parse_text_empty_) + : Entity(parse_hex_func, parse_text_func), manager(manager) {} }; } // namespace m3_vedirect diff --git a/esphome/components/m3_vedirect/hexregister.cpp b/esphome/components/m3_vedirect/hexregister.cpp index 2390bbd7e59b..7a350071c08b 100644 --- a/esphome/components/m3_vedirect/hexregister.cpp +++ b/esphome/components/m3_vedirect/hexregister.cpp @@ -5,7 +5,7 @@ namespace esphome { namespace m3_vedirect { const REG_DEF HexRegister::REG_DEF_UNDEFINED(0); -const TEXT_DEF HexRegister::TEXT_DEF_UNDEFINED; + void HexRegister::set_reg_def(Manager *manager, const REG_DEF *reg_def) { this->reg_def_ = reg_def; this->init_reg_def_(); diff --git a/esphome/components/m3_vedirect/hexregister.h b/esphome/components/m3_vedirect/hexregister.h index d52c6f75bd73..93228bd97723 100644 --- a/esphome/components/m3_vedirect/hexregister.h +++ b/esphome/components/m3_vedirect/hexregister.h @@ -21,8 +21,12 @@ class HexRegister { protected: friend class Manager; static const REG_DEF REG_DEF_UNDEFINED; - const REG_DEF *reg_def_{®_DEF_UNDEFINED}; - parse_hex_func_t parse_hex_{parse_hex_empty_}; + const REG_DEF *reg_def_; + parse_hex_func_t parse_hex_; + parse_text_func_t parse_text_; + + HexRegister(parse_hex_func_t parse_hex_func = parse_hex_empty_, parse_text_func_t parse_text_func = parse_text_empty_) + : reg_def_(®_DEF_UNDEFINED), parse_hex_(parse_hex_func), parse_text_(parse_text_func) {} // called by the Manager when VEDirect timeouts (we'll send 'unknown' to APIServer) virtual void link_disconnected_(){}; @@ -33,12 +37,6 @@ class HexRegister { static void parse_hex_empty_(HexRegister *hex_register, const RxHexFrame *hexframe) {} - static const TEXT_DEF TEXT_DEF_UNDEFINED; - parse_text_func_t parse_text_{parse_text_empty_}; - /// @brief Preset entity properties based off our TEXT_DEF. This is being called - /// automatically when a proper definition is available. - virtual void init_text_def_(const TEXT_DEF *text_def) {} - static void parse_text_empty_(HexRegister *hex_register, const char *text_value) {} virtual void parse_bitmask_(BITMASK_DEF::bitmask_t bitmask_value){}; diff --git a/esphome/components/m3_vedirect/manager.cpp b/esphome/components/m3_vedirect/manager.cpp index 5e6e59c1db2a..b7f5ec088b5d 100644 --- a/esphome/components/m3_vedirect/manager.cpp +++ b/esphome/components/m3_vedirect/manager.cpp @@ -201,27 +201,15 @@ HexRegister *Manager::build_text_entity_(const char *label) { if (reg_def) { hex_register = this->get_hex_register_(reg_def->register_id, true); } else { - switch (text_def->cls) { - case REG_DEF::CLASS::NUMERIC: - hex_register = this->dynamic_build_entity_(text_def->description, text_def->label); - break; - case REG_DEF::CLASS::BOOLEAN: - hex_register = this->dynamic_build_entity_(text_def->description, text_def->label); - break; - default: - hex_register = this->dynamic_build_entity_(text_def->description, text_def->label); - } + hex_register = this->dynamic_build_entity_(text_def->description, text_def->label); } - } else { - // ENTITIES_DEF lacks the definition for this parameter so + // We lack the definition for this TEXT RECORD so // we return a plain TextSensor entity. // We allocate a copy since the label param is 'volatile' label = strdup(label); hex_register = this->dynamic_build_entity_(label, label); - text_def = &HexRegister::TEXT_DEF_UNDEFINED; } - hex_register->init_text_def_(text_def); this->text_entities_.emplace(label, hex_register); return hex_register; } diff --git a/esphome/components/m3_vedirect/select/__init__.py b/esphome/components/m3_vedirect/select/__init__.py index 4e78ddb446ca..9a4f3064aca9 100644 --- a/esphome/components/m3_vedirect/select/__init__.py +++ b/esphome/components/m3_vedirect/select/__init__.py @@ -2,10 +2,11 @@ import esphome.config_validation as cv from .. import ( + CLASS, CONF_VEDIRECT_ENTITIES, - VEDIRECT_ENTITY_SCHEMA, m3_vedirect_ns, new_vedirect_entity, + vedirect_entity_schema, vedirect_platform_schema, vedirect_platform_to_code, ) @@ -15,7 +16,7 @@ # 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 + vedirect_entity_schema((CLASS.ENUM,), False), ) PLATFORM_ENTITIES = { diff --git a/esphome/components/m3_vedirect/select/select.cpp b/esphome/components/m3_vedirect/select/select.cpp index f66312e3d130..2ea27f053122 100644 --- a/esphome/components/m3_vedirect/select/select.cpp +++ b/esphome/components/m3_vedirect/select/select.cpp @@ -25,10 +25,9 @@ void Select::init_reg_def_() { this->traits_().options().push_back(std::string(lookup_def.label)); } 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; } } @@ -36,16 +35,7 @@ void Select::init_reg_def_() { void Select::parse_hex_default_(HexRegister *hex_register, const RxHexFrame *hex_frame) { std::string hex_value; if (hex_frame->data_to_hex(hex_value)) { - Select *select = static_cast(hex_register)->parse_string_(hex_value.c_str()); } } @@ -54,16 +44,8 @@ void Select::parse_hex_enum_(HexRegister *hex_register, const RxHexFrame *hex_fr static_cast(hex_register)->parse_string_(text_value); } void Select::parse_text_enum_(HexRegister *hex_register, const char *text_value) { @@ -99,14 +81,25 @@ void Select::parse_enum_(ENUM_DEF::enum_t enum_value) { } } -void Select::control(const std::string &value) { - if (this->reg_def_) { - auto lookup_def = this->reg_def_->enum_def->lookup_value(value.c_str()); - if (lookup_def) - this->manager->send_register_set(this->reg_def_->register_id, lookup_def->value); +void Select::parse_string_(const char *string_value) { + if (strcmp(this->state.c_str(), string_value)) { + auto &options = this->traits_().options(); + auto value = std::string(string_value); + auto it = std::find(options.begin(), options.end(), value); + auto index = std::distance(options.begin(), it); + if (it == options.end()) { + options.push_back(value); + } + this->publish_state_(index); } } +void Select::control(const std::string &value) { + auto lookup_def = this->reg_def_->enum_def->lookup_value(value.c_str()); + if (lookup_def) + this->manager->send_register_set(this->reg_def_->register_id, lookup_def->value); +} + void Select::publish_state_(size_t index) { this->has_state_ = true; this->state = this->traits_().options()[index]; diff --git a/esphome/components/m3_vedirect/select/select.h b/esphome/components/m3_vedirect/select/select.h index af1209097699..6d9d138ba16b 100644 --- a/esphome/components/m3_vedirect/select/select.h +++ b/esphome/components/m3_vedirect/select/select.h @@ -8,7 +8,7 @@ namespace m3_vedirect { class Select final : public ConfigEntity, public esphome::select::Select { public: - Select(Manager *manager) : ConfigEntity(manager) {} + Select(Manager *manager) : ConfigEntity(manager, parse_hex_default_, parse_text_default_) {} protected: friend class Manager; @@ -20,10 +20,11 @@ class Select final : public ConfigEntity, public esphome::select::Select { static void parse_hex_default_(HexRegister *hexregister, const RxHexFrame *hexframe); static void parse_hex_enum_(HexRegister *hexregister, const RxHexFrame *hexframe); - 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_enum_(HexRegister *hex_register, const char *text_value); inline void parse_enum_(ENUM_DEF::enum_t enum_value) override; + inline void parse_string_(const char *string_value) override; // interface esphome::select::Select diff --git a/esphome/components/m3_vedirect/sensor/__init__.py b/esphome/components/m3_vedirect/sensor/__init__.py index e696e409c40e..85d0f4db3d0f 100644 --- a/esphome/components/m3_vedirect/sensor/__init__.py +++ b/esphome/components/m3_vedirect/sensor/__init__.py @@ -1,30 +1,23 @@ -import esphome.codegen as cg from esphome.components import sensor import esphome.config_validation as cv import esphome.const as ec from .. import ( + CLASS, CONF_VEDIRECT_ENTITIES, - VEDIRECT_ENTITY_SCHEMA, m3_vedirect_ns, new_vedirect_entity, + vedirect_entity_schema, vedirect_platform_schema, vedirect_platform_to_code, ) # m3_vedirect::Sensor mapped to HEX/TEXT data -CONF_TEXT_SCALE = "text_scale" -CONF_HEX_SCALE = "hex_scale" +# 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) - .extend( - { - cv.Optional(CONF_TEXT_SCALE): cv.float_, - cv.Optional(CONF_HEX_SCALE): cv.float_, - } - ) +VEDIRECT_SENSOR_SCHEMA = sensor.sensor_schema(VEDirectSensor).extend( + vedirect_entity_schema((CLASS.NUMERIC,), True) ) PLATFORM_ENTITIES = { @@ -41,8 +34,6 @@ async def new_vedirect_sensor(config, manager): var = await new_vedirect_entity(config, manager) - if CONF_TEXT_SCALE in config: - cg.add(var.set_text_scale(config[CONF_TEXT_SCALE])) await sensor.register_sensor(var, config) return var diff --git a/esphome/components/m3_vedirect/sensor/sensor.cpp b/esphome/components/m3_vedirect/sensor/sensor.cpp index 5894e2ebebbd..9f62e4324762 100644 --- a/esphome/components/m3_vedirect/sensor/sensor.cpp +++ b/esphome/components/m3_vedirect/sensor/sensor.cpp @@ -6,6 +6,29 @@ namespace esphome { namespace m3_vedirect { +const char *Sensor::UNIT_TO_DEVICE_CLASS[REG_DEF::UNIT::UNIT_COUNT] = { + nullptr, "current", "voltage", "apparent_power", "power", nullptr, "energy", "battery", "duration", "temperature", +}; +const sensor::StateClass Sensor::UNIT_TO_STATE_CLASS[REG_DEF::UNIT::UNIT_COUNT] = { + sensor::StateClass::STATE_CLASS_NONE, + sensor::StateClass::STATE_CLASS_MEASUREMENT, + sensor::StateClass::STATE_CLASS_MEASUREMENT, + sensor::StateClass::STATE_CLASS_MEASUREMENT, + sensor::StateClass::STATE_CLASS_MEASUREMENT, + sensor::StateClass::STATE_CLASS_TOTAL, + sensor::StateClass::STATE_CLASS_TOTAL_INCREASING, + sensor::StateClass::STATE_CLASS_MEASUREMENT, + sensor::StateClass::STATE_CLASS_MEASUREMENT, + sensor::StateClass::STATE_CLASS_MEASUREMENT, +}; +const uint8_t Sensor::SCALE_TO_DIGITS[REG_DEF::SCALE::SCALE_COUNT] = { + 0, // S_1, + 1, // S_0_1, + 2, // S_0_01, + 3, // S_0_001, + 2, // S_0_25, +}; + void Sensor::dynamic_register_() { App.register_sensor(this); if (api::global_api_server) @@ -15,57 +38,54 @@ void Sensor::dynamic_register_() { void Sensor::link_disconnected_() { this->publish_state(NAN); } void Sensor::init_reg_def_() { - switch (this->reg_def_->cls) { - case REG_DEF::CLASS::NUMERIC: - this->numeric_to_float_ = this->reg_def_->numeric_to_float; - this->parse_hex_ = parse_hex_numeric_; - break; - default: - // defaults if nothing better - this->parse_hex_ = parse_hex_default_; - break; - } + auto reg_def = this->reg_def_; + // Whatever the CLASS, sensor will just extract any meaningful numeric value + // from the HEX payload eventually scaling by hex_scale + this->parse_hex_ = DATA_TYPE_TO_PARSE_HEX_FUNC_[reg_def->data_type]; + this->set_unit_of_measurement(REG_DEF::UNITS[reg_def->unit]); + this->set_device_class(UNIT_TO_DEVICE_CLASS[reg_def->unit]); + this->set_state_class(UNIT_TO_STATE_CLASS[reg_def->unit]); + this->set_accuracy_decimals(SCALE_TO_DIGITS[reg_def->scale]); + this->set_hex_scale(REG_DEF::SCALE_TO_SCALE[reg_def->scale]); + this->set_text_scale(REG_DEF::SCALE_TO_SCALE[reg_def_->text_scale]); } -void Sensor::parse_hex_default_(HexRegister *hexregister, const RxHexFrame *hexframe) { +void Sensor::parse_hex_default_(HexRegister *hex_register, const RxHexFrame *hex_frame) { + Sensor *sensor = static_cast(hex_register); float value; - switch (hexframe->data_size()) { + switch (hex_frame->data_size()) { case 1: - value = hexframe->data_u16(); + value = hex_frame->data_u8() * sensor->hex_scale_; break; case 2: // it might be signed though - value = hexframe->data_u16(); + value = hex_frame->data_u16() * sensor->hex_scale_; break; case 4: - value = hexframe->data_u16(); + value = hex_frame->data_u32() * sensor->hex_scale_; break; default: - return; + value = NAN; } - Sensor *sensor = static_cast(hexregister); if (sensor->raw_state != value) { sensor->publish_state(value); } } -void Sensor::parse_hex_numeric_(HexRegister *hexregister, const RxHexFrame *hexframe) { +template void Sensor::parse_hex_t_(HexRegister *hex_register, const RxHexFrame *hex_frame) { static_assert(RxHexFrame::ALLOCATED_DATA_SIZE >= 4, "HexFrame storage might lead to access overflow"); - Sensor *sensor = static_cast(hexregister); - float value = sensor->numeric_to_float_(hexframe->data_begin()); + Sensor *sensor = static_cast(hex_register); + float value = hex_frame->data_t() * sensor->hex_scale_; if (sensor->raw_state != value) { sensor->publish_state(value); } } -void Sensor::init_text_def_(const TEXT_DEF *text_def) { - this->set_unit_of_measurement(REG_DEF::UNITS[text_def->unit]); - this->set_accuracy_decimals(text_def->digits); - this->set_device_class(UNIT_TO_DEVICE_CLASS[text_def->unit]); - this->set_state_class(UNIT_TO_STATE_CLASS[text_def->unit]); - this->set_text_scale(REG_DEF::DIGITS_TO_SCALE[text_def->digits]); - this->parse_text_ = parse_text_default_; -} +const Sensor::parse_hex_func_t Sensor::DATA_TYPE_TO_PARSE_HEX_FUNC_[REG_DEF::DATA_TYPE::_COUNT] = { + Sensor::parse_hex_default_, Sensor::parse_hex_t_, Sensor::parse_hex_t_, + Sensor::parse_hex_t_, Sensor::parse_hex_t_, Sensor::parse_hex_t_, + Sensor::parse_hex_t_, +}; void Sensor::parse_text_default_(HexRegister *hex_register, const char *text_value) { Sensor *sensor = static_cast(hex_register); diff --git a/esphome/components/m3_vedirect/sensor/sensor.h b/esphome/components/m3_vedirect/sensor/sensor.h index 9b0690057bf6..34f820aa6e12 100644 --- a/esphome/components/m3_vedirect/sensor/sensor.h +++ b/esphome/components/m3_vedirect/sensor/sensor.h @@ -8,23 +8,29 @@ namespace m3_vedirect { class Sensor final : public Entity, public esphome::sensor::Sensor { public: - Sensor(Manager *Manager) {} + // configuration symbols for numeric sensors + static const char *UNIT_TO_DEVICE_CLASS[REG_DEF::UNIT::UNIT_COUNT]; + static const sensor::StateClass UNIT_TO_STATE_CLASS[REG_DEF::UNIT::UNIT_COUNT]; + static const uint8_t SCALE_TO_DIGITS[REG_DEF::SCALE::SCALE_COUNT]; + + Sensor(Manager *Manager) : Entity(parse_hex_default_, parse_text_default_) {} + void set_hex_scale(float scale) { this->hex_scale_ = scale; } void set_text_scale(float scale) { this->text_scale_ = scale; } protected: friend class Manager; + float hex_scale_{1.}; float text_scale_{1.}; void dynamic_register_() override; void link_disconnected_() override; - REG_DEF::numeric_to_float_func_t numeric_to_float_; - void init_reg_def_() override; - static void parse_hex_default_(HexRegister *hex_register, const RxHexFrame *hex_frame); - static void parse_hex_numeric_(HexRegister *hex_register, const RxHexFrame *hex_frame); - void init_text_def_(const TEXT_DEF *text_def) override; + static const parse_hex_func_t DATA_TYPE_TO_PARSE_HEX_FUNC_[]; + static void parse_hex_default_(HexRegister *hexregister, const RxHexFrame *hexframe); + template static void parse_hex_t_(HexRegister *hex_register, const RxHexFrame *hex_frame); + static void parse_text_default_(HexRegister *hex_register, const char *text_value); }; diff --git a/esphome/components/m3_vedirect/switch/__init__.py b/esphome/components/m3_vedirect/switch/__init__.py index 4c8e5d83d908..14556ee047d1 100644 --- a/esphome/components/m3_vedirect/switch/__init__.py +++ b/esphome/components/m3_vedirect/switch/__init__.py @@ -1,29 +1,23 @@ -import esphome.codegen as cg from esphome.components import switch 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, ) -CONF_MASK = "mask" VEDirectSwitch = m3_vedirect_ns.class_("Switch", switch.Switch) -VEDIRECT_SWITCH_SCHEMA = ( - switch.switch_schema( - VEDirectSwitch, - default_restore_mode="DISABLED", - ) - .extend(VEDIRECT_ENTITY_SCHEMA) - .extend( - { - cv.Optional(CONF_MASK): cv.uint32_t, - } - ) +VEDIRECT_SWITCH_SCHEMA = switch.switch_schema( + VEDirectSwitch, default_restore_mode="DISABLED" +).extend( + vedirect_entity_schema((CLASS.BOOLEAN, CLASS.BITMASK, CLASS.ENUM), False), + VEDIRECT_BINARY_ENTITY_BASE_SCHEMA, ) @@ -36,8 +30,6 @@ async def new_vedirect_switch(config, manager): var = await new_vedirect_entity(config, manager) - if CONF_MASK in config: - cg.add(var.set_mask(config[CONF_MASK])) await switch.register_switch(var, config) return var diff --git a/esphome/components/m3_vedirect/switch/switch.cpp b/esphome/components/m3_vedirect/switch/switch.cpp index 0632a39e6bd4..e2f925013c83 100644 --- a/esphome/components/m3_vedirect/switch/switch.cpp +++ b/esphome/components/m3_vedirect/switch/switch.cpp @@ -17,17 +17,22 @@ void Switch::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_enum_; break; } } +void Switch::parse_hex_default_(HexRegister *hex_register, const RxHexFrame *hex_frame) { + static_assert(RxHexFrame::ALLOCATED_DATA_SIZE >= 1, "HexFrame storage might lead to access overflow"); + static_cast(hex_register)->publish_state(hex_frame->data_u8()); +} + void Switch::parse_hex_bitmask_(HexRegister *hex_register, const RxHexFrame *hex_frame) { // BITMASK registers have storage up to 4 bytes static_assert(RxHexFrame::ALLOCATED_DATA_SIZE >= 4, "HexFrame storage might lead to access overflow"); @@ -40,6 +45,26 @@ void Switch::parse_hex_enum_(HexRegister *hex_register, const RxHexFrame *hex_fr static_cast(hex_register)->parse_enum_(hex_frame->data_u8()); } +void Switch::parse_text_default_(HexRegister *hex_register, const char *text_value) { + static_cast(hex_register)->publish_state(!strcasecmp(text_value, "ON")); +} + +void Switch::parse_text_bitmask_(HexRegister *hex_register, const char *text_value) { + char *endptr; + BITMASK_DEF::bitmask_t bitmask_value = strtoumax(text_value, &endptr, 0); + if (*endptr == 0) { + static_cast(hex_register)->parse_bitmask_(bitmask_value); + } +} + +void Switch::parse_text_enum_(HexRegister *hex_register, const char *text_value) { + char *endptr; + ENUM_DEF::enum_t enum_value = strtoumax(text_value, &endptr, 0); + if (*endptr == 0) { + static_cast(hex_register)->parse_enum_(enum_value); + } +} + void Switch::parse_bitmask_(BITMASK_DEF::bitmask_t bitmask_value) { if (this->raw_value_ != bitmask_value) { this->raw_value_ = bitmask_value; @@ -57,17 +82,20 @@ void Switch::write_state(bool state) { // This code should work for both ENUM-like and BITMASK-like registers // For the latter, actual bits are preserved so that we can toggle individual bits // inside the register. The 'mask' too might be used to control multiple bits at once. - if (this->reg_def_) { - uint32_t hexvalue; - switch (this->reg_def_->cls) { - case REG_DEF::CLASS::BITMASK: - hexvalue = state ? this->raw_value_ | this->mask_ : this->raw_value_ & ~this->mask_; - break; - default: - hexvalue = state ? this->mask_ : 0; // what's a reasonable negation of mask_ ? - } - this->manager->send_register_set(this->reg_def_->register_id, &hexvalue, this->reg_def_->data_type); + uint32_t hexvalue; + switch (this->reg_def_->cls) { + case REG_DEF::CLASS::BITMASK: + hexvalue = state ? this->raw_value_ | this->mask_ : this->raw_value_ & ~this->mask_; + break; + case REG_DEF::CLASS::ENUM: + hexvalue = state ? this->mask_ : 0; // what's a reasonable negation of mask_ ? + break; + default: + // consider BOOLEAN + this->manager->send_register_set(this->reg_def_->register_id, (uint8_t) (state ? 1 : 0)); + return; } + this->manager->send_register_set(this->reg_def_->register_id, &hexvalue, this->reg_def_->data_type); } } // namespace m3_vedirect } // namespace esphome diff --git a/esphome/components/m3_vedirect/switch/switch.h b/esphome/components/m3_vedirect/switch/switch.h index 5d5c28046ed6..bd11477f9058 100644 --- a/esphome/components/m3_vedirect/switch/switch.h +++ b/esphome/components/m3_vedirect/switch/switch.h @@ -8,7 +8,9 @@ namespace m3_vedirect { class Switch final : public ConfigEntity, public esphome::switch_::Switch { public: - Switch(Manager *Manager) : ConfigEntity(manager) {} + // TODO: setup a default text parser (even tho 'TEXT' entities are usually readonly + // we might have a mapping to an R/W hex register - same as Select) + Switch(Manager *manager) : ConfigEntity(manager, parse_hex_default_, parse_text_default_) {} void set_mask(uint32_t mask) { this->mask_ = mask; } @@ -21,9 +23,14 @@ class Switch final : public ConfigEntity, public esphome::switch_::Switch { void dynamic_register_() override; void init_reg_def_() override; + static void parse_hex_default_(HexRegister *hex_register, const RxHexFrame *hex_frame); static void parse_hex_bitmask_(HexRegister *hex_register, const RxHexFrame *hex_frame); static void parse_hex_enum_(HexRegister *hex_register, const RxHexFrame *hex_frame); + 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; diff --git a/esphome/components/m3_vedirect/text_sensor/__init__.py b/esphome/components/m3_vedirect/text_sensor/__init__.py index 65c973bfc5ff..72c730be8d5c 100644 --- a/esphome/components/m3_vedirect/text_sensor/__init__.py +++ b/esphome/components/m3_vedirect/text_sensor/__init__.py @@ -2,10 +2,11 @@ import esphome.config_validation as cv from .. import ( + CLASS, CONF_VEDIRECT_ENTITIES, - VEDIRECT_ENTITY_SCHEMA, m3_vedirect_ns, new_vedirect_entity, + vedirect_entity_schema, vedirect_platform_schema, vedirect_platform_to_code, ) @@ -18,7 +19,7 @@ # m3_vedirect::TextSensor mapped to HEX/TEXT data VEDirectTextSensor = m3_vedirect_ns.class_("TextSensor", text_sensor.TextSensor) VEDIRECT_TEXT_SENSOR_SCHEMA = text_sensor.text_sensor_schema(VEDirectTextSensor).extend( - VEDIRECT_ENTITY_SCHEMA + vedirect_entity_schema((CLASS.BITMASK, CLASS.ENUM, CLASS.STRING), True) ) PLATFORM_ENTITIES = { diff --git a/esphome/components/m3_vedirect/text_sensor/text_sensor.cpp b/esphome/components/m3_vedirect/text_sensor/text_sensor.cpp index 48444db0a47c..b8809a1c1089 100644 --- a/esphome/components/m3_vedirect/text_sensor/text_sensor.cpp +++ b/esphome/components/m3_vedirect/text_sensor/text_sensor.cpp @@ -33,55 +33,34 @@ void TextSensor::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 TextSensor::parse_hex_default_(HexRegister *hexregister, const RxHexFrame *hexframe) { +void TextSensor::parse_hex_default_(HexRegister *hex_register, const RxHexFrame *hexframe) { std::string hex_value; if (hexframe->data_to_hex(hex_value)) { - static_cast(hexregister)->parse_string_(hex_value.c_str()); + static_cast(hex_register)->parse_string_(hex_value.c_str()); } } -void TextSensor::parse_hex_bitmask_(HexRegister *hexregister, const RxHexFrame *hexframe) { +void TextSensor::parse_hex_bitmask_(HexRegister *hex_register, const RxHexFrame *hexframe) { // BITMASK registers have storage up to 4 bytes static_assert(RxHexFrame::ALLOCATED_DATA_SIZE >= 4, "HexFrame storage might lead to access overflow"); - static_cast(hexregister) - ->parse_bitmask_(HEXFRAME::GET_DATA_AS_INT[hexregister->get_reg_def()->data_type](hexframe->record())); + static_cast(hex_register) + ->parse_bitmask_(HEXFRAME::GET_DATA_AS_INT[hex_register->get_reg_def()->data_type](hexframe->record())); } -void TextSensor::parse_hex_enum_(HexRegister *hexregister, const RxHexFrame *hexframe) { +void TextSensor::parse_hex_enum_(HexRegister *hex_register, const RxHexFrame *hexframe) { static_assert(RxHexFrame::ALLOCATED_DATA_SIZE >= 1, "HexFrame storage might lead to access overflow"); - static_cast(hexregister)->parse_enum_(hexframe->data_u8()); -} - -void TextSensor::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: - if ((this->reg_def_->cls == REG_DEF::CLASS::BITMASK) && (this->reg_def_->enum_def)) - this->parse_text_ = parse_text_bitmask_; - else - this->parse_text_ = parse_text_default_; - break; - case REG_DEF::CLASS::ENUM: - if ((this->reg_def_->cls == REG_DEF::CLASS::ENUM) && (this->reg_def_->enum_def)) - this->parse_text_ = parse_text_enum_; - else - this->parse_text_ = parse_text_default_; - break; - default: - this->parse_text_ = parse_text_default_; - break; - } + static_cast(hex_register)->parse_enum_(hexframe->data_u8()); } void TextSensor::parse_text_default_(HexRegister *hex_register, const char *text_value) { diff --git a/esphome/components/m3_vedirect/text_sensor/text_sensor.h b/esphome/components/m3_vedirect/text_sensor/text_sensor.h index eaf7d691b799..86c7c37b6a69 100644 --- a/esphome/components/m3_vedirect/text_sensor/text_sensor.h +++ b/esphome/components/m3_vedirect/text_sensor/text_sensor.h @@ -8,7 +8,7 @@ namespace m3_vedirect { class TextSensor final : public Entity, esphome::text_sensor::TextSensor { public: - TextSensor(Manager *Manager) {} + TextSensor(Manager *Manager) : Entity(parse_hex_default_, parse_text_default_) {} protected: friend class Manager; @@ -22,7 +22,6 @@ class TextSensor final : public Entity, esphome::text_sensor::TextSensor { 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); diff --git a/esphome/components/m3_vedirect/ve_hexframe.h b/esphome/components/m3_vedirect/ve_hexframe.h index 45c0cfb0ca2e..eaad105eedda 100644 --- a/esphome/components/m3_vedirect/ve_hexframe.h +++ b/esphome/components/m3_vedirect/ve_hexframe.h @@ -81,7 +81,7 @@ struct HexFrame { int16_t data_i16() const { return *(int16_t *) this->data_begin(); } uint32_t data_u32() const { return *(uint32_t *) this->data_begin(); } // unchecked (buffer overflow) cast to data type: be careful - template T data() { return *(T *) this->data_begin(); } + template T data_t() const { return *(T *) this->data_begin(); } /// @brief Safely extracts the 'raw' payload (i.e. the data past the register id and flags) bool data_to_hex(std::string &hexdata) const; diff --git a/esphome/components/m3_vedirect/ve_reg.h b/esphome/components/m3_vedirect/ve_reg.h index a6915477a254..d6ea462f9930 100644 --- a/esphome/components/m3_vedirect/ve_reg.h +++ b/esphome/components/m3_vedirect/ve_reg.h @@ -27,12 +27,12 @@ struct HEXFRAME { enum DATA_TYPE : uint8_t { STRING = 0, // or unknown - U8 = 1, - U16 = 2, - U32 = 3, - I8 = 4, - I16 = 5, - I32 = 6, + UN8 = 1, + UN16 = 2, + UN32 = 3, + SN8 = 4, + SN16 = 5, + SN32 = 6, _COUNT = 7, }; static const uint8_t DATA_TYPE_TO_SIZE[]; @@ -66,11 +66,11 @@ struct HEXFRAME { }; #pragma pack(pop) -template<> constexpr HEXFRAME::DATA_TYPE HEXFRAME::DATA_TYPE_OF() { return DATA_TYPE::U8; } -template<> constexpr HEXFRAME::DATA_TYPE HEXFRAME::DATA_TYPE_OF() { return DATA_TYPE::U16; } -template<> constexpr HEXFRAME::DATA_TYPE HEXFRAME::DATA_TYPE_OF() { return DATA_TYPE::U32; } -template<> constexpr HEXFRAME::DATA_TYPE HEXFRAME::DATA_TYPE_OF() { return DATA_TYPE::I8; } -template<> constexpr HEXFRAME::DATA_TYPE HEXFRAME::DATA_TYPE_OF() { return DATA_TYPE::I16; } -template<> constexpr HEXFRAME::DATA_TYPE HEXFRAME::DATA_TYPE_OF() { return DATA_TYPE::I32; } +template<> constexpr HEXFRAME::DATA_TYPE HEXFRAME::DATA_TYPE_OF() { return DATA_TYPE::UN8; } +template<> constexpr HEXFRAME::DATA_TYPE HEXFRAME::DATA_TYPE_OF() { return DATA_TYPE::UN16; } +template<> constexpr HEXFRAME::DATA_TYPE HEXFRAME::DATA_TYPE_OF() { return DATA_TYPE::UN32; } +template<> constexpr HEXFRAME::DATA_TYPE HEXFRAME::DATA_TYPE_OF() { return DATA_TYPE::SN8; } +template<> constexpr HEXFRAME::DATA_TYPE HEXFRAME::DATA_TYPE_OF() { return DATA_TYPE::SN16; } +template<> constexpr HEXFRAME::DATA_TYPE HEXFRAME::DATA_TYPE_OF() { return DATA_TYPE::SN32; } } // namespace m3_ve_reg \ No newline at end of file diff --git a/esphome/components/m3_vedirect/ve_reg_defs.cpp b/esphome/components/m3_vedirect/ve_reg_defs.cpp index b5bca1ee8ce8..53c771902979 100644 --- a/esphome/components/m3_vedirect/ve_reg_defs.cpp +++ b/esphome/components/m3_vedirect/ve_reg_defs.cpp @@ -34,37 +34,40 @@ ENUM_DEF::LOOKUP_RESULT ENUM_DEF::get_lookup(enum_t value) { return result; } -const char *REG_DEF::UNITS[] = { - "", "A", "V", "VA", "W", "Ah", "kWh", "%", "min", "°C", +const char *REG_DEF::UNITS[UNIT::UNIT_COUNT] = { + nullptr, "A", "V", "VA", "W", "Ah", "kWh", "%", "min", "°C", +}; +const float REG_DEF::SCALE_TO_SCALE[SCALE::SCALE_COUNT] = { + 1.f, // S_1, + .1f, // S_0_1, + .01f, // S_0_01, + .001f, // S_0_001, + .25f, // S_0_25, }; -const float REG_DEF::DIGITS_TO_SCALE[] = {1.f, .1f, .01f, .001f}; // define the enum helpers structs for ENUM registers #define _ENUMS_LOOKUP_ITEM(enum, value) \ { value, #enum } -#define DEFINE_ENUMS_BITMASK(register_id, label, access, ...) \ - BITMASK_DEF VE_REG_##label##_BITMASK_DEF = {{BITMASK_##label(_ENUMS_LOOKUP_ITEM)}}; -#define DEFINE_ENUMS_BITMASK_S(...) -#define DEFINE_ENUMS_ENUM(register_id, label, access) \ - ENUM_DEF VE_REG_##label##_ENUM_DEF = {{ENUM_##label(_ENUMS_LOOKUP_ITEM)}}; -#define DEFINE_ENUMS_NUMERIC(...) +#define _DEFINE_ENUMS(cls, register_id, label, ...) \ + cls##_DEF VE_REG_##label##_##cls##_DEF = {{cls##_##label(_ENUMS_LOOKUP_ITEM)}}; +#define DEFINE_ENUMS(cls, register_id, label, ...) IF(_DEF_ENUM_##cls)(_DEFINE_ENUMS(cls, register_id, label, ...)) + REGISTERS_COMMON(DEFINE_ENUMS) // define the registers definitions (will be stored in REG_DEF::DEFS) -#define DEFINE_DEFS_BITMASK(register_id, label, access, type) \ +#define DEFINE_REG_DEF_BOOLEAN(register_id, label, access) {register_id, #label, CLASS::BOOLEAN, REG_DEF::access}, +#define DEFINE_REG_DEF_BITMASK(register_id, label, access, type) \ {register_id, #label, REG_DEF::access, HEXFRAME::DATA_TYPE_OF(), &VE_REG_##label##_BITMASK_DEF}, -#define DEFINE_DEFS_BITMASK_S(register_id, label, access, type, bitmask_label) \ +#define DEFINE_REG_DEF_BITMASK_S(register_id, label, access, type, bitmask_label) \ {register_id, #label, REG_DEF::access, HEXFRAME::DATA_TYPE_OF(), &VE_REG_##bitmask_label##_BITMASK_DEF}, -#define DEFINE_DEFS_ENUM(register_id, label, access) {register_id, #label, REG_DEF::access, &VE_REG_##label##_ENUM_DEF}, -#define DEFINE_DEFS_NUMERIC(register_id, label, access, type, digits, unit) \ - {register_id, \ - #label, \ - REG_DEF::access, \ - HEXFRAME::DATA_TYPE_OF(), \ - REG_DEF::digits, \ - REG_DEF::unit, \ - REG_DEF::numeric_to_float_t}, -const REG_DEF REG_DEF::DEFS[REG_DEF::TYPE::_COUNT] = {REGISTERS_COMMON(DEFINE_DEFS)}; +#define DEFINE_REG_DEF_ENUM(register_id, label, access) \ + {register_id, #label, REG_DEF::access, &VE_REG_##label##_ENUM_DEF}, +#define DEFINE_REG_DEF_NUMERIC(register_id, label, access, type, unit, scale, text_scale) \ + {register_id, #label, REG_DEF::access, HEXFRAME::DATA_TYPE_OF(), \ + REG_DEF::unit, REG_DEF::scale, REG_DEF::text_scale}, +#define DEFINE_REG_DEF_STRING(register_id, label, access) {register_id, #label, CLASS::STRING, REG_DEF::access}, +#define DEFINE_REG_DEF(cls, ...) DEFINE_REG_DEF_##cls(__VA_ARGS__) +const REG_DEF REG_DEF::DEFS[REG_DEF::TYPE::TYPE_COUNT] = {REGISTERS_COMMON(DEFINE_REG_DEF)}; const REG_DEF *REG_DEF::find_register_id(register_id_t register_id) { const REG_DEF *reg_def_end = DEFS + ARRAY_COUNT(DEFS); @@ -72,46 +75,17 @@ const REG_DEF *REG_DEF::find_register_id(register_id_t register_id) { return (reg_def_it != reg_def_end) && (reg_def_it->register_id == register_id) ? reg_def_it : nullptr; } -#define DEFINE_TEXT_DEF_BITMASK(label, register_type, name) {label, name, REG_DEF::TYPE::register_type}, +#define DEFINE_TEXT_DEF_REG(label, register_type, name) {label, name, REG_DEF::TYPE::register_type}, +#define DEFINE_TEXT_DEF_BITMASK(label, register_type, name) DEFINE_TEXT_DEF_REG(label, register_type, name) #define DEFINE_TEXT_DEF_BOOLEAN(label, register_type, name) \ {label, name, REG_DEF::TYPE::register_type, REG_DEF::CLASS::BOOLEAN}, -#define DEFINE_TEXT_DEF_ENUM(label, register_type, name) {label, name, REG_DEF::TYPE::register_type}, -#define DEFINE_TEXT_DEF_NUMERIC(label, register_type, name, unit, digits) \ - {label, name, REG_DEF::TYPE::register_type, REG_DEF::UNIT::unit, REG_DEF::DIGITS::digits}, +#define DEFINE_TEXT_DEF_ENUM(label, register_type, name) DEFINE_TEXT_DEF_REG(label, register_type, name) +#define DEFINE_TEXT_DEF_NUMERIC(label, register_type, name) DEFINE_TEXT_DEF_REG(label, register_type, name) #define DEFINE_TEXT_DEF_STRING(label, register_type, name) \ {label, name, REG_DEF::TYPE::register_type, REG_DEF::CLASS::STRING}, +#define DEFINE_TEXT_DEF(cls, ...) DEFINE_TEXT_DEF_##cls(__VA_ARGS__) const TEXT_DEF TEXT_DEF::DEFS[] = {TEXTRECORDS(DEFINE_TEXT_DEF)}; -/* - DEF_TFSENSOR("AC_OUT_I", 0xFFFF, "AC output current", A, D_1), - DEF_TFSENSOR("AC_OUT_S", 0xFFFF, "AC output apparent power", VA, D_0), - DEF_TFSENSOR("AC_OUT_V", 0xFFFF, "AC output voltage", V, D_2), - DEF_TFTEXTSENSOR("AR", 0xFFFF, "Alarm reason"), - DEF_TFBINARYSENSOR("Alarm", 0xFFFF, "Alarm"), - DEF_TFTEXTSENSOR("CS", 0xFFFF, "State of operation"), - DEF_TFTEXTSENSOR("ERR", 0xFFFF, "Error code"), - DEF_TFTEXTSENSOR("FW", 0xFFFF, "Firmware version (FW)"), - DEF_TFTEXTSENSOR("FWE", 0xFFFF, "Firmware version (FWE)"), - DEF_TFSENSOR("H19", 0xFFFF, "Yield total", kWh, D_2), - DEF_TFSENSOR("H20", 0xFFFF, "Yield today", kWh, D_2), - DEF_TFSENSOR("H21", 0xFFFF, "Maximum power today", W, D_0), - DEF_TFSENSOR("H22", 0xFFFF, "Yield yesterday", kWh, D_2), - DEF_TFSENSOR("H23", 0xFFFF, "Maximum power yesterday", W, D_0), - DEF_TFSENSOR("I", 0xFFFF, "Battery current", A, D_3), - DEF_TFSENSOR("IL", 0xFFFF, "Load current", A, D_3), - DEF_TFBINARYSENSOR("LOAD", 0xFFFF, "Output state"), - DEF_TFTEXTSENSOR("MODE", 0xFFFF, "Device mode"), - DEF_TFTEXTSENSOR("MPPT", 0xFFFF, "Tracker operation mode"), - DEF_TFTEXTSENSOR("OR", 0xFFFF, "Off reason"), - DEF_TFTEXTSENSOR("PID", 0xFFFF, "Product Id"), - DEF_TFSENSOR("PPV", 0xFFFF, "PV power", W, D_0), - DEF_TFTEXTSENSOR("Relay", 0xFFFF, "Relay state"), - DEF_TFTEXTSENSOR("SER#", 0xFFFF, "Serial number"), - DEF_TFSENSOR("V", 0xFFFF, "Battery voltage", V, D_3), - DEF_TFSENSOR("VPV", 0xFFFF, "PV voltage", V, D_3), - DEF_TFTEXTSENSOR("WARN", 0xFFFF, "Warning reason"), -}; -*/ const TEXT_DEF *TEXT_DEF::find_label(const char *label) { const TEXT_DEF *it_end = DEFS + ARRAY_COUNT(DEFS); diff --git a/esphome/components/m3_vedirect/ve_reg_defs.h b/esphome/components/m3_vedirect/ve_reg_defs.h index 394074f4ffb0..903427766467 100644 --- a/esphome/components/m3_vedirect/ve_reg_defs.h +++ b/esphome/components/m3_vedirect/ve_reg_defs.h @@ -65,51 +65,34 @@ struct BITMASK_DEF : public ENUM_DEF { BITMASK_DEF(std::initializer_list initializer_list) : ENUM_DEF(initializer_list) {} }; -// declare the enum helpers structs for ENUM registers +// declare the enum helpers structs for BITMASK/ENUM registers +#define _DEF_ENUM_BOOLEAN N +#define _DEF_ENUM_BITMASK Y +#define _DEF_ENUM_BITMASK_S N +#define _DEF_ENUM_ENUM Y +#define _DEF_ENUM_NUMERIC N +#define _DEF_ENUM_STRING N #define _ENUMS_ITEM(enum, value) enum = value - -#define _DECLARE_ENUMS_BITMASK(register_id, label, ...) \ - struct VE_REG_##label##_BITMASK : public BITMASK_DEF { \ - public: \ - enum : enum_t { BITMASK_##label(_ENUMS_ITEM) }; \ - }; \ - extern BITMASK_DEF VE_REG_##label##_BITMASK_DEF; - -#define _DECLARE_ENUMS_BITMASK_S(...) -// This BITMASK is shared among different registers -// we could just setup some typedefs and & but useless atm - -#define _DECLARE_ENUMS_ENUM(register_id, label, ...) \ - struct VE_REG_##label##_ENUM : public ENUM_DEF { \ +#define _DECLARE_ENUMS(cls, register_id, label, ...) \ + struct VE_REG_##label##_##cls : public cls##_DEF { \ public: \ - enum : enum_t { ENUM_##label(_ENUMS_ITEM) }; \ + enum : enum_t { cls##_##label(_ENUMS_ITEM) }; \ }; \ - extern ENUM_DEF VE_REG_##label##_ENUM_DEF; + extern cls##_DEF VE_REG_##label##_##cls##_DEF; +#define DECLARE_ENUMS(cls, register_id, label, ...) IF(_DEF_ENUM_##cls)(_DECLARE_ENUMS(cls, register_id, label, ...)) -#define _DECLARE_ENUMS_NUMERIC(...) -REGISTERS_COMMON(_DECLARE_ENUMS) -#undef _DECLARE_ENUMS_BITMASK -#undef _DECLARE_ENUMS_BITMASK_S -#undef _DECLARE_ENUMS_ENUM -#undef _DECLARE_ENUMS_NUMERIC -#undef _ENUMS_ITEM +REGISTERS_COMMON(DECLARE_ENUMS) struct REG_DEF { -#define _DECLARE_REG_LABEL_BITMASK(register_id, label, ...) label, -#define _DECLARE_REG_LABEL_BITMASK_S(register_id, label, ...) label, -#define _DECLARE_REG_LABEL_ENUM(register_id, label, ...) label, -#define _DECLARE_REG_LABEL_NUMERIC(register_id, label, ...) label, - enum TYPE : uint16_t { REGISTERS_COMMON(_DECLARE_REG_LABEL) _COUNT }; -#undef _DECLARE_REG_LABEL_BITMASK -#undef _DECLARE_REG_LABEL_BITMASK_S -#undef _DECLARE_REG_LABEL_ENUM -#undef _DECLARE_REG_LABEL_NUMERIC - - /// @brief Together with SUBCLASS defines the data semantics of this entity +#define DECLARE_REG_LABEL(cls, register_id, label, ...) label, + enum TYPE : uint16_t { REGISTERS_COMMON(DECLARE_REG_LABEL) TYPE_COUNT }; +#undef DECLARE_REG_LABEL + + /// @brief Defines the data semantics of this register enum CLASS : uint8_t { UNKNOWN, BITMASK, // represents a set of bit flags - BOOLEAN, + BOOLEAN, // boolean state represented by 0 -> false, 1 -> true ENUM, // enumeration data NUMERIC, // numeric data (either signed or unsigned) STRING, @@ -134,37 +117,38 @@ struct REG_DEF { SOC_PERCENTAGE, minute, CELSIUS, + UNIT_COUNT, }; - static const char *UNITS[]; - - enum DIGITS : uint8_t { - D_0 = 0, - D_1 = 1, - D_2 = 2, - D_3 = 3, - }; - static const float DIGITS_TO_SCALE[4]; - typedef float (*numeric_to_float_func_t)(const uint8_t *rawdata); - template inline static float numeric_to_float_t(const uint8_t *rawdata) { - return *(T *) (rawdata) *DIGITS_TO_SCALE[digits]; + static const char *UNITS[UNIT::UNIT_COUNT]; + + enum SCALE : uint8_t { + S_1, + S_0_1, + S_0_01, + S_0_001, + S_0_25, + SCALE_COUNT, }; + static const float SCALE_TO_SCALE[SCALE::SCALE_COUNT]; const register_id_t register_id; const char *const label; - const CLASS cls : 3; - const ACCESS access : 1; - const DATA_TYPE data_type : 3; - + CLASS cls : 3; + ACCESS access : 1; + DATA_TYPE data_type : 3; // only relevant for BITMASK and NUMERIC (ENUM are UN8 though) + uint8_t _padding : 1; union { - ENUM_DEF *const enum_def; struct { - numeric_to_float_func_t const numeric_to_float; - DIGITS const digits : 2; - UNIT const unit : 4; + ENUM_DEF *enum_def; + }; + struct { + UNIT unit : 4; + SCALE scale : 4; + SCALE text_scale : 4; }; }; - static const REG_DEF DEFS[TYPE::_COUNT]; + static const REG_DEF DEFS[TYPE::TYPE_COUNT]; bool operator<(const register_id_t register_id) const { return this->register_id < register_id; } static const REG_DEF *find_register_id(register_id_t register_id); static const REG_DEF *find_type(TYPE type) { return (type < ARRAY_COUNT(DEFS)) ? DEFS + type : nullptr; } @@ -174,7 +158,16 @@ struct REG_DEF { label(nullptr), cls(CLASS::UNKNOWN), access(ACCESS::READ_ONLY), - data_type(DATA_TYPE::STRING) {} + data_type(DATA_TYPE::STRING), + enum_def(nullptr) {} + /// @brief Constructor for STRING or BOOLEAN register definitions + REG_DEF(register_id_t register_id, const char *label, CLASS cls, ACCESS access) + : register_id(register_id), + label(label), + cls(cls), + access(access), + data_type(cls == CLASS::BOOLEAN ? DATA_TYPE::UN8 : DATA_TYPE::STRING), + enum_def(nullptr) {} /// @brief Constructor for BITMASK registers definitions REG_DEF(register_id_t register_id, const char *label, ACCESS access, DATA_TYPE data_type, ENUM_DEF *enum_def) : register_id(register_id), @@ -189,19 +182,19 @@ struct REG_DEF { label(label), cls(CLASS::ENUM), access(access), - data_type(DATA_TYPE::U8), + data_type(DATA_TYPE::UN8), enum_def(enum_def) {} /// @brief Constructor for NUMERIC registers definitions - REG_DEF(register_id_t register_id, const char *label, ACCESS access, DATA_TYPE data_type, DIGITS digits, UNIT unit, - numeric_to_float_func_t numeric_to_float) + REG_DEF(register_id_t register_id, const char *label, ACCESS access, DATA_TYPE data_type, UNIT unit, SCALE scale, + SCALE text_scale) : register_id(register_id), label(label), cls(CLASS::NUMERIC), access(access), data_type(data_type), - numeric_to_float(numeric_to_float), - digits(digits), - unit(unit) {} + unit(unit), + scale(scale), + text_scale(text_scale) {} /// @brief get our symbolic name (TYPE) for this REG_DEF. Only /// valid when the structure is peeked from our static DEFS @@ -216,51 +209,16 @@ struct TEXT_DEF { const char *label; const char *description; const REG_DEF::TYPE register_type; - const REG_DEF::CLASS cls : 3; - // Optional entity 'class' definitions - union { - // Sensor entity definitions - struct { - const REG_DEF::UNIT unit : 4; - const REG_DEF::DIGITS digits : 2; - }; - }; TEXT_DEF(const char *label, const char *description, REG_DEF::TYPE register_type, REG_DEF::CLASS cls) - : label(label), - description(description), - register_type(register_type), - cls(cls), - unit(REG_DEF::UNIT::NONE), - digits(REG_DEF::DIGITS::D_0) {} + : label(label), description(description), register_type(register_type) {} - // Constructor used when we register_type is a valid mapping to a REG_DEF + // Constructor used when register_type is a valid mapping to a REG_DEF TEXT_DEF(const char *label, const char *description, REG_DEF::TYPE register_type) - : label(label), - description(description), - register_type(register_type), - cls(REG_DEF::DEFS[register_type].cls), - unit(REG_DEF::UNIT::NONE), - digits(REG_DEF::DIGITS::D_0) {} - - // Constructor for numeric records - TEXT_DEF(const char *label, const char *description, REG_DEF::TYPE register_type, REG_DEF::UNIT unit, - REG_DEF::DIGITS digits) - : label(label), - description(description), - register_type(register_type), - cls(REG_DEF::CLASS::NUMERIC), - unit(unit), - digits(digits) {} + : label(label), description(description), register_type(register_type) {} // Constructor for default unknown/untyped field - TEXT_DEF() - : label(nullptr), - description(nullptr), - register_type(REG_DEF::TYPE::_COUNT), - cls(REG_DEF::CLASS::UNKNOWN), - unit(REG_DEF::UNIT::NONE), - digits(REG_DEF::DIGITS::D_0) {} + TEXT_DEF() : label(nullptr), description(nullptr), register_type(REG_DEF::TYPE::TYPE_COUNT) {} bool operator<(const char *label) const { return strcmp(this->label, label) < 0; } static const TEXT_DEF DEFS[]; diff --git a/esphome/components/m3_vedirect/ve_reg_enums.h b/esphome/components/m3_vedirect/ve_reg_enums.h index 5915aa4ca664..d022815c6d1f 100644 --- a/esphome/components/m3_vedirect/ve_reg_enums.h +++ b/esphome/components/m3_vedirect/ve_reg_enums.h @@ -43,6 +43,9 @@ ENUM(STARTING_UP, 0xF5), ENUM(REPEATED_ABSORPTION, 0xF6), ENUM(AUTO_EQUALIZE, 0xF7), \ ENUM(BATTERY_SAFE, 0xF8), ENUM(EXTERNAL_CONTROL, 0xFC), ENUM(UNKNOWN, 0xFF), +#define ENUM_MPPT_TRACKER_MODE(ENUM) \ + ENUM(OFF, 0x00), ENUM(V_I_LIMITED, 0x01), ENUM(MPPT, 0x02), + #define BITMASK_WARNING_REASON(ENUM) \ ENUM(LOW_BATTERY_VOLTAGE, 0x00), ENUM(HIGH_BATTERY_VOLTAGE, 0x01), ENUM(LOW_SOC, 0x02), \ ENUM(LOW_STARTER_VOLTAGE, 0x03), ENUM(HIGH_STARTER_VOLTAGE, 0x04), ENUM(LOW_TEMPERATURE, 0x05), \ diff --git a/esphome/components/m3_vedirect/ve_reg_macro.h b/esphome/components/m3_vedirect/ve_reg_macro.h new file mode 100644 index 000000000000..0991e2bff57b --- /dev/null +++ b/esphome/components/m3_vedirect/ve_reg_macro.h @@ -0,0 +1,137 @@ +#define PASTE_(x, y) x##y +#define PASTE(x, y) PASTE_(x, y) +#define PASTE3_(x, y, z) x##y##z +#define PASTE3(x, y, z) PASTE3_(x, y, z) +#define Y(...) __VA_ARGS__ +#define N(...) +#define IF(x) x // alternate method similar to IFNOT() + +#define NOT_N Y +#define NOT_Y N +#define IF_NOT(x) PASTE(NOT_, x) +#define NOT(x) PASTE(NOT_, x) + +#define N_OR_N N +#define N_OR_Y Y +#define Y_OR_N Y +#define Y_OR_Y Y +#define OR(x, y) PASTE3(x, _OR_, y) + +#define N_AND_N N +#define N_AND_Y N +#define Y_AND_N N +#define Y_AND_Y Y +#define AND(x, y) PASTE3(x, _AND_, y) + +#define N_XOR_N N +#define N_XOR_Y Y +#define Y_XOR_N Y +#define Y_XOR_Y N +#define XOR(x, y) PASTE3(x, _XOR_, y) + +#define N_NOR_N Y +#define N_NOR_Y N +#define Y_NOR_N N +#define Y_NOR_Y N +#define NOR(x, y) PASTE3(x, _NOR_, y) + +#define N_NAND_N Y +#define N_NAND_Y Y +#define Y_NAND_N Y +#define Y_NAND_Y N +#define NAND(x, y) PASTE3(x, _NAND_, y) + +#define N_XNOR_N Y +#define N_XNOR_Y N +#define Y_XNOR_N N +#define Y_XNOR_Y Y +#define XNOR(x, y) PASTE3(x, _XNOR_, y) + +#define IF2(x, y, z) PASTE3(x, y, z) + +#if 1 +#define FLAVOR_MPPT_BS +#define FLAVOR_MPPT_RS +#define FLAVOR_INV_PHNX +#define FLAVOR_BMV +#define FLAVOR_BMV71 +#endif + +// Inverter flavors +#ifdef FLAVOR_INV_PHNX +#define FLAVOR_INV +#define DEF_INV_PHNX Y +#else +#define DEF_INV_PHNX N +#endif + +// Charger flavors +#ifdef FLAVOR_CHG_PHNX +#define FLAVOR_CHG +#define DEF_CHG_PHNX Y +#else +#define DEF_CHG_PHNX N +#endif + +// MPPT charger flavors +#ifdef FLAVOR_MPPT_BS // BlueSolar MPPT +#define FLAVOR_MPPT +#define DEF_MPPT_BS Y +#else +#define DEF_MPPT_BS N +#endif + +#ifdef FLAVOR_MPPT_RS +#define FLAVOR_MPPT +#define DEF_MPPT_RS Y +#else +#define DEF_MPPT_RS N +#endif + +// Battery Monitor flavors +#ifdef FLAVOR_BMV60 +#define FLAVOR_BMV +#define DEF_BMV60 Y +#else +#define DEF_BMV60 N +#endif + +#ifdef FLAVOR_BMV70 +#define FLAVOR_BMV +#define DEF_BMV70 Y +#else +#define DEF_BMV70 N +#endif + +#ifdef FLAVOR_BMV71 +#define FLAVOR_BMV +#define DEF_BMV71 Y +#else +#define DEF_BMV71 N +#endif + +// define some 'groups' +#ifdef FLAVOR_MPPT // any MPPT charger flavor +#define FLAVOR_CHG +#define DEF_MPPT Y +#else +#define DEF_MPPT N +#endif + +#ifdef FLAVOR_BMV // any BMV flavor +#define DEF_BMV Y +#else +#define DEF_BMV N +#endif + +#ifdef FLAVOR_CHG // any charger flavor +#define DEF_CHG Y +#else +#define DEF_CHG N +#endif + +#ifdef FLAVOR_INV // any inverter flavor +#define DEF_INV Y +#else +#define DEF_INV N +#endif \ No newline at end of file diff --git a/esphome/components/m3_vedirect/ve_reg_registers.h b/esphome/components/m3_vedirect/ve_reg_registers.h index 6a24fc48a1c6..1ea559e5a49b 100644 --- a/esphome/components/m3_vedirect/ve_reg_registers.h +++ b/esphome/components/m3_vedirect/ve_reg_registers.h @@ -1,16 +1,42 @@ #pragma once - +#include "ve_reg_macro.h" // clang-format off #define REGISTERS_COMMON(MACRO) \ - MACRO##_BITMASK(0x0090, BLE_MODE, READ_WRITE, uint8_t) \ - MACRO##_ENUM(0x0200, DEVICE_MODE, READ_WRITE) \ - MACRO##_ENUM(0x0201, DEVICE_STATE, READ_ONLY) \ - MACRO##_BITMASK_S(0x0205, DEVICE_OFF_REASON, READ_ONLY, uint8_t, DEVICE_OFF_REASON_2) \ - MACRO##_BITMASK(0x0207, DEVICE_OFF_REASON_2, READ_ONLY, uint32_t) \ - MACRO##_BITMASK(0x031C, WARNING_REASON, READ_ONLY, uint16_t) \ - MACRO##_BITMASK_S(0x031E, ALARM_REASON, READ_ONLY, uint16_t, WARNING_REASON) \ - MACRO##_NUMERIC(0x2201, AC_OUT_CURRENT, READ_ONLY, int16_t, D_1, A) \ - MACRO##_ENUM(0xEDDA, CHR_ERROR_CODE, READ_ONLY) + MACRO(BITMASK, 0x0090, BLE_MODE, READ_WRITE, uint8_t) \ + MACRO(STRING, 0x010A, SERIAL_NUMBER, READ_ONLY) \ + MACRO(STRING, 0x010B, MODEL_NAME, READ_ONLY) \ + MACRO(ENUM, 0x0200, DEVICE_MODE, READ_WRITE) \ + MACRO(ENUM, 0x0201, DEVICE_STATE, READ_ONLY) \ + MACRO(BITMASK_S, 0x0205, DEVICE_OFF_REASON, READ_ONLY, uint8_t, DEVICE_OFF_REASON_2) \ + MACRO(BITMASK, 0x0207, DEVICE_OFF_REASON_2, READ_ONLY, uint32_t) \ + MACRO(BITMASK, 0x031C, WARNING_REASON, READ_ONLY, uint16_t) \ + MACRO(BITMASK_S, 0x031E, ALARM_REASON, READ_ONLY, uint16_t, WARNING_REASON) \ + MACRO(BOOLEAN, 0x034E, RELAY_CONTROL, READ_WRITE) \ + IF(DEF_BMV)(MACRO(NUMERIC, 0x0FFE, TTG, READ_ONLY, uint16_t, minute, S_1, S_1)) \ + IF(DEF_BMV)(MACRO(NUMERIC, 0x0FFF, SOC, READ_ONLY, uint16_t, SOC_PERCENTAGE, S_0_01, S_0_1)) \ + IF(DEF_INV)(MACRO(NUMERIC, 0x2200, AC_OUT_VOLTAGE, READ_ONLY, int16_t, V, S_0_01, S_0_01)) \ + IF(DEF_INV)(MACRO(NUMERIC, 0x2201, AC_OUT_CURRENT, READ_ONLY, int16_t, A, S_0_1, S_0_1)) \ + IF(DEF_INV)(MACRO(NUMERIC, 0x2205, AC_OUT_APPARENT_POWER, READ_ONLY, int32_t, VA, S_1, S_1)) \ + MACRO(NUMERIC, 0xED8D, DC_CHANNEL1_VOLTAGE, READ_ONLY, int16_t, V, S_0_01, S_0_001) \ + IF(DEF_BMV)(MACRO(NUMERIC, 0xED8E, DC_CHANNEL1_POWER, READ_ONLY, int16_t, W, S_1, S_1)) \ + MACRO(NUMERIC, 0xED8F, DC_CHANNEL1_CURRENT, READ_ONLY, int16_t, A, S_0_1, S_0_001) \ + IF(DEF_MPPT)(MACRO(BOOLEAN, 0xEDA8, LOAD_OUTPUT_STATE, READ_ONLY)) \ + IF(DEF_MPPT)(MACRO(NUMERIC, 0xEDAD, LOAD_CURRENT, READ_ONLY, uint16_t, A, S_0_1, S_0_001)) \ + IF(DEF_MPPT)(MACRO(ENUM, 0xEDB3, MPPT_TRACKER_MODE, READ_ONLY)) \ + IF(DEF_MPPT)(MACRO(NUMERIC, 0xEDB8, PANEL_MAXIMUM_VOLTAGE, READ_ONLY, uint16_t, V, S_0_01, S_0_01)) \ + IF(DEF_MPPT)(MACRO(NUMERIC, 0xEDBB, PANEL_VOLTAGE, READ_ONLY, uint16_t, V, S_0_01, S_0_001)) \ + IF(DEF_MPPT)(MACRO(NUMERIC, 0xEDBC, PANEL_POWER, READ_ONLY, uint32_t, W, S_0_01, S_1)) \ + IF(DEF_MPPT)(MACRO(NUMERIC, 0xEDBD, PANEL_CURRENT, READ_ONLY, uint16_t, A, S_0_1, S_0_1)) \ + IF(DEF_MPPT)(MACRO(NUMERIC, 0xEDD0, MAXIMUM_POWER_YESTERDAY, READ_ONLY, uint16_t, W, S_1, S_1)) \ + IF(DEF_MPPT)(MACRO(NUMERIC, 0xEDD1, YIELD_YESTERDAY, READ_ONLY, uint16_t, kWh, S_0_01, S_0_01)) \ + IF(DEF_MPPT)(MACRO(NUMERIC, 0xEDD2, MAXIMUM_POWER_TODAY, READ_ONLY, uint16_t, W, S_1, S_1)) \ + IF(DEF_MPPT)(MACRO(NUMERIC, 0xEDD3, YIELD_TODAY, READ_ONLY, uint16_t, kWh, S_0_01, S_0_01)) \ + IF(DEF_CHG)(MACRO(ENUM, 0xEDDA, CHR_ERROR_CODE, READ_ONLY)) \ + IF(DEF_MPPT)(MACRO(NUMERIC, 0xEDDC, USER_YIELD, READ_ONLY, uint32_t, kWh, S_0_01, S_0_01)) \ + IF(DEF_MPPT)(MACRO(NUMERIC, 0xEDDD, SYSTEM_YIELD, READ_ONLY, uint32_t, kWh, S_0_01, S_0_01)) \ + IF(DEF_BMV)(MACRO(NUMERIC, 0xEDEC, BAT_TEMPERATURE, READ_ONLY, uint16_t, CELSIUS, S_0_01, S_1)) /*TODO:HEX register carries 0.01K*/ \ + IF(DEF_BMV71)(MACRO(NUMERIC, 0xEEB8, DC_MONITOR_MODE, READ_ONLY, int16_t, NONE, S_1, S_1)) /*might be R/W and it's a signed ENUM*/ \ + IF(DEF_BMV)(MACRO(BOOLEAN, 0xEEFC, ALARM_BUZZER, READ_WRITE)) // clang-format on \ No newline at end of file diff --git a/esphome/components/m3_vedirect/ve_reg_text.h b/esphome/components/m3_vedirect/ve_reg_text.h index 0708bad2b310..ac2a99b1dae4 100644 --- a/esphome/components/m3_vedirect/ve_reg_text.h +++ b/esphome/components/m3_vedirect/ve_reg_text.h @@ -1,41 +1,46 @@ #pragma once +#include "ve_reg_macro.h" // clang-format off -#define _REGISTER_TYPE_UNDEFINED _COUNT +/* + NUMERIC is a numeric TEXT record where the scale matches the one defined in REG_DEF + this is likely a temporary setting while we migrate the code +*/ +#define _REGISTER_TYPE_UNDEFINED TYPE_COUNT #define TEXTRECORDS(MACRO) \ - MACRO##_NUMERIC("AC_OUT_I", _REGISTER_TYPE_UNDEFINED, "AC output current", A, D_1) \ - MACRO##_NUMERIC("AC_OUT_S", _REGISTER_TYPE_UNDEFINED, "AC output apparent power", VA, D_0) \ - MACRO##_NUMERIC("AC_OUT_V", _REGISTER_TYPE_UNDEFINED, "AC output voltage", V, D_2) \ - MACRO##_BITMASK("AR", ALARM_REASON, "Alarm reason") \ - MACRO##_BOOLEAN("Alarm", _REGISTER_TYPE_UNDEFINED, "Alarm") \ - MACRO##_ENUM("CS", DEVICE_STATE, "State of operation") \ - MACRO##_ENUM("ERR", CHR_ERROR_CODE, "Error code") \ - MACRO##_STRING("FW", _REGISTER_TYPE_UNDEFINED, "Firmware version (FW)") \ - MACRO##_STRING("FWE", _REGISTER_TYPE_UNDEFINED, "Firmware version (FWE)") \ - MACRO##_NUMERIC("H19", _REGISTER_TYPE_UNDEFINED, "Yield total", kWh, D_2) \ - MACRO##_NUMERIC("H20", _REGISTER_TYPE_UNDEFINED, "Yield today", kWh, D_2) \ - MACRO##_NUMERIC("H21", _REGISTER_TYPE_UNDEFINED, "Maximum power today", W, D_0) \ - MACRO##_NUMERIC("H22", _REGISTER_TYPE_UNDEFINED, "Yield yesterday", kWh, D_2) \ - MACRO##_NUMERIC("H23", _REGISTER_TYPE_UNDEFINED, "Maximum power yesterday", W, D_0) \ - MACRO##_NUMERIC("HSDS", _REGISTER_TYPE_UNDEFINED, "Day sequence number", NONE, D_0) \ - MACRO##_NUMERIC("I", _REGISTER_TYPE_UNDEFINED, "Battery current", A, D_3) \ - MACRO##_NUMERIC("IL", _REGISTER_TYPE_UNDEFINED, "Load current", A, D_3) \ - MACRO##_BOOLEAN("LOAD", _REGISTER_TYPE_UNDEFINED, "Output state") \ - MACRO##_ENUM("MODE", DEVICE_MODE, "Device mode") \ - MACRO##_STRING("MON", _REGISTER_TYPE_UNDEFINED, "DC monitor mode") \ - MACRO##_STRING("MPPT", _REGISTER_TYPE_UNDEFINED, "Tracker operation mode") \ - MACRO##_BITMASK("OR", DEVICE_OFF_REASON_2, "Off reason") \ - MACRO##_STRING("PID", _REGISTER_TYPE_UNDEFINED, "Product Id") \ - MACRO##_NUMERIC("PPV", _REGISTER_TYPE_UNDEFINED, "PV power", W, D_0) \ - MACRO##_BOOLEAN("Relay", _REGISTER_TYPE_UNDEFINED, "Relay state") \ - MACRO##_STRING("SER#", _REGISTER_TYPE_UNDEFINED, "Serial number") \ - MACRO##_NUMERIC("SOC", _REGISTER_TYPE_UNDEFINED, "SOC", SOC_PERCENTAGE, D_1) \ - MACRO##_NUMERIC("T", _REGISTER_TYPE_UNDEFINED, "Battery temperature", CELSIUS, D_0) \ - MACRO##_NUMERIC("TTG", _REGISTER_TYPE_UNDEFINED, "Time To Go", minute, D_0) \ - MACRO##_NUMERIC("V", _REGISTER_TYPE_UNDEFINED, "Battery voltage", V, D_3) \ - MACRO##_NUMERIC("VPV", _REGISTER_TYPE_UNDEFINED, "PV voltage", V, D_3) \ - MACRO##_NUMERIC("W", _REGISTER_TYPE_UNDEFINED, "Battery power", W, D_0) \ - MACRO##_BITMASK("WARN", WARNING_REASON, "Warning reason") + IF(DEF_INV)(MACRO(NUMERIC, "AC_OUT_I", AC_OUT_CURRENT, "AC output current")) \ + IF(DEF_INV)(MACRO(NUMERIC, "AC_OUT_S", AC_OUT_APPARENT_POWER, "AC output apparent power")) \ + IF(DEF_INV)(MACRO(NUMERIC, "AC_OUT_V", AC_OUT_VOLTAGE, "AC output voltage")) \ + MACRO(BITMASK, "AR", ALARM_REASON, "Alarm reason") \ + IF(DEF_BMV)(MACRO(BOOLEAN, "Alarm", ALARM_BUZZER, "Alarm")) \ + MACRO(ENUM, "CS", DEVICE_STATE, "State of operation") \ + IF(DEF_CHG)(MACRO(ENUM, "ERR", CHR_ERROR_CODE, "Charger error")) \ + MACRO(STRING, "FW", _REGISTER_TYPE_UNDEFINED, "Firmware version (FW)") \ + MACRO(STRING, "FWE", _REGISTER_TYPE_UNDEFINED, "Firmware version (FWE)") \ + IF(DEF_MPPT)(MACRO(NUMERIC, "H19", USER_YIELD, "Yield total")) \ + IF(DEF_MPPT)(MACRO(NUMERIC, "H20", YIELD_TODAY, "Yield today")) \ + IF(DEF_MPPT)(MACRO(NUMERIC, "H21", MAXIMUM_POWER_TODAY, "Maximum power today")) \ + IF(DEF_MPPT)(MACRO(NUMERIC, "H22", YIELD_YESTERDAY, "Yield yesterday")) \ + IF(DEF_MPPT)(MACRO(NUMERIC, "H23", MAXIMUM_POWER_YESTERDAY, "Maximum power yesterday")) \ + IF(DEF_MPPT)(MACRO(STRING, "HSDS", _REGISTER_TYPE_UNDEFINED, "Day sequence number")) \ + MACRO(NUMERIC, "I", DC_CHANNEL1_CURRENT, "Battery current") \ + IF(DEF_MPPT)(MACRO(NUMERIC, "IL", LOAD_CURRENT, "Load current")) \ + IF(DEF_MPPT)(MACRO(BOOLEAN, "LOAD", LOAD_OUTPUT_STATE, "Output state")) \ + MACRO(ENUM, "MODE", DEVICE_MODE, "Device mode") \ + IF(DEF_BMV71)(MACRO(STRING, "MON", DC_MONITOR_MODE, "DC monitor mode")) \ + IF(DEF_MPPT)(MACRO(ENUM, "MPPT", MPPT_TRACKER_MODE, "Tracker operation mode")) \ + MACRO(BITMASK, "OR", DEVICE_OFF_REASON_2, "Off reason") \ + IF(DEF_BMV)(MACRO(NUMERIC, "P", DC_CHANNEL1_POWER, "Battery power")) \ + MACRO(STRING, "PID", _REGISTER_TYPE_UNDEFINED, "Product Id") \ + IF(DEF_MPPT)(MACRO(NUMERIC, "PPV", PANEL_POWER, "PV power")) \ + MACRO(BOOLEAN, "Relay", RELAY_CONTROL, "Relay state") \ + MACRO(STRING, "SER#", SERIAL_NUMBER, "Serial number") \ + IF(DEF_BMV)(MACRO(NUMERIC, "SOC", SOC, "SOC")) \ + IF(DEF_BMV)(MACRO(NUMERIC, "T", BAT_TEMPERATURE, "Battery temperature")) \ + IF(DEF_BMV)(MACRO(NUMERIC, "TTG", TTG, "Time To Go")) \ + MACRO(NUMERIC, "V", DC_CHANNEL1_VOLTAGE, "Battery voltage") \ + IF(DEF_MPPT)(MACRO(NUMERIC, "VPV", PANEL_VOLTAGE, "PV voltage")) \ + IF(DEF_INV)(MACRO(BITMASK, "WARN", WARNING_REASON, "Warning reason")) // clang-format on \ No newline at end of file