Skip to content

Commit

Permalink
Add select entity to control the operation mode (Closes: #53)
Browse files Browse the repository at this point in the history
  • Loading branch information
syssi committed Jun 28, 2022
1 parent 23b21aa commit 36491c0
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 3 deletions.
2 changes: 1 addition & 1 deletion components/soyosource_display/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

DEPENDENCIES = ["uart"]

AUTO_LOAD = ["button", "number", "sensor", "text_sensor"]
AUTO_LOAD = ["button", "number", "select", "sensor", "text_sensor"]

soyosource_display_ns = cg.esphome_ns.namespace("soyosource_display")
SoyosourceDisplay = soyosource_display_ns.class_(
Expand Down
74 changes: 74 additions & 0 deletions components/soyosource_display/select/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import esphome.codegen as cg
from esphome.components import select
import esphome.config_validation as cv
from esphome.const import CONF_ICON, CONF_ID

from .. import (
CONF_SOYOSOURCE_DISPLAY_COMPONENT_SCHEMA,
CONF_SOYOSOURCE_DISPLAY_ID,
soyosource_display_ns,
)

DEPENDENCIES = ["soyosource_display"]

CODEOWNERS = ["@syssi"]

CONF_OPERATION_MODE = "operation_mode"
CONF_OPTIONSMAP = "optionsmap"

ICON_OPERATION_MODE = "mdi:heart-pulse"

SoyosourceSelect = soyosource_display_ns.class_(
"SoyosourceSelect", select.Select, cg.Component
)


def ensure_option_map(value):
cv.check_not_templatable(value)
option = cv.All(cv.int_range(0, 2**16 - 1))
mapping = cv.All(cv.string_strict)
options_map_schema = cv.Schema({option: mapping})
value = options_map_schema(value)

all_values = list(value.keys())
unique_values = set(value.keys())
if len(all_values) != len(unique_values):
raise cv.Invalid("Mapping values must be unique.")

return value


SELECTS = {
CONF_OPERATION_MODE: 0x0A,
}

SOYOSOURCE_SELECT_SCHEMA = select.SELECT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(SoyosourceSelect),
cv.Optional(CONF_ICON, default=ICON_OPERATION_MODE): select.icon,
cv.Optional(CONF_OPTIONSMAP): ensure_option_map,
}
).extend(cv.COMPONENT_SCHEMA)

CONFIG_SCHEMA = CONF_SOYOSOURCE_DISPLAY_COMPONENT_SCHEMA.extend(
{
cv.Optional(CONF_OPERATION_MODE): SOYOSOURCE_SELECT_SCHEMA,
}
)


async def to_code(config):
hub = await cg.get_variable(config[CONF_SOYOSOURCE_DISPLAY_ID])

for key, address in SELECTS.items():
if key in config:
conf = config[key]
options_map = conf[CONF_OPTIONSMAP]
var = cg.new_Pvariable(conf[CONF_ID])
await cg.register_component(var, conf)
await select.register_select(var, conf, options=list(options_map.values()))
cg.add(var.set_select_mappings(list(options_map.keys())))

cg.add(getattr(hub, f"set_{key}_select")(var))
cg.add(var.set_parent(hub))
cg.add(var.set_holding_register(address))
47 changes: 47 additions & 0 deletions components/soyosource_display/select/soyosource_select.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#include "soyosource_select.h"
#include "esphome/core/log.h"

