Skip to content

Commit

Permalink
implement linking between TEXT frame values and HEX registers (m3_ved…
Browse files Browse the repository at this point in the history
…irect)
  • Loading branch information
krahabb committed Oct 31, 2024
1 parent 4f35d0a commit 7f3e918
Show file tree
Hide file tree
Showing 27 changed files with 745 additions and 266 deletions.
10 changes: 2 additions & 8 deletions esphome/components/m3_vedirect/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,8 @@
from esphome.const import CONF_ID, CONF_NAME, CONF_PAYLOAD, CONF_TRIGGER_ID

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

m3_vedirect_ns = cg.esphome_ns.namespace("m3_vedirect")
Expand Down
17 changes: 13 additions & 4 deletions esphome/components/m3_vedirect/binary_sensor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import esphome.codegen as cg
from esphome.components import binary_sensor
import esphome.config_validation as cv

Expand All @@ -15,11 +16,17 @@
entity_category="diagnostic"
)

# m3_vedirect::BinarySensor mapped to HEX/TEXT data
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)
VEDIRECT_BINARY_SENSOR_SCHEMA = (
binary_sensor.binary_sensor_schema(VEDirectBinarySensor)
.extend(VEDIRECT_ENTITY_SCHEMA)
.extend(
{
cv.Optional(CONF_MASK): cv.uint32_t,
}
)
)

PLATFORM_ENTITIES = {
CONF_VEDIRECT_ENTITIES: cv.ensure_list(VEDIRECT_BINARY_SENSOR_SCHEMA),
Expand All @@ -31,6 +38,8 @@

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
61 changes: 49 additions & 12 deletions esphome/components/m3_vedirect/binary_sensor/binary_sensor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,65 @@ void BinarySensor::init_reg_def_() {
}
}

void BinarySensor::parse_hex_default_(HexRegister *hexregister, const RxHexFrame *hexframe) {
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");
static_cast<BinarySensor *>(hexregister)->publish_state(hexframe->data_u8());
static_cast<BinarySensor *>(hex_register)->publish_state(hex_frame->data_u8());
}
void BinarySensor::parse_hex_bitmask_(HexRegister *hexregister, const RxHexFrame *hexframe) {

void BinarySensor::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");
BinarySensor *binary_sensor = static_cast<BinarySensor *>(hexregister);
binary_sensor->publish_state(HEXFRAME::GET_DATA_AS_INT[binary_sensor->reg_def_->data_type](hexframe->record()) &
binary_sensor->mask_);
static_cast<BinarySensor *>(hex_register)
->parse_bitmask_(HEXFRAME::GET_DATA_AS_INT[hex_register->get_reg_def()->data_type](hex_frame->record()));
}
void BinarySensor::parse_hex_enum_(HexRegister *hexregister, const RxHexFrame *hexframe) {

void BinarySensor::parse_hex_enum_(HexRegister *hex_register, const RxHexFrame *hex_frame) {
static_assert(RxHexFrame::ALLOCATED_DATA_SIZE >= 1, "HexFrame storage might lead to access overflow");
BinarySensor *binary_sensor = static_cast<BinarySensor *>(hexregister);
binary_sensor->publish_state(hexframe->data_u8() == binary_sensor->mask_);
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);
}

void BinarySensor::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<BinarySensor *>(hex_register)->parse_bitmask_(bitmask_value);
}
}

void BinarySensor::parse_text_(const char *text_value) { publish_state(!strcasecmp(text_value, "ON")); }
void BinarySensor::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<BinarySensor *>(hex_register)->parse_enum_(enum_value);
}
}

