Feature pipsolar anh (#1664)

Co-authored-by: Andreas Hergert <andreas.hergert@otrs.com>
Co-authored-by: Otto Winter <otto@otto-winter.com>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
Andreas Hergert 2021-08-10 21:48:32 +02:00 committed by GitHub
parent 6144ce1fe0
commit d3375193a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 2132 additions and 0 deletions

View file

@ -87,6 +87,7 @@ esphome/components/number/* @esphome/core
esphome/components/ota/* @esphome/core
esphome/components/output/* @esphome/core
esphome/components/pid/* @OttoWinter
esphome/components/pipsolar/* @andreashergert1984
esphome/components/pmsa003i/* @sjtrny
esphome/components/pn532/* @OttoWinter @jesserockz
esphome/components/pn532_i2c/* @OttoWinter @jesserockz

View file

@ -0,0 +1,32 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.const import CONF_ID
from esphome.components import uart
DEPENDENCIES = ["uart"]
CODEOWNERS = ["@andreashergert1984"]
AUTO_LOAD = ["binary_sensor", "text_sensor", "sensor", "switch", "output"]
MULTI_CONF = True
CONF_PIPSOLAR_ID = "pipsolar_id"
pipsolar_ns = cg.esphome_ns.namespace("pipsolar")
PipsolarComponent = pipsolar_ns.class_("Pipsolar", cg.Component)
PIPSOLAR_COMPONENT_SCHEMA = cv.COMPONENT_SCHEMA.extend(
{
cv.Required(CONF_PIPSOLAR_ID): cv.use_id(PipsolarComponent),
}
)
CONFIG_SCHEMA = cv.All(
cv.Schema({cv.GenerateID(): cv.declare_id(PipsolarComponent)})
.extend(cv.polling_component_schema("1s"))
.extend(uart.UART_DEVICE_SCHEMA)
)
def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
yield cg.register_component(var, config)
yield uart.register_uart_device(var, config)

View file

@ -0,0 +1,144 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import (
CONF_ID,
)
from .. import PIPSOLAR_COMPONENT_SCHEMA, CONF_PIPSOLAR_ID
DEPENDENCIES = ["uart"]
CONF_ADD_SBU_PRIORITY_VERSION = "add_sbu_priority_version"
CONF_CONFIGURATION_STATUS = "configuration_status"
CONF_SCC_FIRMWARE_VERSION = "scc_firmware_version"
CONF_LOAD_STATUS = "load_status"
CONF_BATTERY_VOLTAGE_TO_STEADY_WHILE_CHARGING = (
"battery_voltage_to_steady_while_charging"
)
CONF_CHARGING_STATUS = "charging_status"
CONF_SCC_CHARGING_STATUS = "scc_charging_status"
CONF_AC_CHARGING_STATUS = "ac_charging_status"
CONF_CHARGING_TO_FLOATING_MODE = "charging_to_floating_mode"
CONF_SWITCH_ON = "switch_on"
CONF_DUSTPROOF_INSTALLED = "dustproof_installed"
CONF_SILENCE_BUZZER_OPEN_BUZZER = "silence_buzzer_open_buzzer"
CONF_OVERLOAD_BYPASS_FUNCTION = "overload_bypass_function"
CONF_LCD_ESCAPE_TO_DEFAULT = "lcd_escape_to_default"
CONF_OVERLOAD_RESTART_FUNCTION = "overload_restart_function"
CONF_OVER_TEMPERATURE_RESTART_FUNCTION = "over_temperature_restart_function"
CONF_BACKLIGHT_ON = "backlight_on"
CONF_ALARM_ON_WHEN_PRIMARY_SOURCE_INTERRUPT = "alarm_on_when_primary_source_interrupt"
CONF_FAULT_CODE_RECORD = "fault_code_record"
CONF_POWER_SAVING = "power_saving"
CONF_WARNINGS_PRESENT = "warnings_present"
CONF_FAULTS_PRESENT = "faults_present"
CONF_WARNING_POWER_LOSS = "warning_power_loss"
CONF_FAULT_INVERTER_FAULT = "fault_inverter_fault"
CONF_FAULT_BUS_OVER = "fault_bus_over"
CONF_FAULT_BUS_UNDER = "fault_bus_under"
CONF_FAULT_BUS_SOFT_FAIL = "fault_bus_soft_fail"
CONF_WARNING_LINE_FAIL = "warning_line_fail"
CONF_FAULT_OPVSHORT = "fault_opvshort"
CONF_FAULT_INVERTER_VOLTAGE_TOO_LOW = "fault_inverter_voltage_too_low"
CONF_FAULT_INVERTER_VOLTAGE_TOO_HIGH = "fault_inverter_voltage_too_high"
CONF_WARNING_OVER_TEMPERATURE = "warning_over_temperature"
CONF_WARNING_FAN_LOCK = "warning_fan_lock"
CONF_WARNING_BATTERY_VOLTAGE_HIGH = "warning_battery_voltage_high"
CONF_WARNING_BATTERY_LOW_ALARM = "warning_battery_low_alarm"
CONF_WARNING_BATTERY_UNDER_SHUTDOWN = "warning_battery_under_shutdown"
CONF_WARNING_BATTERY_DERATING = "warning_battery_derating"
CONF_WARNING_OVER_LOAD = "warning_over_load"
CONF_WARNING_EEPROM_FAILED = "warning_eeprom_failed"
CONF_FAULT_INVERTER_OVER_CURRENT = "fault_inverter_over_current"
CONF_FAULT_INVERTER_SOFT_FAILED = "fault_inverter_soft_failed"
CONF_FAULT_SELF_TEST_FAILED = "fault_self_test_failed"
CONF_FAULT_OP_DC_VOLTAGE_OVER = "fault_op_dc_voltage_over"
CONF_FAULT_BATTERY_OPEN = "fault_battery_open"
CONF_FAULT_CURRENT_SENSOR_FAILED = "fault_current_sensor_failed"
CONF_FAULT_BATTERY_SHORT = "fault_battery_short"
CONF_WARNING_POWER_LIMIT = "warning_power_limit"
CONF_WARNING_PV_VOLTAGE_HIGH = "warning_pv_voltage_high"
CONF_FAULT_MPPT_OVERLOAD = "fault_mppt_overload"
CONF_WARNING_MPPT_OVERLOAD = "warning_mppt_overload"
CONF_WARNING_BATTERY_TOO_LOW_TO_CHARGE = "warning_battery_too_low_to_charge"
CONF_FAULT_DC_DC_OVER_CURRENT = "fault_dc_dc_over_current"
CONF_FAULT_CODE = "fault_code"
CONF_WARNUNG_LOW_PV_ENERGY = "warnung_low_pv_energy"
CONF_WARNING_HIGH_AC_INPUT_DURING_BUS_SOFT_START = (
"warning_high_ac_input_during_bus_soft_start"
)
CONF_WARNING_BATTERY_EQUALIZATION = "warning_battery_equalization"
TYPES = [
CONF_ADD_SBU_PRIORITY_VERSION,
CONF_CONFIGURATION_STATUS,
CONF_SCC_FIRMWARE_VERSION,
CONF_LOAD_STATUS,
CONF_BATTERY_VOLTAGE_TO_STEADY_WHILE_CHARGING,
CONF_CHARGING_STATUS,
CONF_SCC_CHARGING_STATUS,
CONF_AC_CHARGING_STATUS,
CONF_CHARGING_TO_FLOATING_MODE,
CONF_SWITCH_ON,
CONF_DUSTPROOF_INSTALLED,
CONF_SILENCE_BUZZER_OPEN_BUZZER,
CONF_OVERLOAD_BYPASS_FUNCTION,
CONF_LCD_ESCAPE_TO_DEFAULT,
CONF_OVERLOAD_RESTART_FUNCTION,
CONF_OVER_TEMPERATURE_RESTART_FUNCTION,
CONF_BACKLIGHT_ON,
CONF_ALARM_ON_WHEN_PRIMARY_SOURCE_INTERRUPT,
CONF_FAULT_CODE_RECORD,
CONF_POWER_SAVING,
CONF_WARNINGS_PRESENT,
CONF_FAULTS_PRESENT,
CONF_WARNING_POWER_LOSS,
CONF_FAULT_INVERTER_FAULT,
CONF_FAULT_BUS_OVER,
CONF_FAULT_BUS_UNDER,
CONF_FAULT_BUS_SOFT_FAIL,
CONF_WARNING_LINE_FAIL,
CONF_FAULT_OPVSHORT,
CONF_FAULT_INVERTER_VOLTAGE_TOO_LOW,
CONF_FAULT_INVERTER_VOLTAGE_TOO_HIGH,
CONF_WARNING_OVER_TEMPERATURE,
CONF_WARNING_FAN_LOCK,
CONF_WARNING_BATTERY_VOLTAGE_HIGH,
CONF_WARNING_BATTERY_LOW_ALARM,
CONF_WARNING_BATTERY_UNDER_SHUTDOWN,
CONF_WARNING_BATTERY_DERATING,
CONF_WARNING_OVER_LOAD,
CONF_WARNING_EEPROM_FAILED,
CONF_FAULT_INVERTER_OVER_CURRENT,
CONF_FAULT_INVERTER_SOFT_FAILED,
CONF_FAULT_SELF_TEST_FAILED,
CONF_FAULT_OP_DC_VOLTAGE_OVER,
CONF_FAULT_BATTERY_OPEN,
CONF_FAULT_CURRENT_SENSOR_FAILED,
CONF_FAULT_BATTERY_SHORT,
CONF_WARNING_POWER_LIMIT,
CONF_WARNING_PV_VOLTAGE_HIGH,
CONF_FAULT_MPPT_OVERLOAD,
CONF_WARNING_MPPT_OVERLOAD,
CONF_WARNING_BATTERY_TOO_LOW_TO_CHARGE,
CONF_FAULT_DC_DC_OVER_CURRENT,
CONF_FAULT_CODE,
CONF_WARNUNG_LOW_PV_ENERGY,
CONF_WARNING_HIGH_AC_INPUT_DURING_BUS_SOFT_START,
CONF_WARNING_BATTERY_EQUALIZATION,
]
CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend(
{cv.Optional(type): binary_sensor.BINARY_SENSOR_SCHEMA for type in TYPES}
)
async def to_code(config):
paren = await cg.get_variable(config[CONF_PIPSOLAR_ID])
for type in TYPES:
if type in config:
conf = config[type]
sens = cg.new_Pvariable(conf[CONF_ID])
await binary_sensor.register_binary_sensor(sens, conf)
cg.add(getattr(paren, f"set_{type}")(sens))

View file

@ -0,0 +1,106 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import output
from esphome.const import CONF_ID, CONF_VALUE
from .. import PIPSOLAR_COMPONENT_SCHEMA, CONF_PIPSOLAR_ID, pipsolar_ns
DEPENDENCIES = ["pipsolar"]
PipsolarOutput = pipsolar_ns.class_("PipsolarOutput", output.FloatOutput)
SetOutputAction = pipsolar_ns.class_("SetOutputAction", automation.Action)
CONF_POSSIBLE_VALUES = "possible_values"
# 3.11 PCVV<nn.n><cr>: Setting battery C.V. (constant voltage) charging voltage 48.0V ~ 58.4V for 48V unit
# battery_bulk_voltage;
# battery_recharge_voltage; 12V unit: 11V/11.3V/11.5V/11.8V/12V/12.3V/12.5V/12.8V
# 24V unit: 22V/22.5V/23V/23.5V/24V/24.5V/25V/25.5V
# 48V unit: 44V/45V/46V/47V/48V/49V/50V/51V
# battery_under_voltage; 40.0V ~ 48.0V for 48V unit
# battery_float_voltage; 48.0V ~ 58.4V for 48V unit
# battery_type; 00 for AGM, 01 for Flooded battery
# current_max_ac_charging_current;
# output_source_priority; 00 / 01 / 02
# charger_source_priority; For HS: 00 for utility first, 01 for solar first, 02 for solar and utility, 03 for only solar charging
# For MS/MSX: 00 for utility first, 01 for solar first, 03 for only solar charging
# battery_redischarge_voltage; 12V unit: 00.0V12V/12.3V/12.5V/12.8V/13V/13.3V/13.5V/13.8V/14V/14.3V/14.5
# 24V unit: 00.0V/24V/24.5V/25V/25.5V/26V/26.5V/27V/27.5V/28V/28.5V/29V
# 48V unit: 00.0V48V/49V/50V/51V/52V/53V/54V/55V/56V/57V/58V
CONF_BATTERY_RECHARGE_VOLTAGE = "battery_recharge_voltage"
CONF_BATTERY_UNDER_VOLTAGE = "battery_under_voltage"
CONF_BATTERY_FLOAT_VOLTAGE = "battery_float_voltage"
CONF_BATTERY_TYPE = "battery_type"
CONF_CURRENT_MAX_AC_CHARGING_CURRENT = "current_max_ac_charging_current"
CONF_CURRENT_MAX_CHARGING_CURRENT = "current_max_charging_current"
CONF_OUTPUT_SOURCE_PRIORITY = "output_source_priority"
CONF_CHARGER_SOURCE_PRIORITY = "charger_source_priority"
CONF_BATTERY_REDISCHARGE_VOLTAGE = "battery_redischarge_voltage"
TYPES = {
CONF_BATTERY_RECHARGE_VOLTAGE: (
[44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0, 51.0],
"PBCV%02.1f",
),
CONF_BATTERY_UNDER_VOLTAGE: (
[40.0, 40.1, 42, 43, 44, 45, 46, 47, 48.0],
"PSDV%02.1f",
),
CONF_BATTERY_FLOAT_VOLTAGE: ([48.0, 49.0, 50.0, 51.0], "PBFT%02.1f"),
CONF_BATTERY_TYPE: ([0, 1, 2], "PBT%02.0f"),
CONF_CURRENT_MAX_AC_CHARGING_CURRENT: ([2, 10, 20], "MUCHGC0%02.0f"),
CONF_CURRENT_MAX_CHARGING_CURRENT: ([10, 20, 30, 40], "MCHGC0%02.0f"),
CONF_OUTPUT_SOURCE_PRIORITY: ([0, 1, 2], "POP%02.0f"),
CONF_CHARGER_SOURCE_PRIORITY: ([0, 1, 2, 3], "PCP%02.0f"),
CONF_BATTERY_REDISCHARGE_VOLTAGE: (
[0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58],
"PBDV%02.1f",
),
}
CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend(
{
cv.Optional(type): output.FLOAT_OUTPUT_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(PipsolarOutput),
cv.Optional(CONF_POSSIBLE_VALUES, default=values): cv.All(
cv.ensure_list(cv.positive_float), cv.Length(min=1)
),
}
)
for type, (values, _) in TYPES.items()
}
)
async def to_code(config):
paren = await cg.get_variable(config[CONF_PIPSOLAR_ID])
for type, (_, command) in TYPES.items():
if type in config:
conf = config[type]
var = cg.new_Pvariable(conf[CONF_ID])
await output.register_output(var, conf)
cg.add(var.set_parent(paren))
cg.add(var.set_set_command(command))
if (CONF_POSSIBLE_VALUES) in conf:
cg.add(var.set_possible_values(conf[CONF_POSSIBLE_VALUES]))
@automation.register_action(
"output.pipsolar.set_level",
SetOutputAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(CONF_ID),
cv.Required(CONF_VALUE): cv.templatable(cv.positive_float),
}
),
)
def output_pipsolar_set_level_to_code(config, action_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
template_ = yield cg.templatable(config[CONF_VALUE], args, float)
cg.add(var.set_level(template_))
yield var

View file

@ -0,0 +1,22 @@
#include "pipsolar_output.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace pipsolar {
static const char *const TAG = "pipsolar.output";
void PipsolarOutput::write_state(float state) {
char tmp[10];
sprintf(tmp, this->set_command_.c_str(), state);
if (std::find(this->possible_values_.begin(), this->possible_values_.end(), state) != this->possible_values_.end()) {
ESP_LOGD(TAG, "Will write: %s out of value %f / %02.0f", tmp, state, state);
this->parent_->switch_command(std::string(tmp));
} else {
ESP_LOGD(TAG, "Will not write: %s as it is not in list of allowed values", tmp);
}
}
} // namespace pipsolar
} // namespace esphome

View file

@ -0,0 +1,40 @@
#pragma once
#include "../pipsolar.h"
#include "esphome/components/output/float_output.h"
#include "esphome/core/component.h"
namespace esphome {
namespace pipsolar {
class Pipsolar;
class PipsolarOutput : public output::FloatOutput {
public:
PipsolarOutput() {}
void set_parent(Pipsolar *parent) { this->parent_ = parent; }
void set_set_command(std::string command) { this->set_command_ = std::move(command); };
void set_possible_values(std::vector<float> possible_values) { this->possible_values_ = std::move(possible_values); }
void set_value(float value) { this->write_state(value); };
protected:
void write_state(float state) override;
std::string set_command_;
Pipsolar *parent_;
std::vector<float> possible_values_;
};
template<typename... Ts> class SetOutputAction : public Action<Ts...> {
public:
SetOutputAction(PipsolarOutput *output) : output_(output) {}
TEMPLATABLE_VALUE(float, level)
void play(Ts... x) override { this->output_->set_value(this->level_.value(x...)); }
protected:
PipsolarOutput *output_;
};
} // namespace pipsolar
} // namespace esphome

View file

@ -0,0 +1,922 @@
#include "pipsolar.h"
#include "esphome/core/log.h"
namespace esphome {
namespace pipsolar {
static const char *const TAG = "pipsolar";
void Pipsolar::setup() {
this->state_ = STATE_IDLE;
this->command_start_millis_ = 0;
}
void Pipsolar::empty_uart_buffer_() {
uint8_t byte;
while (this->available()) {
this->read_byte(&byte);
}
}
void Pipsolar::loop() {
// Read message
if (this->state_ == STATE_IDLE) {
this->empty_uart_buffer_();
switch (this->send_next_command_()) {
case 0:
// no command send (empty queue) time to poll
if (millis() - this->last_poll_ > this->update_interval_) {
this->send_next_poll_();
this->last_poll_ = millis();
}
return;
break;
case 1:
// command send
return;
break;
}
}
if (this->state_ == STATE_COMMAND_COMPLETE) {
if (this->check_incoming_length_(4)) {
ESP_LOGD(TAG, "response length for command OK");
if (this->check_incoming_crc_()) {
// crc ok
if (this->read_buffer_[1] == 'A' && this->read_buffer_[2] == 'C' && this->read_buffer_[3] == 'K') {
ESP_LOGD(TAG, "command successful");
} else {
ESP_LOGD(TAG, "command not successful");
}
this->command_queue_[this->command_queue_position_] = std::string("");
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
this->state_ = STATE_IDLE;
} else {
// crc failed
this->command_queue_[this->command_queue_position_] = std::string("");
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
this->state_ = STATE_IDLE;
}
} else {
ESP_LOGD(TAG, "response length for command %s not OK: with length %zu",
this->command_queue_[this->command_queue_position_].c_str(), this->read_pos_);
this->command_queue_[this->command_queue_position_] = std::string("");
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
this->state_ = STATE_IDLE;
}
}
if (this->state_ == STATE_POLL_DECODED) {
std::string mode;
switch (this->used_polling_commands_[this->last_polling_command_].identifier) {
case POLLING_QPIRI:
if (this->grid_rating_voltage_) {
this->grid_rating_voltage_->publish_state(value_grid_rating_voltage_);
}
if (this->grid_rating_current_) {
this->grid_rating_current_->publish_state(value_grid_rating_current_);
}
if (this->ac_output_rating_voltage_) {
this->ac_output_rating_voltage_->publish_state(value_ac_output_rating_voltage_);
}
if (this->ac_output_rating_frequency_) {
this->ac_output_rating_frequency_->publish_state(value_ac_output_rating_frequency_);
}
if (this->ac_output_rating_current_) {
this->ac_output_rating_current_->publish_state(value_ac_output_rating_current_);
}
if (this->ac_output_rating_apparent_power_) {
this->ac_output_rating_apparent_power_->publish_state(value_ac_output_rating_apparent_power_);
}
if (this->ac_output_rating_active_power_) {
this->ac_output_rating_active_power_->publish_state(value_ac_output_rating_active_power_);
}
if (this->battery_rating_voltage_) {
this->battery_rating_voltage_->publish_state(value_battery_rating_voltage_);
}
if (this->battery_recharge_voltage_) {
this->battery_recharge_voltage_->publish_state(value_battery_recharge_voltage_);
}
if (this->battery_under_voltage_) {
this->battery_under_voltage_->publish_state(value_battery_under_voltage_);
}
if (this->battery_bulk_voltage_) {
this->battery_bulk_voltage_->publish_state(value_battery_bulk_voltage_);
}
if (this->battery_float_voltage_) {
this->battery_float_voltage_->publish_state(value_battery_float_voltage_);
}
if (this->battery_type_) {
this->battery_type_->publish_state(value_battery_type_);
}
if (this->current_max_ac_charging_current_) {
this->current_max_ac_charging_current_->publish_state(value_current_max_ac_charging_current_);
}
if (this->current_max_charging_current_) {
this->current_max_charging_current_->publish_state(value_current_max_charging_current_);
}
if (this->input_voltage_range_) {
this->input_voltage_range_->publish_state(value_input_voltage_range_);
}
// special for input voltage range switch
if (this->input_voltage_range_switch_) {
this->input_voltage_range_switch_->publish_state(value_input_voltage_range_ == 1);
}
if (this->output_source_priority_) {
this->output_source_priority_->publish_state(value_output_source_priority_);
}
// special for output source priority switches
if (this->output_source_priority_utility_switch_) {
this->output_source_priority_utility_switch_->publish_state(value_output_source_priority_ == 0);
}
if (this->output_source_priority_solar_switch_) {
this->output_source_priority_solar_switch_->publish_state(value_output_source_priority_ == 1);
}
if (this->output_source_priority_battery_switch_) {
this->output_source_priority_battery_switch_->publish_state(value_output_source_priority_ == 2);
}
if (this->charger_source_priority_) {
this->charger_source_priority_->publish_state(value_charger_source_priority_);
}
if (this->parallel_max_num_) {
this->parallel_max_num_->publish_state(value_parallel_max_num_);
}
if (this->machine_type_) {
this->machine_type_->publish_state(value_machine_type_);
}
if (this->topology_) {
this->topology_->publish_state(value_topology_);
}
if (this->output_mode_) {
this->output_mode_->publish_state(value_output_mode_);
}
if (this->battery_redischarge_voltage_) {
this->battery_redischarge_voltage_->publish_state(value_battery_redischarge_voltage_);
}
if (this->pv_ok_condition_for_parallel_) {
this->pv_ok_condition_for_parallel_->publish_state(value_pv_ok_condition_for_parallel_);
}
// special for pv ok condition switch
if (this->pv_ok_condition_for_parallel_switch_) {
this->pv_ok_condition_for_parallel_switch_->publish_state(value_pv_ok_condition_for_parallel_ == 1);
}
if (this->pv_power_balance_) {
this->pv_power_balance_->publish_state(value_pv_power_balance_ == 1);
}
// special for power balance switch
if (this->pv_power_balance_switch_) {
this->pv_power_balance_switch_->publish_state(value_pv_power_balance_ == 1);
}
this->state_ = STATE_IDLE;
break;
case POLLING_QPIGS:
if (this->grid_voltage_) {
this->grid_voltage_->publish_state(value_grid_voltage_);
}
if (this->grid_frequency_) {
this->grid_frequency_->publish_state(value_grid_frequency_);
}
if (this->ac_output_voltage_) {
this->ac_output_voltage_->publish_state(value_ac_output_voltage_);
}
if (this->ac_output_frequency_) {
this->ac_output_frequency_->publish_state(value_ac_output_frequency_);
}
if (this->ac_output_apparent_power_) {
this->ac_output_apparent_power_->publish_state(value_ac_output_apparent_power_);
}
if (this->ac_output_active_power_) {
this->ac_output_active_power_->publish_state(value_ac_output_active_power_);
}
if (this->output_load_percent_) {
this->output_load_percent_->publish_state(value_output_load_percent_);
}
if (this->bus_voltage_) {
this->bus_voltage_->publish_state(value_bus_voltage_);
}
if (this->battery_voltage_) {
this->battery_voltage_->publish_state(value_battery_voltage_);
}
if (this->battery_charging_current_) {
this->battery_charging_current_->publish_state(value_battery_charging_current_);
}
if (this->battery_capacity_percent_) {
this->battery_capacity_percent_->publish_state(value_battery_capacity_percent_);
}
if (this->inverter_heat_sink_temperature_) {
this->inverter_heat_sink_temperature_->publish_state(value_inverter_heat_sink_temperature_);
}
if (this->pv_input_current_for_battery_) {
this->pv_input_current_for_battery_->publish_state(value_pv_input_current_for_battery_);
}
if (this->pv_input_voltage_) {
this->pv_input_voltage_->publish_state(value_pv_input_voltage_);
}
if (this->battery_voltage_scc_) {
this->battery_voltage_scc_->publish_state(value_battery_voltage_scc_);
}
if (this->battery_discharge_current_) {
this->battery_discharge_current_->publish_state(value_battery_discharge_current_);
}
if (this->add_sbu_priority_version_) {
this->add_sbu_priority_version_->publish_state(value_add_sbu_priority_version_);
}
if (this->configuration_status_) {
this->configuration_status_->publish_state(value_configuration_status_);
}
if (this->scc_firmware_version_) {
this->scc_firmware_version_->publish_state(value_scc_firmware_version_);
}
if (this->load_status_) {
this->load_status_->publish_state(value_load_status_);
}
if (this->battery_voltage_to_steady_while_charging_) {
this->battery_voltage_to_steady_while_charging_->publish_state(
value_battery_voltage_to_steady_while_charging_);
}
if (this->charging_status_) {
this->charging_status_->publish_state(value_charging_status_);
}
if (this->scc_charging_status_) {
this->scc_charging_status_->publish_state(value_scc_charging_status_);
}
if (this->ac_charging_status_) {
this->ac_charging_status_->publish_state(value_ac_charging_status_);
}
if (this->battery_voltage_offset_for_fans_on_) {
this->battery_voltage_offset_for_fans_on_->publish_state(value_battery_voltage_offset_for_fans_on_ / 10.0f);
} //.1 scale
if (this->eeprom_version_) {
this->eeprom_version_->publish_state(value_eeprom_version_);
}
if (this->pv_charging_power_) {
this->pv_charging_power_->publish_state(value_pv_charging_power_);
}
if (this->charging_to_floating_mode_) {
this->charging_to_floating_mode_->publish_state(value_charging_to_floating_mode_);
}
if (this->switch_on_) {
this->switch_on_->publish_state(value_switch_on_);
}
if (this->dustproof_installed_) {
this->dustproof_installed_->publish_state(value_dustproof_installed_);
}
this->state_ = STATE_IDLE;
break;
case POLLING_QMOD:
if (this->device_mode_) {
mode = value_device_mode_;
this->device_mode_->publish_state(mode);
}
this->state_ = STATE_IDLE;
break;
case POLLING_QFLAG:
if (this->silence_buzzer_open_buzzer_) {
this->silence_buzzer_open_buzzer_->publish_state(value_silence_buzzer_open_buzzer_);
}
if (this->overload_bypass_function_) {
this->overload_bypass_function_->publish_state(value_overload_bypass_function_);
}
if (this->lcd_escape_to_default_) {
this->lcd_escape_to_default_->publish_state(value_lcd_escape_to_default_);
}
if (this->overload_restart_function_) {
this->overload_restart_function_->publish_state(value_overload_restart_function_);
}
if (this->over_temperature_restart_function_) {
this->over_temperature_restart_function_->publish_state(value_over_temperature_restart_function_);
}
if (this->backlight_on_) {
this->backlight_on_->publish_state(value_backlight_on_);
}
if (this->alarm_on_when_primary_source_interrupt_) {
this->alarm_on_when_primary_source_interrupt_->publish_state(value_alarm_on_when_primary_source_interrupt_);
}
if (this->fault_code_record_) {
this->fault_code_record_->publish_state(value_fault_code_record_);
}
if (this->power_saving_) {
this->power_saving_->publish_state(value_power_saving_);
}
this->state_ = STATE_IDLE;
break;
case POLLING_QPIWS:
if (this->warnings_present_) {
this->warnings_present_->publish_state(value_warnings_present_);
}
if (this->faults_present_) {
this->faults_present_->publish_state(value_faults_present_);
}
if (this->warning_power_loss_) {
this->warning_power_loss_->publish_state(value_warning_power_loss_);
}
if (this->fault_inverter_fault_) {
this->fault_inverter_fault_->publish_state(value_fault_inverter_fault_);
}
if (this->fault_bus_over_) {
this->fault_bus_over_->publish_state(value_fault_bus_over_);
}
if (this->fault_bus_under_) {
this->fault_bus_under_->publish_state(value_fault_bus_under_);
}
if (this->fault_bus_soft_fail_) {
this->fault_bus_soft_fail_->publish_state(value_fault_bus_soft_fail_);
}
if (this->warning_line_fail_) {
this->warning_line_fail_->publish_state(value_warning_line_fail_);
}
if (this->fault_opvshort_) {
this->fault_opvshort_->publish_state(value_fault_opvshort_);
}
if (this->fault_inverter_voltage_too_low_) {
this->fault_inverter_voltage_too_low_->publish_state(value_fault_inverter_voltage_too_low_);
}
if (this->fault_inverter_voltage_too_high_) {
this->fault_inverter_voltage_too_high_->publish_state(value_fault_inverter_voltage_too_high_);
}
if (this->warning_over_temperature_) {
this->warning_over_temperature_->publish_state(value_warning_over_temperature_);
}
if (this->warning_fan_lock_) {
this->warning_fan_lock_->publish_state(value_warning_fan_lock_);
}
if (this->warning_battery_voltage_high_) {
this->warning_battery_voltage_high_->publish_state(value_warning_battery_voltage_high_);
}
if (this->warning_battery_low_alarm_) {
this->warning_battery_low_alarm_->publish_state(value_warning_battery_low_alarm_);
}
if (this->warning_battery_under_shutdown_) {
this->warning_battery_under_shutdown_->publish_state(value_warning_battery_under_shutdown_);
}
if (this->warning_battery_derating_) {
this->warning_battery_derating_->publish_state(value_warning_battery_derating_);
}
if (this->warning_over_load_) {
this->warning_over_load_->publish_state(value_warning_over_load_);
}
if (this->warning_eeprom_failed_) {
this->warning_eeprom_failed_->publish_state(value_warning_eeprom_failed_);
}
if (this->fault_inverter_over_current_) {
this->fault_inverter_over_current_->publish_state(value_fault_inverter_over_current_);
}
if (this->fault_inverter_soft_failed_) {
this->fault_inverter_soft_failed_->publish_state(value_fault_inverter_soft_failed_);
}
if (this->fault_self_test_failed_) {
this->fault_self_test_failed_->publish_state(value_fault_self_test_failed_);
}
if (this->fault_op_dc_voltage_over_) {
this->fault_op_dc_voltage_over_->publish_state(value_fault_op_dc_voltage_over_);
}
if (this->fault_battery_open_) {
this->fault_battery_open_->publish_state(value_fault_battery_open_);
}
if (this->fault_current_sensor_failed_) {
this->fault_current_sensor_failed_->publish_state(value_fault_current_sensor_failed_);
}
if (this->fault_battery_short_) {
this->fault_battery_short_->publish_state(value_fault_battery_short_);
}
if (this->warning_power_limit_) {
this->warning_power_limit_->publish_state(value_warning_power_limit_);
}
if (this->warning_pv_voltage_high_) {
this->warning_pv_voltage_high_->publish_state(value_warning_pv_voltage_high_);
}
if (this->fault_mppt_overload_) {
this->fault_mppt_overload_->publish_state(value_fault_mppt_overload_);
}
if (this->warning_mppt_overload_) {
this->warning_mppt_overload_->publish_state(value_warning_mppt_overload_);
}
if (this->warning_battery_too_low_to_charge_) {
this->warning_battery_too_low_to_charge_->publish_state(value_warning_battery_too_low_to_charge_);
}
if (this->fault_dc_dc_over_current_) {
this->fault_dc_dc_over_current_->publish_state(value_fault_dc_dc_over_current_);
}
if (this->fault_code_) {
this->fault_code_->publish_state(value_fault_code_);
}
if (this->warnung_low_pv_energy_) {
this->warnung_low_pv_energy_->publish_state(value_warnung_low_pv_energy_);
}
if (this->warning_high_ac_input_during_bus_soft_start_) {
this->warning_high_ac_input_during_bus_soft_start_->publish_state(
value_warning_high_ac_input_during_bus_soft_start_);
}
if (this->warning_battery_equalization_) {
this->warning_battery_equalization_->publish_state(value_warning_battery_equalization_);
}
this->state_ = STATE_IDLE;
break;
case POLLING_QT:
this->state_ = STATE_IDLE;
break;
case POLLING_QMN:
this->state_ = STATE_IDLE;
break;
}
}
if (this->state_ == STATE_POLL_CHECKED) {
bool enabled = true;
std::string fc;
char tmp[PIPSOLAR_READ_BUFFER_LENGTH];
sprintf(tmp, "%s", this->read_buffer_);
switch (this->used_polling_commands_[this->last_polling_command_].identifier) {
case POLLING_QPIRI:
ESP_LOGD(TAG, "Decode QPIRI");
sscanf(tmp, "(%f %f %f %f %f %d %d %f %f %f %f %f %d %d %d %d %d %d %d %d %d %d %f %d %d", // NOLINT
&value_grid_rating_voltage_, &value_grid_rating_current_, &value_ac_output_rating_voltage_, // NOLINT
&value_ac_output_rating_frequency_, &value_ac_output_rating_current_, // NOLINT
&value_ac_output_rating_apparent_power_, &value_ac_output_rating_active_power_, // NOLINT
&value_battery_rating_voltage_, &value_battery_recharge_voltage_, // NOLINT
&value_battery_under_voltage_, &value_battery_bulk_voltage_, &value_battery_float_voltage_, // NOLINT
&value_battery_type_, &value_current_max_ac_charging_current_, // NOLINT
&value_current_max_charging_current_, &value_input_voltage_range_, // NOLINT
&value_output_source_priority_, &value_charger_source_priority_, &value_parallel_max_num_, // NOLINT
&value_machine_type_, &value_topology_, &value_output_mode_, // NOLINT
&value_battery_redischarge_voltage_, &value_pv_ok_condition_for_parallel_, // NOLINT
&value_pv_power_balance_); // NOLINT
if (this->last_qpiri_) {
this->last_qpiri_->publish_state(tmp);
}
this->state_ = STATE_POLL_DECODED;
break;
case POLLING_QPIGS:
ESP_LOGD(TAG, "Decode QPIGS");
sscanf( // NOLINT
tmp, // NOLINT
"(%f %f %f %f %d %d %d %d %f %d %d %d %d %f %f %d %1d%1d%1d%1d%1d%1d%1d%1d %d %d %d %1d%1d%1d", // NOLINT
&value_grid_voltage_, &value_grid_frequency_, &value_ac_output_voltage_, // NOLINT
&value_ac_output_frequency_, // NOLINT
&value_ac_output_apparent_power_, &value_ac_output_active_power_, &value_output_load_percent_, // NOLINT
&value_bus_voltage_, &value_battery_voltage_, &value_battery_charging_current_, // NOLINT
&value_battery_capacity_percent_, &value_inverter_heat_sink_temperature_, // NOLINT
&value_pv_input_current_for_battery_, &value_pv_input_voltage_, &value_battery_voltage_scc_, // NOLINT
&value_battery_discharge_current_, &value_add_sbu_priority_version_, // NOLINT
&value_configuration_status_, &value_scc_firmware_version_, &value_load_status_, // NOLINT
&value_battery_voltage_to_steady_while_charging_, &value_charging_status_, // NOLINT
&value_scc_charging_status_, &value_ac_charging_status_, // NOLINT
&value_battery_voltage_offset_for_fans_on_, &value_eeprom_version_, &value_pv_charging_power_, // NOLINT
&value_charging_to_floating_mode_, &value_switch_on_, // NOLINT
&value_dustproof_installed_); // NOLINT
if (this->last_qpigs_) {
this->last_qpigs_->publish_state(tmp);
}
this->state_ = STATE_POLL_DECODED;
break;
case POLLING_QMOD:
ESP_LOGD(TAG, "Decode QMOD");
this->value_device_mode_ = char(this->read_buffer_[1]);
if (this->last_qmod_) {
this->last_qmod_->publish_state(tmp);
}
this->state_ = STATE_POLL_DECODED;
break;
case POLLING_QFLAG:
ESP_LOGD(TAG, "Decode QFLAG");
// result like:"(EbkuvxzDajy"
// get through all char: ignore first "(" Enable flag on 'E', Disable on 'D') else set the corresponding value
for (int i = 1; i < strlen(tmp); i++) {
switch (tmp[i]) {
case 'E':
enabled = true;
break;
case 'D':
enabled = false;
break;
case 'a':
this->value_silence_buzzer_open_buzzer_ = enabled;
break;
case 'b':
this->value_overload_bypass_function_ = enabled;
break;
case 'k':
this->value_lcd_escape_to_default_ = enabled;
break;
case 'u':
this->value_overload_restart_function_ = enabled;
break;
case 'v':
this->value_over_temperature_restart_function_ = enabled;
break;
case 'x':
this->value_backlight_on_ = enabled;
break;
case 'y':
this->value_alarm_on_when_primary_source_interrupt_ = enabled;
break;
case 'z':
this->value_fault_code_record_ = enabled;
break;
case 'j':
this->value_power_saving_ = enabled;
break;
}
}
if (this->last_qflag_) {
this->last_qflag_->publish_state(tmp);
}
this->state_ = STATE_POLL_DECODED;
break;
case POLLING_QPIWS:
ESP_LOGD(TAG, "Decode QPIWS");
// '(00000000000000000000000000000000'
// iterate over all available flag (as not all models have all flags, but at least in the same order)
this->value_warnings_present_ = false;
this->value_faults_present_ = true;
for (int i = 1; i < strlen(tmp); i++) {
enabled = tmp[i] == '1';
switch (i) {
case 1:
this->value_warning_power_loss_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 2:
this->value_fault_inverter_fault_ = enabled;
this->value_faults_present_ += enabled;
break;
case 3:
this->value_fault_bus_over_ = enabled;
this->value_faults_present_ += enabled;
break;
case 4:
this->value_fault_bus_under_ = enabled;
this->value_faults_present_ += enabled;
break;
case 5:
this->value_fault_bus_soft_fail_ = enabled;
this->value_faults_present_ += enabled;
break;
case 6:
this->value_warning_line_fail_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 7:
this->value_fault_opvshort_ = enabled;
this->value_faults_present_ += enabled;
break;
case 8:
this->value_fault_inverter_voltage_too_low_ = enabled;
this->value_faults_present_ += enabled;
break;
case 9:
this->value_fault_inverter_voltage_too_high_ = enabled;
this->value_faults_present_ += enabled;
break;
case 10:
this->value_warning_over_temperature_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 11:
this->value_warning_fan_lock_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 12:
this->value_warning_battery_voltage_high_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 13:
this->value_warning_battery_low_alarm_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 15:
this->value_warning_battery_under_shutdown_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 16:
this->value_warning_battery_derating_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 17:
this->value_warning_over_load_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 18:
this->value_warning_eeprom_failed_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 19:
this->value_fault_inverter_over_current_ = enabled;
this->value_faults_present_ += enabled;
break;
case 20:
this->value_fault_inverter_soft_failed_ = enabled;
this->value_faults_present_ += enabled;
break;
case 21:
this->value_fault_self_test_failed_ = enabled;
this->value_faults_present_ += enabled;
break;
case 22:
this->value_fault_op_dc_voltage_over_ = enabled;
this->value_faults_present_ += enabled;
break;
case 23:
this->value_fault_battery_open_ = enabled;
this->value_faults_present_ += enabled;
break;
case 24:
this->value_fault_current_sensor_failed_ = enabled;
this->value_faults_present_ += enabled;
break;
case 25:
this->value_fault_battery_short_ = enabled;
this->value_faults_present_ += enabled;
break;
case 26:
this->value_warning_power_limit_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 27:
this->value_warning_pv_voltage_high_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 28:
this->value_fault_mppt_overload_ = enabled;
this->value_faults_present_ += enabled;
break;
case 29:
this->value_warning_mppt_overload_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 30:
this->value_warning_battery_too_low_to_charge_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 31:
this->value_fault_dc_dc_over_current_ = enabled;
this->value_faults_present_ += enabled;
break;
case 32:
fc = tmp[i];
fc += tmp[i + 1];
this->value_fault_code_ = strtol(fc.c_str(), nullptr, 10);
break;
case 34:
this->value_warnung_low_pv_energy_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 35:
this->value_warning_high_ac_input_during_bus_soft_start_ = enabled;
this->value_warnings_present_ += enabled;
break;
case 36:
this->value_warning_battery_equalization_ = enabled;
this->value_warnings_present_ += enabled;
break;
}
}
if (this->last_qpiws_) {
this->last_qpiws_->publish_state(tmp);
}
this->state_ = STATE_POLL_DECODED;
break;
case POLLING_QT:
ESP_LOGD(TAG, "Decode QT");
if (this->last_qt_) {
this->last_qt_->publish_state(tmp);
}
this->state_ = STATE_POLL_DECODED;
break;
case POLLING_QMN:
ESP_LOGD(TAG, "Decode QMN");
if (this->last_qmn_) {
this->last_qmn_->publish_state(tmp);
}
this->state_ = STATE_POLL_DECODED;
break;
default:
this->state_ = STATE_IDLE;
break;
}
return;
}
if (this->state_ == STATE_POLL_COMPLETE) {
if (this->check_incoming_crc_()) {
if (this->read_buffer_[0] == '(' && this->read_buffer_[1] == 'N' && this->read_buffer_[2] == 'A' &&
this->read_buffer_[3] == 'K') {
this->state_ = STATE_IDLE;
return;
}
// crc ok
this->state_ = STATE_POLL_CHECKED;
return;
} else {
this->state_ = STATE_IDLE;
}
}
if (this->state_ == STATE_COMMAND || this->state_ == STATE_POLL) {
while (this->available()) {
uint8_t byte;
this->read_byte(&byte);
if (this->read_pos_ == PIPSOLAR_READ_BUFFER_LENGTH) {
this->read_pos_ = 0;
this->empty_uart_buffer_();
}
this->read_buffer_[this->read_pos_] = byte;
this->read_pos_++;
// end of answer
if (byte == 0x0D) {
this->read_buffer_[this->read_pos_] = 0;
this->empty_uart_buffer_();
if (this->state_ == STATE_POLL) {
this->state_ = STATE_POLL_COMPLETE;
}
if (this->state_ == STATE_COMMAND) {
this->state_ = STATE_COMMAND_COMPLETE;
}
}
} // available
}
if (this->state_ == STATE_COMMAND) {
if (millis() - this->command_start_millis_ > esphome::pipsolar::Pipsolar::COMMAND_TIMEOUT) {
// command timeout
const char *command = this->command_queue_[this->command_queue_position_].c_str();
this->command_start_millis_ = millis();
ESP_LOGD(TAG, "timeout command from queue: %s", command);
this->command_queue_[this->command_queue_position_] = std::string("");
this->command_queue_position_ = (command_queue_position_ + 1) % COMMAND_QUEUE_LENGTH;
this->state_ = STATE_IDLE;
return;
} else {
}
}
if (this->state_ == STATE_POLL) {
if (millis() - this->command_start_millis_ > esphome::pipsolar::Pipsolar::COMMAND_TIMEOUT) {
// command timeout
ESP_LOGD(TAG, "timeout command to poll: %s", this->used_polling_commands_[this->last_polling_command_].command);
this->state_ = STATE_IDLE;
} else {
}
}
}
uint8_t Pipsolar::check_incoming_length_(uint8_t length) {
if (this->read_pos_ - 3 == length) {
return 1;
}
return 0;
}
uint8_t Pipsolar::check_incoming_crc_() {
uint16_t crc16;
crc16 = calc_crc_(read_buffer_, read_pos_ - 3);
ESP_LOGD(TAG, "checking crc on incoming message");
if (((uint8_t)((crc16) >> 8)) == read_buffer_[read_pos_ - 3] &&
((uint8_t)((crc16) &0xff)) == read_buffer_[read_pos_ - 2]) {
ESP_LOGD(TAG, "CRC OK");
read_buffer_[read_pos_ - 1] = 0;
read_buffer_[read_pos_ - 2] = 0;
read_buffer_[read_pos_ - 3] = 0;
return 1;
}
ESP_LOGD(TAG, "CRC NOK expected: %X %X but got: %X %X", ((uint8_t)((crc16) >> 8)), ((uint8_t)((crc16) &0xff)),
read_buffer_[read_pos_ - 3], read_buffer_[read_pos_ - 2]);
return 0;
}
// send next command used
uint8_t Pipsolar::send_next_command_() {
uint16_t crc16;
if (this->command_queue_[this->command_queue_position_].length() != 0) {
const char *command = this->command_queue_[this->command_queue_position_].c_str();
uint8_t byte_command[16];
uint8_t length = this->command_queue_[this->command_queue_position_].length();
for (uint8_t i = 0; i < length; i++) {
byte_command[i] = (uint8_t) this->command_queue_[this->command_queue_position_].at(i);
}
this->state_ = STATE_COMMAND;
this->command_start_millis_ = millis();
this->empty_uart_buffer_();
this->read_pos_ = 0;
crc16 = calc_crc_(byte_command, length);
this->write_str(command);
// checksum
this->write(((uint8_t)((crc16) >> 8))); // highbyte
this->write(((uint8_t)((crc16) &0xff))); // lowbyte
// end Byte
this->write(0x0D);
ESP_LOGD(TAG, "Sending command from queue: %s with length %d", command, length);
return 1;
}
return 0;
}
void Pipsolar::send_next_poll_() {
uint16_t crc16;
this->last_polling_command_ = (this->last_polling_command_ + 1) % 15;
if (this->used_polling_commands_[this->last_polling_command_].length == 0) {
this->last_polling_command_ = 0;
}
if (this->used_polling_commands_[this->last_polling_command_].length == 0) {
// no command specified
return;
}
this->state_ = STATE_POLL;
this->command_start_millis_ = millis();
this->empty_uart_buffer_();
this->read_pos_ = 0;
crc16 = calc_crc_(this->used_polling_commands_[this->last_polling_command_].command,
this->used_polling_commands_[this->last_polling_command_].length);
this->write_array(this->used_polling_commands_[this->last_polling_command_].command,
this->used_polling_commands_[this->last_polling_command_].length);
// checksum
this->write(((uint8_t)((crc16) >> 8))); // highbyte
this->write(((uint8_t)((crc16) &0xff))); // lowbyte
// end Byte
this->write(0x0D);
ESP_LOGD(TAG, "Sending polling command : %s with length %d",
this->used_polling_commands_[this->last_polling_command_].command,
this->used_polling_commands_[this->last_polling_command_].length);
}
void Pipsolar::queue_command_(const char *command, byte length) {
uint8_t next_position = command_queue_position_;
for (uint8_t i = 0; i < COMMAND_QUEUE_LENGTH; i++) {
uint8_t testposition = (next_position + i) % COMMAND_QUEUE_LENGTH;
if (command_queue_[testposition].length() == 0) {
command_queue_[testposition] = command;
ESP_LOGD(TAG, "Command queued successfully: %s with length %u at position %d", command,
command_queue_[testposition].length(), testposition);
return;
}
}
ESP_LOGD(TAG, "Command queue full dropping command: %s", command);
}
void Pipsolar::switch_command(const std::string &command) {
ESP_LOGD(TAG, "got command: %s", command.c_str());
queue_command_(command.c_str(), command.length());
}
void Pipsolar::dump_config() {
ESP_LOGCONFIG(TAG, "Pipsolar:");
ESP_LOGCONFIG(TAG, "used commands:");
for (auto &used_polling_command : this->used_polling_commands_) {
if (used_polling_command.length != 0) {
ESP_LOGCONFIG(TAG, "%s", used_polling_command.command);
}
}
}
void Pipsolar::update() {}
void Pipsolar::add_polling_command_(const char *command, ENUMPollingCommand polling_command) {
for (auto &used_polling_command : this->used_polling_commands_) {
if (used_polling_command.length == strlen(command)) {
uint8_t len = strlen(command);
if (memcmp(used_polling_command.command, command, len) == 0) {
return;
}
}
if (used_polling_command.length == 0) {
size_t length = strlen(command) + 1;
const char *beg = command;
const char *end = command + length;
used_polling_command.command = new uint8_t[length];
size_t i = 0;
for (; beg != end; ++beg, ++i) {
used_polling_command.command[i] = (uint8_t)(*beg);
}
used_polling_command.errors = 0;
used_polling_command.identifier = polling_command;
used_polling_command.length = length - 1;
return;
}
}
}
uint16_t Pipsolar::calc_crc_(uint8_t *msg, int n) {
// Initial value. xmodem uses 0xFFFF but this example
// requires an initial value of zero.
uint16_t x = 0;
while (n--) {
x = crc_xmodem_update_(x, (uint16_t) *msg++);
}
return (x);
}
// See bottom of this page: http://www.nongnu.org/avr-libc/user-manual/group__util__crc.html
// Polynomial: x^16 + x^12 + x^5 + 1 (0x1021)
uint16_t Pipsolar::crc_xmodem_update_(uint16_t crc, uint8_t data) {
int i;
crc = crc ^ ((uint16_t) data << 8);
for (i = 0; i < 8; i++) {
if (crc & 0x8000)
crc = (crc << 1) ^ 0x1021; //(polynomial = 0x1021)
else
crc <<= 1;
}
return crc;
}
} // namespace pipsolar
} // namespace esphome

View file

@ -0,0 +1,223 @@
#pragma once
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/switch/switch.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
namespace esphome {
namespace pipsolar {
enum ENUMPollingCommand {
POLLING_QPIRI = 0,
POLLING_QPIGS = 1,
POLLING_QMOD = 2,
POLLING_QFLAG = 3,
POLLING_QPIWS = 4,
POLLING_QT = 5,
POLLING_QMN = 6,
};
struct PollingCommand {
uint8_t *command;
uint8_t length = 0;
uint8_t errors;
ENUMPollingCommand identifier;
};
#define PIPSOLAR_VALUED_ENTITY_(type, name, polling_command, value_type) \
protected: \
value_type value_##name##_; \
PIPSOLAR_ENTITY_(type, name, polling_command)
#define PIPSOLAR_ENTITY_(type, name, polling_command) \
protected: \
type *name##_{}; /* NOLINT */ \
\
public: \
void set_##name(type *name) { /* NOLINT */ \
this->name##_ = name; \
this->add_polling_command_(#polling_command, POLLING_##polling_command); \
}
#define PIPSOLAR_SENSOR(name, polling_command, value_type) \
PIPSOLAR_VALUED_ENTITY_(sensor::Sensor, name, polling_command, value_type)
#define PIPSOLAR_SWITCH(name, polling_command) PIPSOLAR_ENTITY_(switch_::Switch, name, polling_command)
#define PIPSOLAR_BINARY_SENSOR(name, polling_command, value_type) \
PIPSOLAR_VALUED_ENTITY_(binary_sensor::BinarySensor, name, polling_command, value_type)
#define PIPSOLAR_VALUED_TEXT_SENSOR(name, polling_command, value_type) \
PIPSOLAR_VALUED_ENTITY_(text_sensor::TextSensor, name, polling_command, value_type)
#define PIPSOLAR_TEXT_SENSOR(name, polling_command) PIPSOLAR_ENTITY_(text_sensor::TextSensor, name, polling_command)
class Pipsolar : public uart::UARTDevice, public PollingComponent {
// QPIGS values
PIPSOLAR_SENSOR(grid_voltage, QPIGS, float)
PIPSOLAR_SENSOR(grid_frequency, QPIGS, float)
PIPSOLAR_SENSOR(ac_output_voltage, QPIGS, float)
PIPSOLAR_SENSOR(ac_output_frequency, QPIGS, float)
PIPSOLAR_SENSOR(ac_output_apparent_power, QPIGS, int)
PIPSOLAR_SENSOR(ac_output_active_power, QPIGS, int)
PIPSOLAR_SENSOR(output_load_percent, QPIGS, int)
PIPSOLAR_SENSOR(bus_voltage, QPIGS, int)
PIPSOLAR_SENSOR(battery_voltage, QPIGS, float)
PIPSOLAR_SENSOR(battery_charging_current, QPIGS, int)
PIPSOLAR_SENSOR(battery_capacity_percent, QPIGS, int)
PIPSOLAR_SENSOR(inverter_heat_sink_temperature, QPIGS, int)
PIPSOLAR_SENSOR(pv_input_current_for_battery, QPIGS, int)
PIPSOLAR_SENSOR(pv_input_voltage, QPIGS, float)
PIPSOLAR_SENSOR(battery_voltage_scc, QPIGS, float)
PIPSOLAR_SENSOR(battery_discharge_current, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(add_sbu_priority_version, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(configuration_status, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(scc_firmware_version, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(load_status, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(battery_voltage_to_steady_while_charging, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(charging_status, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(scc_charging_status, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(ac_charging_status, QPIGS, int)
PIPSOLAR_SENSOR(battery_voltage_offset_for_fans_on, QPIGS, int) //.1 scale
PIPSOLAR_SENSOR(eeprom_version, QPIGS, int)
PIPSOLAR_SENSOR(pv_charging_power, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(charging_to_floating_mode, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(switch_on, QPIGS, int)
PIPSOLAR_BINARY_SENSOR(dustproof_installed, QPIGS, int)
// QPIRI values
PIPSOLAR_SENSOR(grid_rating_voltage, QPIRI, float)
PIPSOLAR_SENSOR(grid_rating_current, QPIRI, float)
PIPSOLAR_SENSOR(ac_output_rating_voltage, QPIRI, float)
PIPSOLAR_SENSOR(ac_output_rating_frequency, QPIRI, float)
PIPSOLAR_SENSOR(ac_output_rating_current, QPIRI, float)
PIPSOLAR_SENSOR(ac_output_rating_apparent_power, QPIRI, int)
PIPSOLAR_SENSOR(ac_output_rating_active_power, QPIRI, int)
PIPSOLAR_SENSOR(battery_rating_voltage, QPIRI, float)
PIPSOLAR_SENSOR(battery_recharge_voltage, QPIRI, float)
PIPSOLAR_SENSOR(battery_under_voltage, QPIRI, float)
PIPSOLAR_SENSOR(battery_bulk_voltage, QPIRI, float)
PIPSOLAR_SENSOR(battery_float_voltage, QPIRI, float)
PIPSOLAR_SENSOR(battery_type, QPIRI, int)
PIPSOLAR_SENSOR(current_max_ac_charging_current, QPIRI, int)
PIPSOLAR_SENSOR(current_max_charging_current, QPIRI, int)
PIPSOLAR_SENSOR(input_voltage_range, QPIRI, int)
PIPSOLAR_SENSOR(output_source_priority, QPIRI, int)
PIPSOLAR_SENSOR(charger_source_priority, QPIRI, int)
PIPSOLAR_SENSOR(parallel_max_num, QPIRI, int)
PIPSOLAR_SENSOR(machine_type, QPIRI, int)
PIPSOLAR_SENSOR(topology, QPIRI, int)
PIPSOLAR_SENSOR(output_mode, QPIRI, int)
PIPSOLAR_SENSOR(battery_redischarge_voltage, QPIRI, float)
PIPSOLAR_SENSOR(pv_ok_condition_for_parallel, QPIRI, int)
PIPSOLAR_SENSOR(pv_power_balance, QPIRI, int)
// QMOD values
PIPSOLAR_VALUED_TEXT_SENSOR(device_mode, QMOD, char)
// QFLAG values
PIPSOLAR_BINARY_SENSOR(silence_buzzer_open_buzzer, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(overload_bypass_function, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(lcd_escape_to_default, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(overload_restart_function, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(over_temperature_restart_function, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(backlight_on, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(alarm_on_when_primary_source_interrupt, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(fault_code_record, QFLAG, int)
PIPSOLAR_BINARY_SENSOR(power_saving, QFLAG, int)
// QPIWS values
PIPSOLAR_BINARY_SENSOR(warnings_present, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(faults_present, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_power_loss, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_inverter_fault, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_bus_over, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_bus_under, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_bus_soft_fail, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_line_fail, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_opvshort, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_inverter_voltage_too_low, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_inverter_voltage_too_high, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_over_temperature, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_fan_lock, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_battery_voltage_high, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_battery_low_alarm, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_battery_under_shutdown, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_battery_derating, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_over_load, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_eeprom_failed, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_inverter_over_current, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_inverter_soft_failed, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_self_test_failed, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_op_dc_voltage_over, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_battery_open, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_current_sensor_failed, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_battery_short, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_power_limit, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_pv_voltage_high, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_mppt_overload, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_mppt_overload, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_battery_too_low_to_charge, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_dc_dc_over_current, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(fault_code, QPIWS, int)
PIPSOLAR_BINARY_SENSOR(warnung_low_pv_energy, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_high_ac_input_during_bus_soft_start, QPIWS, bool)
PIPSOLAR_BINARY_SENSOR(warning_battery_equalization, QPIWS, bool)
PIPSOLAR_TEXT_SENSOR(last_qpigs, QPIGS)
PIPSOLAR_TEXT_SENSOR(last_qpiri, QPIRI)
PIPSOLAR_TEXT_SENSOR(last_qmod, QMOD)
PIPSOLAR_TEXT_SENSOR(last_qflag, QFLAG)
PIPSOLAR_TEXT_SENSOR(last_qpiws, QPIWS)
PIPSOLAR_TEXT_SENSOR(last_qt, QT)
PIPSOLAR_TEXT_SENSOR(last_qmn, QMN)
PIPSOLAR_SWITCH(output_source_priority_utility_switch, QPIRI)
PIPSOLAR_SWITCH(output_source_priority_solar_switch, QPIRI)
PIPSOLAR_SWITCH(output_source_priority_battery_switch, QPIRI)
PIPSOLAR_SWITCH(input_voltage_range_switch, QPIRI)
PIPSOLAR_SWITCH(pv_ok_condition_for_parallel_switch, QPIRI)
PIPSOLAR_SWITCH(pv_power_balance_switch, QPIRI)
void switch_command(const std::string &command);
void setup() override;
void loop() override;
void dump_config() override;
void update() override;
protected:
static const size_t PIPSOLAR_READ_BUFFER_LENGTH = 110; // maximum supported answer length
static const size_t COMMAND_QUEUE_LENGTH = 10;
static const size_t COMMAND_TIMEOUT = 5000;
uint32_t last_poll_ = 0;
void add_polling_command_(const char *command, ENUMPollingCommand polling_command);
void empty_uart_buffer_();
uint8_t check_incoming_crc_();
uint8_t check_incoming_length_(uint8_t length);
uint16_t calc_crc_(uint8_t *msg, int n);
uint16_t crc_xmodem_update_(uint16_t crc, uint8_t data);
uint8_t send_next_command_();
void send_next_poll_();
void queue_command_(const char *command, byte length);
std::string command_queue_[COMMAND_QUEUE_LENGTH];
uint8_t command_queue_position_ = 0;
uint8_t read_buffer_[PIPSOLAR_READ_BUFFER_LENGTH];
size_t read_pos_{0};
uint32_t command_start_millis_ = 0;
uint8_t state_;
enum State {
STATE_IDLE = 0,
STATE_POLL = 1,
STATE_COMMAND = 2,
STATE_POLL_COMPLETE = 3,
STATE_COMMAND_COMPLETE = 4,
STATE_POLL_CHECKED = 5,
STATE_POLL_DECODED = 6,
};
uint8_t last_polling_command_ = 0;
PollingCommand used_polling_commands_[15];
};
} // namespace pipsolar
} // namespace esphome

View file

@ -0,0 +1,220 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
DEVICE_CLASS_CURRENT,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_POWER,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLTAGE,
ICON_CURRENT_AC,
ICON_EMPTY,
UNIT_AMPERE,
UNIT_CELSIUS,
UNIT_HERTZ,
UNIT_PERCENT,
UNIT_VOLT,
UNIT_EMPTY,
UNIT_VOLT_AMPS,
UNIT_WATT,
CONF_BUS_VOLTAGE,
CONF_BATTERY_VOLTAGE,
)
from .. import PIPSOLAR_COMPONENT_SCHEMA, CONF_PIPSOLAR_ID
DEPENDENCIES = ["uart"]
# QPIRI sensors
CONF_GRID_RATING_VOLTAGE = "grid_rating_voltage"
CONF_GRID_RATING_CURRENT = "grid_rating_current"
CONF_AC_OUTPUT_RATING_VOLTAGE = "ac_output_rating_voltage"
CONF_AC_OUTPUT_RATING_FREQUENCY = "ac_output_rating_frequency"
CONF_AC_OUTPUT_RATING_CURRENT = "ac_output_rating_current"
CONF_AC_OUTPUT_RATING_APPARENT_POWER = "ac_output_rating_apparent_power"
CONF_AC_OUTPUT_RATING_ACTIVE_POWER = "ac_output_rating_active_power"
CONF_BATTERY_RATING_VOLTAGE = "battery_rating_voltage"
CONF_BATTERY_RECHARGE_VOLTAGE = "battery_recharge_voltage"
CONF_BATTERY_UNDER_VOLTAGE = "battery_under_voltage"
CONF_BATTERY_BULK_VOLTAGE = "battery_bulk_voltage"
CONF_BATTERY_FLOAT_VOLTAGE = "battery_float_voltage"
CONF_BATTERY_TYPE = "battery_type"
CONF_CURRENT_MAX_AC_CHARGING_CURRENT = "current_max_ac_charging_current"
CONF_CURRENT_MAX_CHARGING_CURRENT = "current_max_charging_current"
CONF_INPUT_VOLTAGE_RANGE = "input_voltage_range"
CONF_OUTPUT_SOURCE_PRIORITY = "output_source_priority"
CONF_CHARGER_SOURCE_PRIORITY = "charger_source_priority"
CONF_PARALLEL_MAX_NUM = "parallel_max_num"
CONF_MACHINE_TYPE = "machine_type"
CONF_TOPOLOGY = "topology"
CONF_OUTPUT_MODE = "output_mode"
CONF_BATTERY_REDISCHARGE_VOLTAGE = "battery_redischarge_voltage"
CONF_PV_OK_CONDITION_FOR_PARALLEL = "pv_ok_condition_for_parallel"
CONF_PV_POWER_BALANCE = "pv_power_balance"
CONF_GRID_VOLTAGE = "grid_voltage"
CONF_GRID_FREQUENCY = "grid_frequency"
CONF_AC_OUTPUT_VOLTAGE = "ac_output_voltage"
CONF_AC_OUTPUT_FREQUENCY = "ac_output_frequency"
CONF_AC_OUTPUT_APPARENT_POWER = "ac_output_apparent_power"
CONF_AC_OUTPUT_ACTIVE_POWER = "ac_output_active_power"
CONF_OUTPUT_LOAD_PERCENT = "output_load_percent"
CONF_BATTERY_CHARGING_CURRENT = "battery_charging_current"
CONF_BATTERY_CAPACITY_PERCENT = "battery_capacity_percent"
CONF_INVERTER_HEAT_SINK_TEMPERATURE = "inverter_heat_sink_temperature"
CONF_PV_INPUT_CURRENT_FOR_BATTERY = "pv_input_current_for_battery"
CONF_PV_INPUT_VOLTAGE = "pv_input_voltage"
CONF_BATTERY_VOLTAGE_SCC = "battery_voltage_scc"
CONF_BATTERY_DISCHARGE_CURRENT = "battery_discharge_current"
CONF_ADD_SBU_PRIORITY_VERSION = "add_sbu_priority_version"
CONF_CONFIGURATION_STATUS = "configuration_status"
CONF_SCC_FIRMWARE_VERSION = "scc_firmware_version"
CONF_BATTERY_VOLTAGE_OFFSET_FOR_FANS_ON = "battery_voltage_offset_for_fans_on"
CONF_EEPROM_VERSION = "eeprom_version"
CONF_PV_CHARGING_POWER = "pv_charging_power"
TYPES = {
CONF_GRID_RATING_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_GRID_RATING_CURRENT: sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
),
CONF_AC_OUTPUT_RATING_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_AC_OUTPUT_RATING_FREQUENCY: sensor.sensor_schema(
UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY
),
CONF_AC_OUTPUT_RATING_CURRENT: sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
),
CONF_AC_OUTPUT_RATING_APPARENT_POWER: sensor.sensor_schema(
UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_POWER
),
CONF_AC_OUTPUT_RATING_ACTIVE_POWER: sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
),
CONF_BATTERY_RATING_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_RECHARGE_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_UNDER_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_BULK_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_FLOAT_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_TYPE: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_CURRENT_MAX_AC_CHARGING_CURRENT: sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
),
CONF_CURRENT_MAX_CHARGING_CURRENT: sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
),
CONF_INPUT_VOLTAGE_RANGE: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_OUTPUT_SOURCE_PRIORITY: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_CHARGER_SOURCE_PRIORITY: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_PARALLEL_MAX_NUM: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_MACHINE_TYPE: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_TOPOLOGY: sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY),
CONF_OUTPUT_MODE: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_BATTERY_REDISCHARGE_VOLTAGE: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_PV_OK_CONDITION_FOR_PARALLEL: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_PV_POWER_BALANCE: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_GRID_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_GRID_FREQUENCY: sensor.sensor_schema(
UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY
),
CONF_AC_OUTPUT_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_AC_OUTPUT_FREQUENCY: sensor.sensor_schema(
UNIT_HERTZ, ICON_CURRENT_AC, 1, DEVICE_CLASS_EMPTY
),
CONF_AC_OUTPUT_APPARENT_POWER: sensor.sensor_schema(
UNIT_VOLT_AMPS, ICON_EMPTY, 1, DEVICE_CLASS_POWER
),
CONF_AC_OUTPUT_ACTIVE_POWER: sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
),
CONF_OUTPUT_LOAD_PERCENT: sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_BUS_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_CHARGING_CURRENT: sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
),
CONF_BATTERY_CAPACITY_PERCENT: sensor.sensor_schema(
UNIT_PERCENT, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_INVERTER_HEAT_SINK_TEMPERATURE: sensor.sensor_schema(
UNIT_CELSIUS, ICON_EMPTY, 1, DEVICE_CLASS_TEMPERATURE
),
CONF_PV_INPUT_CURRENT_FOR_BATTERY: sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
),
CONF_PV_INPUT_VOLTAGE: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_VOLTAGE_SCC: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_BATTERY_DISCHARGE_CURRENT: sensor.sensor_schema(
UNIT_AMPERE, ICON_EMPTY, 1, DEVICE_CLASS_CURRENT
),
CONF_BATTERY_VOLTAGE_OFFSET_FOR_FANS_ON: sensor.sensor_schema(
UNIT_VOLT, ICON_EMPTY, 1, DEVICE_CLASS_VOLTAGE
),
CONF_EEPROM_VERSION: sensor.sensor_schema(
UNIT_EMPTY, ICON_EMPTY, 1, DEVICE_CLASS_EMPTY
),
CONF_PV_CHARGING_POWER: sensor.sensor_schema(
UNIT_WATT, ICON_EMPTY, 1, DEVICE_CLASS_POWER
),
}
CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend(
{cv.Optional(type): schema for type, schema in TYPES.items()}
)
async def to_code(config):
paren = await cg.get_variable(config[CONF_PIPSOLAR_ID])
for type, _ in TYPES.items():
if type in config:
conf = config[type]
sens = await sensor.new_sensor(conf)
cg.add(getattr(paren, f"set_{type}")(sens))

View file

@ -0,0 +1,60 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import switch
from esphome.const import (
CONF_ID,
CONF_INVERTED,
CONF_ICON,
ICON_POWER,
)
from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA, pipsolar_ns
DEPENDENCIES = ["uart"]
CONF_OUTPUT_SOURCE_PRIORITY_UTILITY = "output_source_priority_utility"
CONF_OUTPUT_SOURCE_PRIORITY_SOLAR = "output_source_priority_solar"
CONF_OUTPUT_SOURCE_PRIORITY_BATTERY = "output_source_priority_battery"
CONF_INPUT_VOLTAGE_RANGE = "input_voltage_range"
CONF_PV_OK_CONDITION_FOR_PARALLEL = "pv_ok_condition_for_parallel"
CONF_PV_POWER_BALANCE = "pv_power_balance"
TYPES = {
CONF_OUTPUT_SOURCE_PRIORITY_UTILITY: ("POP00", None),
CONF_OUTPUT_SOURCE_PRIORITY_SOLAR: ("POP01", None),
CONF_OUTPUT_SOURCE_PRIORITY_BATTERY: ("POP02", None),
CONF_INPUT_VOLTAGE_RANGE: ("PGR01", "PGR00"),
CONF_PV_OK_CONDITION_FOR_PARALLEL: ("PPVOKC1", "PPVOKC0"),
CONF_PV_POWER_BALANCE: ("PSPB1", "PSPB0"),
}
PipsolarSwitch = pipsolar_ns.class_("PipsolarSwitch", switch.Switch, cg.Component)
PIPSWITCH_SCHEMA = switch.SWITCH_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(PipsolarSwitch),
cv.Optional(CONF_INVERTED): cv.invalid(
"Pipsolar switches do not support inverted mode!"
),
cv.Optional(CONF_ICON, default=ICON_POWER): switch.icon,
}
).extend(cv.COMPONENT_SCHEMA)
CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend(
{cv.Optional(type): PIPSWITCH_SCHEMA for type in TYPES}
)
async def to_code(config):
paren = await cg.get_variable(config[CONF_PIPSOLAR_ID])
for type, (on, off) in TYPES.items():
if type in config:
conf = config[type]
var = cg.new_Pvariable(conf[CONF_ID])
await cg.register_component(var, conf)
await switch.register_switch(var, conf)
cg.add(getattr(paren, f"set_{type}_switch")(var))
cg.add(var.set_parent(paren))
cg.add(var.set_on_command(on))
if off is not None:
cg.add(var.set_off_command(off))

View file

@ -0,0 +1,24 @@
#include "pipsolar_switch.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace pipsolar {
static const char *const TAG = "pipsolar.switch";
void PipsolarSwitch::dump_config() { LOG_SWITCH("", "Pipsolar Switch", this); }
void PipsolarSwitch::write_state(bool state) {
if (state) {
if (this->on_command_.length() > 0) {
this->parent_->switch_command(this->on_command_);
}
} else {
if (this->off_command_.length() > 0) {
this->parent_->switch_command(this->off_command_);
}
}
}
} // namespace pipsolar
} // namespace esphome

View file

@ -0,0 +1,25 @@
#pragma once
#include "../pipsolar.h"
#include "esphome/components/switch/switch.h"
#include "esphome/core/component.h"
namespace esphome {
namespace pipsolar {
class Pipsolar;
class PipsolarSwitch : public switch_::Switch, public Component {
public:
void set_parent(Pipsolar *parent) { this->parent_ = parent; };
void set_on_command(std::string command) { this->on_command_ = std::move(command); };
void set_off_command(std::string command) { this->off_command_ = std::move(command); };
void dump_config() override;
protected:
void write_state(bool state) override;
std::string on_command_;
std::string off_command_;
Pipsolar *parent_;
};
} // namespace pipsolar
} // namespace esphome

View file

@ -0,0 +1,52 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import text_sensor
from esphome.const import CONF_ID
from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA, pipsolar_ns
DEPENDENCIES = ["uart"]
CONF_DEVICE_MODE = "device_mode"
CONF_LAST_QPIGS = "last_qpigs"
CONF_LAST_QPIRI = "last_qpiri"
CONF_LAST_QMOD = "last_qmod"
CONF_LAST_QFLAG = "last_qflag"
CONF_LAST_QPIWS = "last_qpiws"
CONF_LAST_QT = "last_qt"
CONF_LAST_QMN = "last_qmn"
PipsolarTextSensor = pipsolar_ns.class_(
"PipsolarTextSensor", text_sensor.TextSensor, cg.Component
)
TYPES = [
CONF_DEVICE_MODE,
CONF_LAST_QPIGS,
CONF_LAST_QPIRI,
CONF_LAST_QMOD,
CONF_LAST_QFLAG,
CONF_LAST_QPIWS,
CONF_LAST_QT,
CONF_LAST_QMN,
]
CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend(
{
cv.Optional(type): text_sensor.TEXT_SENSOR_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(PipsolarTextSensor)}
)
for type in TYPES
}
)
async def to_code(config):
paren = await cg.get_variable(config[CONF_PIPSOLAR_ID])
for type in TYPES:
if type in config:
conf = config[type]
var = cg.new_Pvariable(conf[CONF_ID])
await text_sensor.register_text_sensor(var, conf)
await cg.register_component(var, conf)
cg.add(getattr(paren, f"set_{type}")(var))

View file

@ -0,0 +1,13 @@
#include "pipsolar_textsensor.h"
#include "esphome/core/log.h"
#include "esphome/core/application.h"
namespace esphome {
namespace pipsolar {
static const char *const TAG = "pipsolar.text_sensor";
void PipsolarTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Pipsolar TextSensor", this); }
} // namespace pipsolar
} // namespace esphome

View file

@ -0,0 +1,20 @@
#pragma once
#include "../pipsolar.h"
#include "esphome/components/text_sensor/text_sensor.h"
#include "esphome/core/component.h"
namespace esphome {
namespace pipsolar {
class Pipsolar;
class PipsolarTextSensor : public Component, public text_sensor::TextSensor {
public:
void set_parent(Pipsolar *parent) { this->parent_ = parent; };
void dump_config() override;
protected:
Pipsolar *parent_;
};
} // namespace pipsolar
} // namespace esphome

View file

@ -56,6 +56,9 @@ time:
tuya:
time_id: sntp_time
pipsolar:
id: inverter0
sensor:
- platform: homeassistant
entity_id: sensor.hello_world
@ -63,6 +66,140 @@ sensor:
- platform: tuya
id: tuya_sensor
sensor_datapoint: 1
- platform: pipsolar
pipsolar_id: inverter0
grid_rating_voltage:
id: inverter0_grid_rating_voltage
name: inverter0_grid_rating_voltage
grid_rating_current:
id: inverter0_grid_rating_current
name: inverter0_grid_rating_current
ac_output_rating_voltage:
id: inverter0_ac_output_rating_voltage
name: inverter0_ac_output_rating_voltage
ac_output_rating_frequency:
id: inverter0_ac_output_rating_frequency
name: inverter0_ac_output_rating_frequency
ac_output_rating_current:
id: inverter0_ac_output_rating_current
name: inverter0_ac_output_rating_current
ac_output_rating_apparent_power:
id: inverter0_ac_output_rating_apparent_power
name: inverter0_ac_output_rating_apparent_power
ac_output_rating_active_power:
id: inverter0_ac_output_rating_active_power
name: inverter0_ac_output_rating_active_power
battery_rating_voltage:
id: inverter0_battery_rating_voltage
name: inverter0_battery_rating_voltage
battery_recharge_voltage:
id: inverter0_battery_recharge_voltage
name: inverter0_battery_recharge_voltage
battery_under_voltage:
id: inverter0_battery_under_voltage
name: inverter0_battery_under_voltage
battery_bulk_voltage:
id: inverter0_battery_bulk_voltage
name: inverter0_battery_bulk_voltage
battery_float_voltage:
id: inverter0_battery_float_voltage
name: inverter0_battery_float_voltage
battery_type:
id: inverter0_battery_type
name: inverter0_battery_type
current_max_ac_charging_current:
id: inverter0_current_max_ac_charging_current
name: inverter0_current_max_ac_charging_current
current_max_charging_current:
id: inverter0_current_max_charging_current
name: inverter0_current_max_charging_current
input_voltage_range:
id: inverter0_input_voltage_range
name: inverter0_input_voltage_range
output_source_priority:
id: inverter0_output_source_priority
name: inverter0_output_source_priority
charger_source_priority:
id: inverter0_charger_source_priority
name: inverter0_charger_source_priority
parallel_max_num:
id: inverter0_parallel_max_num
name: inverter0_parallel_max_num
machine_type:
id: inverter0_machine_type
name: inverter0_machine_type
topology:
id: inverter0_topology
name: inverter0_topology
output_mode:
id: inverter0_output_mode
name: inverter0_output_mode
battery_redischarge_voltage:
id: inverter0_battery_redischarge_voltage
name: inverter0_battery_redischarge_voltage
pv_ok_condition_for_parallel:
id: inverter0_pv_ok_condition_for_parallel
name: inverter0_pv_ok_condition_for_parallel
pv_power_balance:
id: inverter0_pv_power_balance
name: inverter0_pv_power_balance
grid_voltage:
id: inverter0_grid_voltage
name: inverter0_grid_voltage
grid_frequency:
id: inverter0_grid_frequency
name: inverter0_grid_frequency
ac_output_voltage:
id: inverter0_ac_output_voltage
name: inverter0_ac_output_voltage
ac_output_frequency:
id: inverter0_ac_output_frequency
name: inverter0_ac_output_frequency
ac_output_apparent_power:
id: inverter0_ac_output_apparent_power
name: inverter0_ac_output_apparent_power
ac_output_active_power:
id: inverter0_ac_output_active_power
name: inverter0_ac_output_active_power
output_load_percent:
id: inverter0_output_load_percent
name: inverter0_output_load_percent
bus_voltage:
id: inverter0_bus_voltage
name: inverter0_bus_voltage
battery_voltage:
id: inverter0_battery_voltage
name: inverter0_battery_voltage
battery_charging_current:
id: inverter0_battery_charging_current
name: inverter0_battery_charging_current
battery_capacity_percent:
id: inverter0_battery_capacity_percent
name: inverter0_battery_capacity_percent
inverter_heat_sink_temperature:
id: inverter0_inverter_heat_sink_temperature
name: inverter0_inverter_heat_sink_temperature
pv_input_current_for_battery:
id: inverter0_pv_input_current_for_battery
name: inverter0_pv_input_current_for_battery
pv_input_voltage:
id: inverter0_pv_input_voltage
name: inverter0_pv_input_voltage
battery_voltage_scc:
id: inverter0_battery_voltage_scc
name: inverter0_battery_voltage_scc
battery_discharge_current:
id: inverter0_battery_discharge_current
name: inverter0_battery_discharge_current
battery_voltage_offset_for_fans_on:
id: inverter0_battery_voltage_offset_for_fans_on
name: inverter0_battery_voltage_offset_for_fans_on
eeprom_version:
id: inverter0_eeprom_version
name: inverter0_eeprom_version
pv_charging_power:
id: inverter0_pv_charging_power
name: inverter0_pv_charging_power
- platform: "hrxl_maxsonar_wr"
name: "Rainwater Tank Level"
filters:
@ -95,6 +232,59 @@ binary_sensor:
- platform: tuya
id: tuya_binary_sensor
sensor_datapoint: 1
- platform: pipsolar
pipsolar_id: inverter0
add_sbu_priority_version:
id: inverter0_add_sbu_priority_version
name: inverter0_add_sbu_priority_version
configuration_status:
id: inverter0_configuration_status
name: inverter0_configuration_status
scc_firmware_version:
id: inverter0_scc_firmware_version
name: inverter0_scc_firmware_version
load_status:
id: inverter0_load_status
name: inverter0_load_status
battery_voltage_to_steady_while_charging:
id: inverter0_battery_voltage_to_steady_while_charging
name: inverter0_battery_voltage_to_steady_while_charging
charging_status:
id: inverter0_charging_status
name: inverter0_charging_status
scc_charging_status:
id: inverter0_scc_charging_status
name: inverter0_scc_charging_status
ac_charging_status:
id: inverter0_ac_charging_status
name: inverter0_ac_charging_status
charging_to_floating_mode:
id: inverter0_charging_to_floating_mode
name: inverter0_charging_to_floating_mode
switch_on:
id: inverter0_switch_on
name: inverter0_switch_on
dustproof_installed:
id: inverter0_dustproof_installed
name: inverter0_dustproof_installed
silence_buzzer_open_buzzer:
id: inverter0_silence_buzzer_open_buzzer
name: inverter0_silence_buzzer_open_buzzer
overload_bypass_function:
id: inverter0_overload_bypass_function
name: inverter0_overload_bypass_function
lcd_escape_to_default:
id: inverter0_lcd_escape_to_default
name: inverter0_lcd_escape_to_default
overload_restart_function:
id: inverter0_overload_restart_function
name: inverter0_overload_restart_function
over_temperature_restart_function:
id: inverter0_over_temperature_restart_function
name: inverter0_over_temperature_restart_function
backlight_on:
id: inverter0_backlight_on
name: inverter0_backlight_on
- platform: template
id: ar1
lambda: 'return {};'
@ -131,6 +321,20 @@ switch:
- platform: tuya
id: tuya_switch
switch_datapoint: 1
- platform: pipsolar
pipsolar_id: inverter0
output_source_priority_utility:
name: inverter0_output_source_priority_utility
output_source_priority_solar:
name: inverter0_output_source_priority_solar
output_source_priority_battery:
name: inverter0_output_source_priority_battery
input_voltage_range:
name: inverter0_input_voltage_range
pv_ok_condition_for_parallel:
name: inverter0_pv_ok_condition_for_parallel
pv_power_balance:
name: inverter0_pv_power_balance
light:
- platform: fastled_clockless
@ -204,6 +408,30 @@ display:
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
text_sensor:
- platform: pipsolar
pipsolar_id: inverter0
device_mode:
id: inverter0_device_mode
name: inverter0_device_mode
last_qpigs:
id: inverter0_last_qpigs
name: inverter0_last_qpigs
last_qpiri:
id: inverter0_last_qpiri
name: inverter0_last_qpiri
last_qmod:
id: inverter0_last_qmod
name: inverter0_last_qmod
last_qflag:
id: inverter0_last_qflag
name: inverter0_last_qflag
output:
- platform: pipsolar
pipsolar_id: inverter0
battery_recharge_voltage:
id: inverter0_battery_recharge_voltage_out
esp32_camera:
name: ESP-32 Camera
data_pins: [GPIO17, GPIO35, GPIO34, GPIO5, GPIO39, GPIO18, GPIO36, GPIO19]