namespace esphome {
namespace soyosource_display {

static const char *const TAG = "soyosource_display.select";

void SoyosourceSelect::setup() {
this->parent_->register_select_listener(this->holding_register_, [this](const uint8_t &enum_value) {
ESP_LOGV(TAG, "Device reported select %u value %u", this->holding_register_, enum_value);
auto mappings = this->mappings_;
auto it = std::find(mappings.cbegin(), mappings.cend(), enum_value);
if (it == mappings.end()) {
ESP_LOGW(TAG, "Invalid value %u", enum_value);
return;
}
size_t mapping_idx = std::distance(mappings.cbegin(), it);
auto value = this->at(mapping_idx);
this->publish_state(value.value());
});
}

void SoyosourceSelect::dump_config() {
LOG_SELECT(TAG, "SoyosourceDisplay Select", this);
ESP_LOGCONFIG(TAG, " Select has register %u", this->holding_register_);
ESP_LOGCONFIG(TAG, " Options are:");
auto options = this->traits.get_options();
for (auto i = 0; i < this->mappings_.size(); i++) {
ESP_LOGCONFIG(TAG, " %i: %s", this->mappings_.at(i), options.at(i).c_str());
}
}

void SoyosourceSelect::control(const std::string &value) {
auto idx = this->index_of(value);
if (idx.has_value()) {
uint8_t mapping = this->mappings_.at(idx.value());
ESP_LOGV(TAG, "Setting %u datapoint value to %u:%s", this->holding_register_, mapping, value.c_str());
this->parent_->update_setting(this->holding_register_, mapping);
return;
}

ESP_LOGW(TAG, "Invalid value %s", value.c_str());
}

} // namespace soyosource_display
} // namespace esphome
33 changes: 33 additions & 0 deletions components/soyosource_display/select/soyosource_select.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#pragma once

#include <utility>
#include <map>

#include "../soyosource_display.h"
#include "esphome/core/component.h"
#include "esphome/components/select/select.h"