void BinarySensor::parse_bitmask_(BITMASK_DEF::bitmask_t bitmask, const REG_DEF *reg_def) {
this->publish_state(bitmask & this->mask_);
void BinarySensor::parse_bitmask_(BITMASK_DEF::bitmask_t bitmask_value) {
this->publish_state(bitmask_value & this->mask_);
}

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
21 changes: 13 additions & 8 deletions esphome/components/m3_vedirect/binary_sensor/binary_sensor.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,32 @@
namespace esphome {
namespace m3_vedirect {

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

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

protected:
friend class Manager;

u_int32_t mask_{0xFFFFFFFF};
uint32_t mask_{0xFFFFFFFF};

void dynamic_register_() override;
void init_reg_def_() override;

static void parse_hex_default_(HexRegister *hexregister, const RxHexFrame *hexframe);
static void parse_hex_bitmask_(HexRegister *hexregister, const RxHexFrame *hexframe);
static void parse_hex_enum_(HexRegister *hexregister, const RxHexFrame *hexframe);
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);

void parse_text_(const char *text_value) override;
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);

void parse_bitmask_(BITMASK_DEF::bitmask_t bitmask, const REG_DEF *reg_def) override;
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
2 changes: 1 addition & 1 deletion esphome/components/m3_vedirect/defines.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once
#include "ve_reg_defs.h"
#include <stddef.h>
#include <string.h>
#include <cstring>

namespace esphome {
namespace m3_vedirect {
Expand Down
57 changes: 11 additions & 46 deletions esphome/components/m3_vedirect/entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,57 +21,22 @@ const sensor::StateClass Entity::UNIT_TO_STATE_CLASS[] = {
sensor::StateClass::STATE_CLASS_MEASUREMENT,
};

#define DEF_TFBINARYSENSOR(name, disabled) \
{ name, Entity::CLASS::BOOLEAN, disabled, UNIT::NONE, DIGITS::D_0 }
#define DEF_TFSENSOR(name, disabled, unit, digits) \
{ name, Entity::CLASS::NUMERIC, disabled, unit, digits }
#define DEF_TFTEXTSENSOR(name, disabled) \
{ name, Entity::CLASS::ENUM, disabled, UNIT::NONE, DIGITS::D_0 }

const Entity::text_def_map_t Entity::TEXT_DEFS{
{"AC_OUT_I", DEF_TFSENSOR("AC output current", false, UNIT::A, DIGITS::D_1)},
{"AC_OUT_S", DEF_TFSENSOR("AC output apparent power", false, UNIT::VA, DIGITS::D_0)},
{"AC_OUT_V", DEF_TFSENSOR("AC output voltage", false, UNIT::V, DIGITS::D_2)},
{"H19", DEF_TFSENSOR("Yield total", false, UNIT::kWh, DIGITS::D_2)},
{"H20", DEF_TFSENSOR("Yield today", false, UNIT::kWh, DIGITS::D_2)},
{"H21", DEF_TFSENSOR("Maximum power today", false, UNIT::W, DIGITS::D_0)},
{"H22", DEF_TFSENSOR("Yield yesterday", true, UNIT::kWh, DIGITS::D_2)},
{"H23", DEF_TFSENSOR("Maximum power yesterday", true, UNIT::W, DIGITS::D_0)},
{"I", DEF_TFSENSOR("Battery current", false, UNIT::A, DIGITS::D_3)},
{"IL", DEF_TFSENSOR("Load current", false, UNIT::A, DIGITS::D_3)},
{"PPV", DEF_TFSENSOR("PV power", false, UNIT::W, DIGITS::D_0)},
{"V", DEF_TFSENSOR("Battery voltage", false, UNIT::V, DIGITS::D_3)},
{"VPV", DEF_TFSENSOR("PV voltage", false, UNIT::V, DIGITS::D_3)},

{"AR", DEF_TFTEXTSENSOR("Alarm reason", false)},
{"CS", DEF_TFTEXTSENSOR("State of operation", false)},
{"ERR", DEF_TFTEXTSENSOR("Error code", false)},
{"FW", DEF_TFTEXTSENSOR("Firmware version (FW)", true)},
{"FWE", DEF_TFTEXTSENSOR("Firmware version (FWE)", true)},
{"MODE", DEF_TFTEXTSENSOR("Device mode", false)},
{"MPPT", DEF_TFTEXTSENSOR("Tracker operation mode", false)},
{"OR", DEF_TFTEXTSENSOR("Off reason", false)},
{"PID", DEF_TFTEXTSENSOR("Product Id", true)},
{"Relay", DEF_TFTEXTSENSOR("Relay state", false)},
{"SER#", DEF_TFTEXTSENSOR("Serial number", true)},
{"WARN", DEF_TFTEXTSENSOR("Warning reason", false)},

{"Alarm", DEF_TFBINARYSENSOR("Alarm", false)},
{"LOAD", DEF_TFBINARYSENSOR("Output state", false)},

};

void Entity::set_text_label(Manager *manager, const char *label) {
auto text_def_it = TEXT_DEFS.find(label);
if (text_def_it != TEXT_DEFS.end())
this->init_text_def_(&text_def_it->second);
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);
}
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);
this->set_reg_def(reg_def ? reg_def : new REG_DEF(register_id));
manager->hex_registers_.emplace(register_id, this);
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
Expand Down
29 changes: 0 additions & 29 deletions esphome/components/m3_vedirect/entity.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,6 @@ class Entity : public HexRegister {

typedef REG_DEF::DIGITS DIGITS;

struct TEXT_DEF {
const char *description;
const CLASS cls : 3;
const bool initially_disabled : 1;
// Optional entity 'class' definitions
union {
// Sensor entity definitions
struct {
const UNIT unit : 4;
const DIGITS digits : 2;
} __attribute__((packed));
};
} __attribute__((packed));

typedef std::unordered_map<const char *, const TEXT_DEF, cstring_hash, cstring_eq> text_def_map_t;
static const text_def_map_t TEXT_DEFS;
static const TEXT_DEF *get_text_def(const char *label) {
auto entity_def = TEXT_DEFS.find(label);
return entity_def == TEXT_DEFS.end() ? nullptr : &entity_def->second;
}

/// @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
Expand All @@ -57,14 +36,6 @@ class Entity : public HexRegister {
/// @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_(){};

/// @brief Preset entity properties based off our TEXT_DEF. This is being called
/// automatically by VEDirectEntity methods when a proper definition is available.
virtual void init_text_def_(const TEXT_DEF *text_def) {}
/// @brief Called by Manager when a new TEXT FRAME has been received.
/// Parses the current value from VE.Direct TEXT frame and update this entity.
/// @param value the raw data carried by VE.Direct
virtual void parse_text_(const char *text_value){};
};

/// @brief Specialization for entities that can write configuration data to
Expand Down
15 changes: 13 additions & 2 deletions esphome/components/m3_vedirect/hexregister.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
#include "hexregister.h"
#include "manager.h"

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_();
manager->hex_registers_.emplace(reg_def->register_id, this);
}

/*
void BitmaskHexRegister::link_disconnected_() { this->bitmask_ = BITMASK_DEF::VALUE_UNKNOWN; }
void BitmaskHexRegister::init_reg_def_() { this->parse_hex_ = parse_hex_bitmask_; }
void BitmaskHexRegister::parse_hex_bitmask_(HexRegister *hexregister, const RxHexFrame *hexframe) {
BitmaskHexRegister *bitmask_hex_register = static_cast<BitmaskHexRegister *>(hexregister);
void BitmaskHexRegister::parse_hex_bitmask_(HexRegister *hex_register, const RxHexFrame *hexframe) {
BitmaskHexRegister *bitmask_hex_register = static_cast<BitmaskHexRegister *>(hex_register);
BITMASK_DEF::bitmask_t bitmask =
HEXFRAME::GET_DATA_AS_INT[bitmask_hex_register->reg_def_->data_type](hexframe->record());
if (bitmask != bitmask_hex_register->bitmask_) {
Expand All @@ -17,6 +27,7 @@ void BitmaskHexRegister::parse_hex_bitmask_(HexRegister *hexregister, const RxHe
}
}
}
*/

} // namespace m3_vedirect
} // namespace esphome
35 changes: 25 additions & 10 deletions esphome/components/m3_vedirect/hexregister.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,20 @@ namespace m3_vedirect {

class HexRegister {
public:
void set_reg_def(const REG_DEF *reg_def) {
this->reg_def_ = reg_def;
this->init_reg_def_();
}
void set_reg_def(Manager *manager, const REG_DEF *reg_def);
const REG_DEF *get_reg_def() { return this->reg_def_; }

typedef FrameHandler::RxHexFrame RxHexFrame;
typedef void (*parse_hex_func_t)(HexRegister *hexregister, const RxHexFrame *hexframe);
inline parse_hex_func_t parse_hex() { return this->parse_hex_; }
typedef void (*parse_hex_func_t)(HexRegister *hex_register, const RxHexFrame *hexframe);
inline void parse_hex(const RxHexFrame *hexframe) { this->parse_hex_(this, hexframe); }

typedef void (*parse_text_func_t)(HexRegister *hex_register, const char *text_value);
inline void parse_text(const char *text_value) { this->parse_text_(this, text_value); }

protected:
friend class Manager;
const REG_DEF *reg_def_{};
static const REG_DEF REG_DEF_UNDEFINED;
const REG_DEF *reg_def_{&REG_DEF_UNDEFINED};
parse_hex_func_t parse_hex_{parse_hex_empty_};

// called by the Manager when VEDirect timeouts (we'll send 'unknown' to APIServer)
Expand All @@ -30,15 +31,28 @@ class HexRegister {
/// @param reg_def: the proper register definition if available
virtual void init_reg_def_(){};

static void parse_hex_empty_(HexRegister *hexregister, const RxHexFrame *hexframe) {}
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){};
virtual void parse_enum_(ENUM_DEF::enum_t enum_value){};
virtual void parse_string_(const char *string_value){};
};

/*
/// @brief Base class (interface) for entities which are linked to BITMASK registers
/// like BinarySensor, TextSensor, Switch
class BitmaskParser {
protected:
friend class BitmaskHexRegister;
virtual void parse_bitmask_(BITMASK_DEF::bitmask_t bitmask, const REG_DEF *reg_def){};
virtual void parse_bitmask_(BITMASK_DEF::bitmask_t bitmask, const REG_DEF *reg_def) {};
};
class BitmaskHexRegister : public HexRegister {
Expand All @@ -52,8 +66,9 @@ class BitmaskHexRegister : public HexRegister {
void link_disconnected_() override;
void init_reg_def_() override;
static void parse_hex_bitmask_(HexRegister *hexregister, const RxHexFrame *hexframe);
static void parse_hex_bitmask_(HexRegister *hex_register, const RxHexFrame *hexframe);
};
*/

} // namespace m3_vedirect
} // namespace esphome
Loading

0 comments on commit 7f3e918

Please sign in to comment.