From 30f093ebc9633184cff0d0d04f443ce3fe2bec6b Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Mon, 27 Jun 2022 14:30:41 +0200 Subject: [PATCH] Add select entity to change the operation mode (Closes: #53) --- components/soyosource_display/__init__.py | 8 + .../soyosource_display/button/__init__.py | 49 +++++ .../button/soyosource_button.cpp | 16 ++ .../button/soyosource_button.h | 27 +++ .../soyosource_display/number/__init__.py | 145 +++++++++++++++ .../number/soyosource_number.cpp | 13 ++ .../number/soyosource_number.h | 26 +++ .../soyosource_display/soyosource_display.cpp | 168 +++++++++++++----- .../soyosource_display/soyosource_display.h | 39 +++- esp8266-display-example.yaml | 68 ++++++- 10 files changed, 509 insertions(+), 50 deletions(-) create mode 100644 components/soyosource_display/button/__init__.py create mode 100644 components/soyosource_display/button/soyosource_button.cpp create mode 100644 components/soyosource_display/button/soyosource_button.h create mode 100644 components/soyosource_display/number/__init__.py create mode 100644 components/soyosource_display/number/soyosource_number.cpp create mode 100644 components/soyosource_display/number/soyosource_number.h diff --git a/components/soyosource_display/__init__.py b/components/soyosource_display/__init__.py index c7bb89f..97d4a19 100644 --- a/components/soyosource_display/__init__.py +++ b/components/soyosource_display/__init__.py @@ -5,6 +5,8 @@ DEPENDENCIES = ["uart"] +AUTO_LOAD = ["button", "number", "sensor", "text_sensor"] + soyosource_display_ns = cg.esphome_ns.namespace("soyosource_display") SoyosourceDisplay = soyosource_display_ns.class_( "SoyosourceDisplay", cg.PollingComponent, uart.UARTDevice @@ -14,6 +16,12 @@ CONF_SOYOSOURCE_DISPLAY_ID = "soyosource_display_id" +CONF_SOYOSOURCE_DISPLAY_COMPONENT_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_SOYOSOURCE_DISPLAY_ID): cv.use_id(SoyosourceDisplay), + } +) + CONFIG_SCHEMA = ( cv.Schema( { diff --git a/components/soyosource_display/button/__init__.py b/components/soyosource_display/button/__init__.py new file mode 100644 index 0000000..17267fb --- /dev/null +++ b/components/soyosource_display/button/__init__.py @@ -0,0 +1,49 @@ +import esphome.codegen as cg +from esphome.components import button +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_RESTART = "restart" + +ICON_RESTART = "mdi:restart" + +BUTTONS = { + CONF_RESTART: 0x11, +} + +SoyosourceButton = soyosource_display_ns.class_( + "SoyosourceButton", button.Button, cg.Component +) + +CONFIG_SCHEMA = CONF_SOYOSOURCE_DISPLAY_COMPONENT_SCHEMA.extend( + { + cv.Optional(CONF_RESTART): button.BUTTON_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SoyosourceButton), + cv.Optional(CONF_ICON, default=ICON_RESTART): cv.icon, + } + ).extend(cv.COMPONENT_SCHEMA), + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_SOYOSOURCE_DISPLAY_ID]) + for key, address in BUTTONS.items(): + if key in config: + conf = config[key] + var = cg.new_Pvariable(conf[CONF_ID]) + await cg.register_component(var, conf) + await button.register_button(var, conf) + cg.add(var.set_parent(hub)) + cg.add(var.set_holding_register(address)) diff --git a/components/soyosource_display/button/soyosource_button.cpp b/components/soyosource_display/button/soyosource_button.cpp new file mode 100644 index 0000000..144ca73 --- /dev/null +++ b/components/soyosource_display/button/soyosource_button.cpp @@ -0,0 +1,16 @@ +#include "soyosource_button.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace soyosource_display { + +static const char *const TAG = "soyosource_display.button"; + +void SoyosourceButton::dump_config() { LOG_BUTTON("", "SoyosourceDisplay Button", this); } +void SoyosourceButton::press_action() { + this->parent_->send_command(this->holding_register_, 0x30, 0x2D, 0x5A, 0x64, 0x06, 0x00); +} + +} // namespace soyosource_display +} // namespace esphome diff --git a/components/soyosource_display/button/soyosource_button.h b/components/soyosource_display/button/soyosource_button.h new file mode 100644 index 0000000..e59739c --- /dev/null +++ b/components/soyosource_display/button/soyosource_button.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../soyosource_display.h" +#include "esphome/core/component.h" +#include "esphome/components/button/button.h" + +namespace esphome { +namespace soyosource_display { + +class SoyosourceDisplay; + +class SoyosourceButton : public button::Button, 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 dump_config() override; + void loop() override {} + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void press_action() override; + SoyosourceDisplay *parent_; + uint8_t holding_register_; +}; + +} // namespace soyosource_display +} // namespace esphome diff --git a/components/soyosource_display/number/__init__.py b/components/soyosource_display/number/__init__.py new file mode 100644 index 0000000..bb500f4 --- /dev/null +++ b/components/soyosource_display/number/__init__.py @@ -0,0 +1,145 @@ +import esphome.codegen as cg +from esphome.components import number +import esphome.config_validation as cv +from esphome.const import ( + CONF_ENTITY_CATEGORY, + CONF_ICON, + CONF_ID, + CONF_MAX_VALUE, + CONF_MIN_VALUE, + CONF_MODE, + CONF_STEP, + CONF_UNIT_OF_MEASUREMENT, + ENTITY_CATEGORY_CONFIG, + ICON_EMPTY, + UNIT_SECOND, + UNIT_VOLT, + UNIT_WATT, +) + +from .. import ( + CONF_SOYOSOURCE_DISPLAY_COMPONENT_SCHEMA, + CONF_SOYOSOURCE_DISPLAY_ID, + soyosource_display_ns, +) + +DEPENDENCIES = ["soyosource_display"] + +CODEOWNERS = ["@syssi"] + +DEFAULT_STEP = 1 + +CONF_START_VOLTAGE = "start_voltage" +CONF_SHUTDOWN_VOLTAGE = "shutdown_voltage" +CONF_OUTPUT_POWER_LIMIT = "output_power_limit" +CONF_START_DELAY = "start_delay" + +# Write settings frame: 0x55 0x0B 0x30 0x2D 0x5A 0x64 0x00 0x00 0x00 0x06 0x01 0xD2 +# 0 1 2 3 4 5 6 7 8 9 10 11 +# | | | | | | | | | | | CRC +# | | | | | | | | | | Operation mode +# | | | | | | | | | Start delay +# | | | | | | | | Unused +# | | | | | | | Unused +# | | | | | | Unused +# | | | | | Grid frequency +# | | | | Output power +# | | | Shutdown voltage +# | | Start voltage +# | Write settings +# SOF Request + +# Settings frame: 0xA6 0x00 0x00 0x63 0x02 0xD4 0x30 0x30 0x2D 0x00 0xFA 0x64 0x5A 0x06 0x7B +# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 +# | | | | | | | | | | | CRC +# | | | | | | | | | | Start delay +# | | | | | | | | | Output power +# | | | | | | | | Grid frequency +# | | | | | | Grid voltage (2 bytes) +# | | | | | Shutdown voltage +# | | | | Start voltage +# | | | Device type +# | | Device model +# | Operation status bitmask +# Operation mode (High nibble), Frame function (Low nibble) +NUMBERS = { + CONF_START_VOLTAGE: 0x02, + CONF_SHUTDOWN_VOLTAGE: 0x03, + CONF_OUTPUT_POWER_LIMIT: 0x04, + CONF_START_DELAY: 0x09, +} + +SoyosourceNumber = soyosource_display_ns.class_( + "SoyosourceNumber", number.Number, cg.Component +) + +SOYOSOURCE_NUMBER_SCHEMA = number.NUMBER_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(SoyosourceNumber), + cv.Optional(CONF_ICON, default=ICON_EMPTY): number.icon, + cv.Optional(CONF_STEP, default=0.01): cv.float_, + cv.Optional(CONF_UNIT_OF_MEASUREMENT, default=UNIT_VOLT): cv.string_strict, + cv.Optional(CONF_MODE, default="BOX"): cv.enum(number.NUMBER_MODES, upper=True), + cv.Optional( + CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_CONFIG + ): cv.entity_category, + } +).extend(cv.COMPONENT_SCHEMA) + +CONFIG_SCHEMA = CONF_SOYOSOURCE_DISPLAY_COMPONENT_SCHEMA.extend( + { + cv.Optional(CONF_START_VOLTAGE): SOYOSOURCE_NUMBER_SCHEMA.extend( + { + cv.Optional(CONF_MIN_VALUE, default=1): cv.float_, + cv.Optional(CONF_MAX_VALUE, default=90): cv.float_, + cv.Optional(CONF_STEP, default=1): cv.float_, + } + ), + cv.Optional(CONF_SHUTDOWN_VOLTAGE): SOYOSOURCE_NUMBER_SCHEMA.extend( + { + cv.Optional(CONF_MIN_VALUE, default=1): cv.float_, + cv.Optional(CONF_MAX_VALUE, default=90): cv.float_, + cv.Optional(CONF_STEP, default=1): cv.float_, + } + ), + cv.Optional(CONF_OUTPUT_POWER_LIMIT): SOYOSOURCE_NUMBER_SCHEMA.extend( + { + cv.Optional(CONF_MIN_VALUE, default=10): cv.float_, + cv.Optional(CONF_MAX_VALUE, default=900): cv.float_, + cv.Optional(CONF_STEP, default=10): cv.float_, + cv.Optional( + CONF_UNIT_OF_MEASUREMENT, default=UNIT_WATT + ): cv.string_strict, + } + ), + cv.Optional(CONF_START_DELAY): SOYOSOURCE_NUMBER_SCHEMA.extend( + { + cv.Optional(CONF_MIN_VALUE, default=1): cv.float_, + cv.Optional(CONF_MAX_VALUE, default=60): cv.float_, + cv.Optional(CONF_STEP, default=1): cv.float_, + cv.Optional( + CONF_UNIT_OF_MEASUREMENT, default=UNIT_SECOND + ): cv.string_strict, + } + ), + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_SOYOSOURCE_DISPLAY_ID]) + for key, address in NUMBERS.items(): + if key in config: + conf = config[key] + var = cg.new_Pvariable(conf[CONF_ID]) + await cg.register_component(var, conf) + await number.register_number( + var, + conf, + min_value=conf[CONF_MIN_VALUE], + max_value=conf[CONF_MAX_VALUE], + step=conf[CONF_STEP], + ) + cg.add(getattr(hub, f"set_{key}_number")(var)) + cg.add(var.set_parent(hub)) + cg.add(var.set_holding_register(address)) diff --git a/components/soyosource_display/number/soyosource_number.cpp b/components/soyosource_display/number/soyosource_number.cpp new file mode 100644 index 0000000..0b5b62d --- /dev/null +++ b/components/soyosource_display/number/soyosource_number.cpp @@ -0,0 +1,13 @@ +#include "soyosource_number.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace soyosource_display { + +static const char *const TAG = "soyosource_display.number"; + +void SoyosourceNumber::dump_config() { LOG_NUMBER("", "SoyosourceDisplay Number", this); } +void SoyosourceNumber::control(float value) { this->parent_->update_setting(this->holding_register_, value); } + +} // namespace soyosource_display +} // namespace esphome diff --git a/components/soyosource_display/number/soyosource_number.h b/components/soyosource_display/number/soyosource_number.h new file mode 100644 index 0000000..acde5f4 --- /dev/null +++ b/components/soyosource_display/number/soyosource_number.h @@ -0,0 +1,26 @@ +#pragma once + +#include "../soyosource_display.h" +#include "esphome/core/component.h" +#include "esphome/components/number/number.h" + +namespace esphome { +namespace soyosource_display { + +class SoyosourceDisplay; + +class SoyosourceNumber : public number::Number, 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 dump_config() override; + + protected: + void control(float value) override; + + SoyosourceDisplay *parent_; + uint8_t holding_register_; +}; + +} // namespace soyosource_display +} // namespace esphome diff --git a/components/soyosource_display/soyosource_display.cpp b/components/soyosource_display/soyosource_display.cpp index 1f1be23..ad896ee 100644 --- a/components/soyosource_display/soyosource_display.cpp +++ b/components/soyosource_display/soyosource_display.cpp @@ -7,10 +7,13 @@ namespace soyosource_display { static const char *const TAG = "soyosource_display"; +static const uint8_t SOF_REQUEST = 0x55; +static const uint8_t SOF_RESPONSE = 0xA6; + static const uint8_t STATUS_COMMAND = 0x01; static const uint8_t SETTINGS_COMMAND = 0x03; -// static const uint8_t REBOOT_COMMAND = 0x11; -// static const uint8_t WRITE_SETTINGS_COMMAND = 0x0B; +static const uint8_t REBOOT_COMMAND = 0x11; +static const uint8_t WRITE_SETTINGS_COMMAND = 0x0B; static const uint8_t ERRORS_SIZE = 8; static const char *const ERRORS[ERRORS_SIZE] = { @@ -65,14 +68,10 @@ bool SoyosourceDisplay::parse_soyosource_display_byte_(uint8_t byte) { // Settings request >>> 0x55 0x03 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0xFC // Settings response <<< 0xA6 0x00 0x00 0xD3 0x02 0xD4 0x30 0x30 0x2D 0x00 0xFB 0x64 0x4B 0x06 0x19 // - // Status response: 0xA6 0x03 0x84 0x91 0x40 0x01 0xC5 0x00 0xDB 0x00 0xF7 0x63 0x02 0xBC 0xFE - // Settings response: 0xA6 0x01 0x72 0x93 0x40 0xD4 0x30 0x2C 0x2B 0x00 0xFA 0x64 0x5A 0x03 0xA3 - // addr func ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ crc - // data if (at == 0) return true; - if (raw[0] != 0xA6) { + if (raw[0] != SOF_RESPONSE) { ESP_LOGW(TAG, "Invalid header."); // return false to reset buffer @@ -109,6 +108,7 @@ void SoyosourceDisplay::on_soyosource_display_data_(const uint8_t &function, con switch (function) { case STATUS_COMMAND: this->on_status_data_(data); + this->send_command(SETTINGS_COMMAND); break; case SETTINGS_COMMAND: this->on_settings_data_(data); @@ -187,6 +187,10 @@ void SoyosourceDisplay::on_settings_data_(const std::vector &data) { ESP_LOGI(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; + // 4 1 0x40 Operation status bitmask // 5 1 0xD4 Device model @@ -196,22 +200,36 @@ void SoyosourceDisplay::on_settings_data_(const std::vector &data) { ESP_LOGI(TAG, " Device type: %s (%d)", this->device_type_to_string_(data[6]).c_str(), data[6]); // 7 1 0x2C Starting voltage V 44 - ESP_LOGI(TAG, " Starting voltage: %d V", data[7]); + uint8_t start_voltage = data[7]; + ESP_LOGI(TAG, " Start voltage: %d V", start_voltage); + this->current_settings_.StartVoltage = start_voltage; + this->publish_state_(this->start_voltage_number_, start_voltage); // 8 1 0x2B Shutdown voltage V 43 - ESP_LOGI(TAG, " Shutdown voltage: %d V", data[8]); + uint8_t shutdown_voltage = data[8]; + ESP_LOGI(TAG, " Shutdown voltage: %d V", shutdown_voltage); + this->current_settings_.ShutdownVoltage = shutdown_voltage; + this->publish_state_(this->shutdown_voltage_number_, shutdown_voltage); // 9 2 0x00 0xFA Grid voltage V 250 ESP_LOGV(TAG, " Grid voltage: %.0f V", (float) soyosource_get_16bit(9)); - // 11 1 0x64 Grid frequency 0.5 Hz 100 - ESP_LOGV(TAG, " Grid frequency: %.1f Hz", (float) data[11] * 0.5f); + // 11 1 0x64 Grid frequency 0.5 Hz 100 + uint8_t grid_frequency = data[11]; + ESP_LOGV(TAG, " Grid frequency: %.1f Hz", (float) grid_frequency * 0.5f); + this->current_settings_.GridFrequency = grid_frequency; // 12 1 0x5A Battery output power 10 W 90 - ESP_LOGI(TAG, " Constant power mode power: %d W", data[12] * 10); + uint8_t output_power_limit = data[12]; + ESP_LOGI(TAG, " Output power limit: %d W", output_power_limit * 10); + this->current_settings_.OutputPowerLimit = output_power_limit; + this->publish_state_(this->output_power_limit_number_, output_power_limit * 10); // 13 1 0x03 Delay in seconds s 3 - ESP_LOGI(TAG, " Start delay: %d s", data[13]); + uint8_t start_delay = data[13]; + ESP_LOGI(TAG, " Start delay: %d s", start_delay); + this->current_settings_.StartDelay = start_delay; + this->publish_state_(this->start_delay_number_, start_delay); } void SoyosourceDisplay::dump_config() { @@ -236,46 +254,65 @@ float SoyosourceDisplay::get_setup_priority() const { return setup_priority::BUS - 1.0f; } -void SoyosourceDisplay::update() { this->send_command_(STATUS_COMMAND); } +void SoyosourceDisplay::update() { this->send_command(STATUS_COMMAND); } -void SoyosourceDisplay::publish_state_(binary_sensor::BinarySensor *binary_sensor, const bool &state) { - if (binary_sensor == nullptr) - return; +void SoyosourceDisplay::send_command(uint8_t function, uint8_t start_voltage, uint8_t shutdown_voltage, + uint8_t output_power_limit, uint8_t grid_frequency, uint8_t start_delay, + uint8_t operation_mode) { + uint8_t frame[12]; - binary_sensor->publish_state(state); + frame[0] = SOF_REQUEST; // Header + frame[1] = function; // Function (0x01: Status, 0x03: Settings, 0x11: Reboot, 0x0B: Write settings) + frame[2] = start_voltage; // Start voltage (0x30: 48 V) + frame[3] = shutdown_voltage; // Shutdown voltage (0x2D: 45 V) + frame[4] = output_power_limit; // Constant Power / Power Limit (0x5A: 90 * 100 = 900 W) + frame[5] = grid_frequency; // Grid frequency (0x64: 100 * 0.5 = 50 Hz) + frame[6] = 0x00; + frame[7] = 0x00; + frame[8] = 0x00; + frame[9] = start_delay; // Start delay in seconds (0x06: 6 seconds) + frame[10] = operation_mode; // Operation mode (0x01: PV, 0x11: PV Limit, 0x02: Bat Const, 0x12: Bat Limit) + frame[11] = chksum(frame, 11); + + this->write_array(frame, 12); + this->flush(); } -void SoyosourceDisplay::publish_state_(sensor::Sensor *sensor, float value) { - if (sensor == nullptr) - return; +void SoyosourceDisplay::update_setting(uint8_t holding_register, float value) { + SoyosourceSettingsFrameT new_settings = this->current_settings_; - sensor->publish_state(value); -} + switch (holding_register) { + case 0x02: + new_settings.StartVoltage = (uint8_t) value; + break; + case 0x03: + new_settings.ShutdownVoltage = (uint8_t) value; + break; + case 0x04: + new_settings.OutputPowerLimit = (uint8_t)(value * 0.1); + break; + case 0x09: + new_settings.StartDelay = (uint8_t) value; + break; + default: + ESP_LOGE(TAG, "Unknown settings register. Aborting write settings"); + return; + } -void SoyosourceDisplay::publish_state_(text_sensor::TextSensor *text_sensor, const std::string &state) { - if (text_sensor == nullptr) + if (new_settings.OperationMode == 0x00) { + ESP_LOGE(TAG, "Cannot update settings because the current settings are unknown"); return; + } - text_sensor->publish_state(state); + this->write_settings_(&new_settings); } -void SoyosourceDisplay::send_command_(uint8_t function) { - uint8_t frame[12]; +void SoyosourceDisplay::write_settings_(SoyosourceSettingsFrameT *settings_frame) { + settings_frame->Header = SOF_REQUEST; + settings_frame->Function = WRITE_SETTINGS_COMMAND; + settings_frame->Checksum = chksum((const uint8_t *) settings_frame, 11); - frame[0] = 0x55; // Header - frame[1] = function; // Function (0x01: Status, 0x03: Settings, 0x11: Reboot, 0x0B: Write settings) - frame[2] = 0x00; // Start voltage (0x30: 48 V) - frame[3] = 0x00; // Stop voltage (0x2D: 45 V) - frame[4] = 0x00; // Constant Power / Power Limit (0x5A: 90 * 100 = 900 W) - frame[5] = 0x00; // Grid frequency (0x64: 100 * 0.5 = 50 Hz) - frame[6] = 0x00; - frame[7] = 0x00; - frame[8] = 0x00; - frame[9] = 0x00; // Start delay in seconds (0x06: 6 seconds) - frame[10] = 0x00; // Operation mode (0x01: PV, 0x11: PV Limit, 0x02: Bat Const, 0x12: Bat Limit) - frame[11] = chksum(frame, 11); - - this->write_array(frame, 12); + this->write_array((const uint8_t *) settings_frame, 12); this->flush(); } @@ -319,6 +356,25 @@ std::string SoyosourceDisplay::operation_mode_to_string_(const uint8_t &operatio return "Unknown"; } +uint8_t SoyosourceDisplay::operation_mode_to_operation_mode_setting_(const uint8_t &operation_mode) { + switch (operation_mode) { + case 0x01: + case 0x05: + return 0x02; + case 0x02: + case 0x06: + return 0x01; + case 0x09: + case 0x0D: + return 0x12; + case 0x0A: + case 0x0E: + return 0x11; + } + + return 0x00; +} + std::string SoyosourceDisplay::device_type_to_string_(const uint8_t &device_type) { switch (device_type) { case 0x18: @@ -357,5 +413,33 @@ std::string SoyosourceDisplay::error_bits_to_string_(const uint8_t &error_bits) return errors_list; } +void SoyosourceDisplay::publish_state_(binary_sensor::BinarySensor *binary_sensor, const bool &state) { + if (binary_sensor == nullptr) + return; + + binary_sensor->publish_state(state); +} + +void SoyosourceDisplay::publish_state_(number::Number *number, float value) { + if (number == nullptr) + return; + + number->publish_state(value); +} + +void SoyosourceDisplay::publish_state_(sensor::Sensor *sensor, float value) { + if (sensor == nullptr) + return; + + sensor->publish_state(value); +} + +void SoyosourceDisplay::publish_state_(text_sensor::TextSensor *text_sensor, const std::string &state) { + if (text_sensor == nullptr) + return; + + text_sensor->publish_state(state); +} + } // namespace soyosource_display } // namespace esphome diff --git a/components/soyosource_display/soyosource_display.h b/components/soyosource_display/soyosource_display.h index 97acb8a..927ab9a 100644 --- a/components/soyosource_display/soyosource_display.h +++ b/components/soyosource_display/soyosource_display.h @@ -2,6 +2,7 @@ #include "esphome/core/component.h" #include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/number/number.h" #include "esphome/components/sensor/sensor.h" #include "esphome/components/text_sensor/text_sensor.h" #include "esphome/components/uart/uart.h" @@ -9,6 +10,19 @@ namespace esphome { namespace soyosource_display { +struct SoyosourceSettingsFrameT { + uint8_t Header; + uint8_t Function; + uint8_t StartVoltage; + uint8_t ShutdownVoltage; + uint8_t OutputPowerLimit; + uint8_t GridFrequency; + uint8_t Unused[3]; + uint8_t StartDelay; + uint8_t OperationMode; + uint8_t Checksum; +}; + class SoyosourceDisplay : public uart::UARTDevice, public PollingComponent { public: void set_fan_running_binary_sensor(binary_sensor::BinarySensor *fan_running_binary_sensor) { @@ -18,6 +32,15 @@ class SoyosourceDisplay : public uart::UARTDevice, public PollingComponent { limiter_connected_binary_sensor_ = limiter_connected_binary_sensor; } + void set_start_voltage_number(number::Number *start_voltage_number) { start_voltage_number_ = start_voltage_number; } + void set_shutdown_voltage_number(number::Number *shutdown_voltage_number) { + shutdown_voltage_number_ = shutdown_voltage_number; + } + void set_output_power_limit_number(number::Number *output_power_limit_number) { + output_power_limit_number_ = output_power_limit_number; + } + void set_start_delay_number(number::Number *start_delay_number) { start_delay_number_ = start_delay_number; } + 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; @@ -44,6 +67,11 @@ class SoyosourceDisplay : public uart::UARTDevice, public PollingComponent { operation_status_text_sensor_ = operation_status_text_sensor; } + SoyosourceSettingsFrameT get_current_settings() { return current_settings_; } + 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); + void update_setting(uint8_t holding_register, float value); void loop() override; void dump_config() override; void update() override; @@ -53,6 +81,11 @@ class SoyosourceDisplay : public uart::UARTDevice, public PollingComponent { binary_sensor::BinarySensor *fan_running_binary_sensor_; binary_sensor::BinarySensor *limiter_connected_binary_sensor_; + number::Number *start_voltage_number_; + number::Number *shutdown_voltage_number_; + number::Number *output_power_limit_number_; + number::Number *start_delay_number_; + sensor::Sensor *error_bitmask_sensor_; sensor::Sensor *operation_mode_id_sensor_; sensor::Sensor *operation_status_id_sensor_; @@ -70,15 +103,19 @@ class SoyosourceDisplay : public uart::UARTDevice, public PollingComponent { std::vector rx_buffer_; uint32_t last_byte_{0}; uint32_t last_send_{0}; + SoyosourceSettingsFrameT current_settings_; void on_soyosource_display_data_(const uint8_t &function, const std::vector &data); void on_status_data_(const std::vector &data); void on_settings_data_(const std::vector &data); 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_(sensor::Sensor *sensor, float value); void publish_state_(text_sensor::TextSensor *text_sensor, const std::string &state); - void send_command_(uint8_t function); + void write_settings_(SoyosourceSettingsFrameT *frame); + + uint8_t operation_mode_to_operation_mode_setting_(const uint8_t &operation_mode); std::string operation_mode_to_string_(const uint8_t &operation_mode); std::string operation_status_to_string_(const uint8_t &operation_status); diff --git a/esp8266-display-example.yaml b/esp8266-display-example.yaml index 0fb7b80..5f020ad 100644 --- a/esp8266-display-example.yaml +++ b/esp8266-display-example.yaml @@ -17,19 +17,49 @@ wifi: ota: api: - logger: - baud_rate: 0 + level: DEBUG + logs: + sensor: INFO + text_sensor: INFO + soyosource_virtual_meter: INFO # Please be careful: The display port has a logic level of 5V. uart: - baud_rate: 9600 - tx_pin: GPIO1 - rx_pin: GPIO3 - debug: - direction: BOTH + - id: uart0 + baud_rate: 9600 + tx_pin: GPIO14 + rx_pin: GPIO12 + debug: + direction: BOTH + + - id: uart1 + baud_rate: 4800 + tx_pin: GPIO4 + rx_pin: GPIO5 + +soyosource_modbus: + - id: modbus0 + uart_id: uart1 + +soyosource_virtual_meter: + - id: virtualmeter0 + soyosource_modbus_id: modbus0 + update_interval: 3s + + # the state of this sensor (instantaneous power in watt) is used as source + power_id: powermeter0 + power_sensor_inactivity_timeout: 20s + power_demand_calculation: NEGATIVE_MEASUREMENTS_REQUIRED + min_power_demand: 0 + max_power_demand: 900 + # A positive buffer value (10) tries to avoid exporting power to the grid (demand - 10 watts) + # A negative buffer value (-10) exports power to the grid (demand + 10 watts) + buffer: 10 soyosource_display: + uart_id: uart0 + update_interval: 5s binary_sensor: - platform: soyosource_display @@ -38,7 +68,31 @@ binary_sensor: limiter_connected: name: "${name} limiter connected" +button: + - platform: soyosource_display + restart: + name: "${name} restart" + +number: + - platform: soyosource_display + start_voltage: + name: "${name} start voltage" + shutdown_voltage: + name: "${name} shutdown voltage" + # Maximum output power in limiter mode / Output power in constant power mode + output_power_limit: + name: "${name} output power limit" + start_delay: + name: "${name} start delay" + sensor: + - platform: template + name: "dummy powermeter" + id: powermeter0 + update_interval: 2s + lambda: |- + return 80.0; + - platform: soyosource_display error_bitmask: name: "${name} error bitmask"