namespace esphome {
namespace soyosource_display {

class SoyosourceDisplay;

class SoyosourceSelect : public select::Select, public Component {
public:
void set_parent(SoyosourceDisplay *parent) { this->parent_ = parent; };
void set_holding_register(uint8_t holding_register) { this->holding_register_ = holding_register; };
void set_select_mappings(std::vector<uint8_t> mappings) { this->mappings_ = std::move(mappings); }

void setup() override;
void dump_config() override;

protected:
void control(const std::string &value) override;

std::vector<uint8_t> mappings_;
SoyosourceDisplay *parent_;
uint8_t holding_register_;
};

} // namespace soyosource_display
} // namespace esphome
22 changes: 20 additions & 2 deletions components/soyosource_display/soyosource_display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,15 +181,22 @@ void SoyosourceDisplay::on_settings_data_(const std::vector<uint8_t> &data) {
// Byte Len Payload Content Coeff. Unit Example value
// 0 1 0xA6 Header
// 1 1 0x00 Unknown
ESP_LOGI(TAG, " Unknown (byte 1): %02X", data[1]);
ESP_LOGD(TAG, " Unknown (byte 1): %02X", data[1]);

// 2 1 0x72
ESP_LOGI(TAG, " Unknown (byte 2): %02X", data[2]);
ESP_LOGD(TAG, " Unknown (byte 2): %02X", data[2]);

// 3 1 0x93 Operation mode (High nibble), Frame function (Low nibble)
uint8_t operation_mode_setting = this->operation_mode_to_operation_mode_setting_(data[3] >> 4);
ESP_LOGI(TAG, " Operation mode setting: %02X", operation_mode_setting);
this->current_settings_.OperationMode = operation_mode_setting;
if (this->operation_mode_select_ != nullptr) {
for (auto &listener : this->select_listeners_) {
if (listener.holding_register == 0x0A) {
listener.on_value(operation_mode_setting);
}
}
}

// 4 1 0x40 Operation status bitmask

Expand Down Expand Up @@ -294,6 +301,9 @@ void SoyosourceDisplay::update_setting(uint8_t holding_register, float value) {
case 0x09:
new_settings.StartDelay = (uint8_t) value;
break;
case 0x0A:
new_settings.OperationMode = (uint8_t) value;
break;
default:
ESP_LOGE(TAG, "Unknown settings register. Aborting write settings");
return;
Expand Down Expand Up @@ -413,6 +423,14 @@ std::string SoyosourceDisplay::error_bits_to_string_(const uint8_t &error_bits)
return errors_list;
}

void SoyosourceDisplay::register_select_listener(uint8_t holding_register, const std::function<void(uint8_t)> &func) {
auto select_listener = SoyosourceSelectListener{
.holding_register = holding_register,
.on_value = func,
};
this->select_listeners_.push_back(select_listener);
}

void SoyosourceDisplay::publish_state_(binary_sensor::BinarySensor *binary_sensor, const bool &state) {
if (binary_sensor == nullptr)
return;
Expand Down
15 changes: 15 additions & 0 deletions components/soyosource_display/soyosource_display.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "esphome/core/component.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/number/number.h"
#include "esphome/components/select/select.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/uart/uart.h"
Expand All @@ -23,6 +24,11 @@ struct SoyosourceSettingsFrameT {
uint8_t Checksum;
};

struct SoyosourceSelectListener {
uint8_t holding_register;
std::function<void(uint8_t)> on_value;
};

class SoyosourceDisplay : public uart::UARTDevice, public PollingComponent {
public:
void set_fan_running_binary_sensor(binary_sensor::BinarySensor *fan_running_binary_sensor) {
Expand All @@ -41,6 +47,10 @@ class SoyosourceDisplay : public uart::UARTDevice, public PollingComponent {
}
void set_start_delay_number(number::Number *start_delay_number) { start_delay_number_ = start_delay_number; }

void set_operation_mode_select(select::Select *operation_mode_select) {
operation_mode_select_ = operation_mode_select;
}

void set_error_bitmask_sensor(sensor::Sensor *error_bitmask_sensor) { error_bitmask_sensor_ = error_bitmask_sensor; }
void set_operation_mode_id_sensor(sensor::Sensor *operation_mode_id_sensor) {
operation_mode_id_sensor_ = operation_mode_id_sensor;
Expand Down Expand Up @@ -68,6 +78,7 @@ class SoyosourceDisplay : public uart::UARTDevice, public PollingComponent {
}

SoyosourceSettingsFrameT get_current_settings() { return current_settings_; }
void register_select_listener(uint8_t holding_register, const std::function<void(uint8_t)> &func);
void send_command(uint8_t function, uint8_t start_voltage = 0, uint8_t shutdown_voltage = 0,
uint8_t output_power_limit = 0, uint8_t grid_frequency = 0, uint8_t start_delay = 0,
uint8_t operation_mode = 0);
Expand All @@ -86,6 +97,8 @@ class SoyosourceDisplay : public uart::UARTDevice, public PollingComponent {
number::Number *output_power_limit_number_;
number::Number *start_delay_number_;

select::Select *operation_mode_select_;

sensor::Sensor *error_bitmask_sensor_;
sensor::Sensor *operation_mode_id_sensor_;
sensor::Sensor *operation_status_id_sensor_;
Expand All @@ -100,6 +113,7 @@ class SoyosourceDisplay : public uart::UARTDevice, public PollingComponent {
text_sensor::TextSensor *operation_status_text_sensor_;
text_sensor::TextSensor *errors_text_sensor_;

std::vector<SoyosourceSelectListener> select_listeners_;
std::vector<uint8_t> rx_buffer_;
uint32_t last_byte_{0};
uint32_t last_send_{0};
Expand All @@ -111,6 +125,7 @@ class SoyosourceDisplay : public uart::UARTDevice, public PollingComponent {
bool parse_soyosource_display_byte_(uint8_t byte);
void publish_state_(binary_sensor::BinarySensor *binary_sensor, const bool &state);
void publish_state_(number::Number *number, float value);
void publish_state_(select::Select *select, const std::string &state);
void publish_state_(sensor::Sensor *sensor, float value);
void publish_state_(text_sensor::TextSensor *text_sensor, const std::string &state);
void write_settings_(SoyosourceSettingsFrameT *frame);
Expand Down
10 changes: 10 additions & 0 deletions esp8266-display-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ number:
start_delay:
name: "${name} start delay"

select:
- platform: soyosource_display
operation_mode:
name: "${name} operation mode"
optionsmap:
1: "PV"
2: "Battery Constant Power"
17: "PV Limit"
18: "Battery Limit"

sensor:
- platform: template
name: "dummy powermeter"
Expand Down

0 comments on commit 36491c0

Please sign in to comment.