mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 02:02:51 +00:00
[renogy_inverter_ble] Add Renogy inverter BLE sensor component (#16754)
Co-authored-by: J. Nick Koston <nick@koston.org> Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
co-authored by
J. Nick Koston
J. Nick Koston
parent
1e13b7f973
commit
150e32c4be
@@ -472,6 +472,7 @@ esphome/components/rc522/* @glmnet
|
||||
esphome/components/rc522_i2c/* @glmnet
|
||||
esphome/components/rc522_spi/* @glmnet
|
||||
esphome/components/rd03d/* @jasstrong
|
||||
esphome/components/renogy_inverter_ble/* @emilioaray-dev
|
||||
esphome/components/resampler/speaker/* @kahrendt
|
||||
esphome/components/restart/* @esphome/core
|
||||
esphome/components/rf_bridge/* @jesserockz
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import ble_client
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
AUTO_LOAD = ["sensor"]
|
||||
CODEOWNERS = ["@emilioaray-dev"]
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
DOMAIN = "renogy_inverter_ble"
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_RENOGY_INVERTER_BLE_ID = "renogy_inverter_ble_id"
|
||||
|
||||
renogy_inverter_ble_ns = cg.esphome_ns.namespace(DOMAIN)
|
||||
RenogyInverterBle = renogy_inverter_ble_ns.class_(
|
||||
"RenogyInverterBle", ble_client.BLEClientNode, cg.PollingComponent
|
||||
)
|
||||
|
||||
RENOGY_INVERTER_BLE_COMPONENT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_RENOGY_INVERTER_BLE_ID): cv.use_id(RenogyInverterBle),
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(RenogyInverterBle),
|
||||
}
|
||||
)
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA)
|
||||
.extend(cv.polling_component_schema("30s"))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
@@ -0,0 +1,248 @@
|
||||
#include "renogy_inverter_ble.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <cstring>
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::renogy_inverter_ble {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "renogy_inverter_ble");
|
||||
|
||||
static constexpr uint16_t SERVICE_NOTIFY_UUID = 0xFFF0;
|
||||
static constexpr uint16_t CHAR_NOTIFY_UUID = 0xFFF1;
|
||||
static constexpr uint16_t SERVICE_WRITE_UUID = 0xFFD0;
|
||||
static constexpr uint16_t CHAR_WRITE_UUID = 0xFFD1;
|
||||
static constexpr uint16_t CHAR_INIT_UUID = 0xFFD4;
|
||||
|
||||
static constexpr uint8_t MODBUS_DEVICE_ID = 0x20;
|
||||
static constexpr uint8_t MODBUS_READ_HOLDING = 0x03;
|
||||
static constexpr uint16_t REG_MAIN = 4000;
|
||||
static constexpr uint16_t REG_MAIN_WORDS = 32;
|
||||
static constexpr uint16_t REG_LOAD = 4408;
|
||||
static constexpr uint16_t REG_LOAD_WORDS = 6;
|
||||
|
||||
// Watchdog: returns the cycle to IDLE when the inverter stops answering
|
||||
static constexpr uint32_t CYCLE_TIMEOUT_MS = 3000;
|
||||
// Gap the inverter needs between two consecutive register reads
|
||||
static constexpr uint32_t LOAD_READ_DELAY_MS = 300;
|
||||
|
||||
static constexpr uint32_t CYCLE_TIMEOUT_ID = 0;
|
||||
static constexpr uint32_t LOAD_READ_ID = 1;
|
||||
|
||||
static void publish(sensor::Sensor *s, float value) {
|
||||
if (s != nullptr) {
|
||||
s->publish_state(value);
|
||||
}
|
||||
}
|
||||
|
||||
void RenogyInverterBle::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Renogy Inverter BLE:");
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "AC input voltage", this->ac_input_voltage_sensor_);
|
||||
LOG_SENSOR(" ", "AC output voltage", this->ac_output_voltage_sensor_);
|
||||
LOG_SENSOR(" ", "AC output current", this->ac_output_current_sensor_);
|
||||
LOG_SENSOR(" ", "AC output frequency", this->ac_output_frequency_sensor_);
|
||||
LOG_SENSOR(" ", "Input frequency", this->input_frequency_sensor_);
|
||||
LOG_SENSOR(" ", "Battery voltage", this->battery_voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
LOG_SENSOR(" ", "Load current", this->load_current_sensor_);
|
||||
LOG_SENSOR(" ", "Load active power", this->load_active_power_sensor_);
|
||||
LOG_SENSOR(" ", "Load apparent power", this->load_apparent_power_sensor_);
|
||||
}
|
||||
|
||||
void RenogyInverterBle::abort_cycle_() {
|
||||
this->cancel_timeout(CYCLE_TIMEOUT_ID);
|
||||
this->cancel_timeout(LOAD_READ_ID);
|
||||
this->frame_len_ = 0;
|
||||
this->state_ = State::IDLE;
|
||||
}
|
||||
|
||||
void RenogyInverterBle::read_register_(uint16_t start_register, uint16_t word_count) {
|
||||
std::array<uint8_t, 8> req{MODBUS_DEVICE_ID,
|
||||
MODBUS_READ_HOLDING,
|
||||
static_cast<uint8_t>(start_register >> 8),
|
||||
static_cast<uint8_t>(start_register),
|
||||
static_cast<uint8_t>(word_count >> 8),
|
||||
static_cast<uint8_t>(word_count)};
|
||||
const uint16_t crc = crc16(req.data(), 6);
|
||||
req[6] = crc & 0xFF; // Modbus sends the CRC low byte first
|
||||
req[7] = crc >> 8;
|
||||
|
||||
this->frame_len_ = 0;
|
||||
const esp_err_t status =
|
||||
esp_ble_gattc_write_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(), this->write_handle_,
|
||||
req.size(), req.data(), ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status != ESP_OK) {
|
||||
ESP_LOGW(TAG, "write_char (reg %u) failed, status=%d", start_register, status);
|
||||
this->abort_cycle_();
|
||||
}
|
||||
}
|
||||
|
||||
void RenogyInverterBle::start_cycle_() {
|
||||
this->set_timeout(CYCLE_TIMEOUT_ID, CYCLE_TIMEOUT_MS, [this]() {
|
||||
ESP_LOGW(TAG, "Read cycle timed out (state=%u); resetting", static_cast<uint8_t>(this->state_));
|
||||
this->abort_cycle_();
|
||||
});
|
||||
if (this->init_handle_ == 0) {
|
||||
// No init characteristic discovered; try the main read directly
|
||||
this->state_ = State::MAIN;
|
||||
this->read_register_(REG_MAIN, REG_MAIN_WORDS);
|
||||
return;
|
||||
}
|
||||
this->state_ = State::INIT;
|
||||
const esp_err_t status = esp_ble_gattc_read_char(this->parent_->get_gattc_if(), this->parent_->get_conn_id(),
|
||||
this->init_handle_, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status != ESP_OK) {
|
||||
ESP_LOGW(TAG, "init read_char failed, status=%d", status);
|
||||
this->abort_cycle_();
|
||||
}
|
||||
}
|
||||
|
||||
void RenogyInverterBle::update() {
|
||||
if (this->node_state != espbt::ClientState::ESTABLISHED) {
|
||||
ESP_LOGD(TAG, "Not connected yet; skipping poll");
|
||||
return;
|
||||
}
|
||||
if (this->state_ != State::IDLE) {
|
||||
ESP_LOGD(TAG, "Previous read cycle still in progress; skipping");
|
||||
return;
|
||||
}
|
||||
this->start_cycle_();
|
||||
}
|
||||
|
||||
void RenogyInverterBle::on_frame_complete_(uint16_t len) {
|
||||
// A Modbus frame with its CRC appended checks to zero
|
||||
if (len < 5 || crc16(this->frame_.data(), len) != 0) {
|
||||
ESP_LOGW(TAG, "CRC mismatch (state=%u, len=%u)", static_cast<uint8_t>(this->state_), len);
|
||||
this->abort_cycle_();
|
||||
return;
|
||||
}
|
||||
// Exception response: function code with the high bit set, third byte is the exception code
|
||||
if ((this->frame_[1] & 0x80) != 0) {
|
||||
ESP_LOGW(TAG, "Modbus exception 0x%02X (state=%u)", this->frame_[2], static_cast<uint8_t>(this->state_));
|
||||
this->abort_cycle_();
|
||||
return;
|
||||
}
|
||||
// Only the reply to the outstanding read is accepted: same device, same function, the requested word count
|
||||
const bool awaiting_reply = this->state_ == State::MAIN || this->state_ == State::LOAD;
|
||||
const uint16_t expected_words = this->state_ == State::MAIN ? REG_MAIN_WORDS : REG_LOAD_WORDS;
|
||||
if (!awaiting_reply || this->frame_[0] != MODBUS_DEVICE_ID || this->frame_[1] != MODBUS_READ_HOLDING ||
|
||||
this->frame_[2] != 2 * expected_words) {
|
||||
ESP_LOGW(TAG, "Unexpected response %02X %02X %02X (state=%u)", this->frame_[0], this->frame_[1], this->frame_[2],
|
||||
static_cast<uint8_t>(this->state_));
|
||||
this->abort_cycle_();
|
||||
return;
|
||||
}
|
||||
auto reg16 = [this](uint8_t i) { return encode_uint16(this->frame_[3 + 2 * i], this->frame_[4 + 2 * i]); };
|
||||
|
||||
if (this->state_ == State::MAIN) {
|
||||
publish(this->ac_input_voltage_sensor_, reg16(0) * 0.1f);
|
||||
publish(this->ac_output_voltage_sensor_, reg16(2) * 0.1f);
|
||||
publish(this->ac_output_current_sensor_, reg16(3) * 0.01f);
|
||||
publish(this->ac_output_frequency_sensor_, reg16(4) * 0.01f);
|
||||
publish(this->battery_voltage_sensor_, reg16(5) * 0.1f);
|
||||
publish(this->temperature_sensor_, reg16(6) * 0.1f);
|
||||
publish(this->input_frequency_sensor_, reg16(9) * 0.01f);
|
||||
this->frame_len_ = 0;
|
||||
this->set_timeout(LOAD_READ_ID, LOAD_READ_DELAY_MS, [this]() {
|
||||
this->state_ = State::LOAD;
|
||||
this->read_register_(REG_LOAD, REG_LOAD_WORDS);
|
||||
});
|
||||
return;
|
||||
}
|
||||
publish(this->load_current_sensor_, reg16(0) * 0.01f);
|
||||
publish(this->load_active_power_sensor_, static_cast<float>(reg16(1)));
|
||||
publish(this->load_apparent_power_sensor_, static_cast<float>(reg16(2)));
|
||||
this->abort_cycle_();
|
||||
}
|
||||
|
||||
void RenogyInverterBle::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
this->abort_cycle_();
|
||||
this->node_state = espbt::ClientState::IDLE;
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
auto *notify_chr = this->parent_->get_characteristic(espbt::ESPBTUUID::from_uint16(SERVICE_NOTIFY_UUID),
|
||||
espbt::ESPBTUUID::from_uint16(CHAR_NOTIFY_UUID));
|
||||
auto *write_chr = this->parent_->get_characteristic(espbt::ESPBTUUID::from_uint16(SERVICE_WRITE_UUID),
|
||||
espbt::ESPBTUUID::from_uint16(CHAR_WRITE_UUID));
|
||||
auto *init_chr = this->parent_->get_characteristic(espbt::ESPBTUUID::from_uint16(SERVICE_WRITE_UUID),
|
||||
espbt::ESPBTUUID::from_uint16(CHAR_INIT_UUID));
|
||||
if (notify_chr == nullptr || write_chr == nullptr) {
|
||||
ESP_LOGE(TAG, "Required characteristics not found; not a Renogy inverter?");
|
||||
break;
|
||||
}
|
||||
this->notify_handle_ = notify_chr->handle;
|
||||
this->write_handle_ = write_chr->handle;
|
||||
this->init_handle_ = (init_chr != nullptr) ? init_chr->handle : 0;
|
||||
ESP_LOGI(TAG, "handles: notify=%u write=%u init=%u", this->notify_handle_, this->write_handle_,
|
||||
this->init_handle_);
|
||||
const esp_err_t status = this->parent_->register_for_notify(this->notify_handle_);
|
||||
if (status != ESP_OK) {
|
||||
ESP_LOGW(TAG, "register_for_notify failed, status=%d", status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_REG_FOR_NOTIFY_EVT: {
|
||||
// Other nodes on the same client see this event too
|
||||
if (param->reg_for_notify.handle != this->notify_handle_) {
|
||||
break;
|
||||
}
|
||||
if (param->reg_for_notify.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Notification registration failed, status=%d", param->reg_for_notify.status);
|
||||
break;
|
||||
}
|
||||
this->node_state = espbt::ClientState::ESTABLISHED;
|
||||
ESP_LOGI(TAG, "Connected; will poll the inverter every update interval");
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (this->state_ != State::INIT || param->read.handle != this->init_handle_) {
|
||||
break;
|
||||
}
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "init read failed, status=%d", param->read.status);
|
||||
this->abort_cycle_();
|
||||
break;
|
||||
}
|
||||
// Init handshake done; the inverter now answers Modbus reads
|
||||
this->state_ = State::MAIN;
|
||||
this->read_register_(REG_MAIN, REG_MAIN_WORDS);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.handle != this->notify_handle_) {
|
||||
break;
|
||||
}
|
||||
const uint16_t value_len = param->notify.value_len;
|
||||
if (this->frame_len_ + value_len > MAX_FRAME) {
|
||||
ESP_LOGW(TAG, "Frame overflow; resetting");
|
||||
this->abort_cycle_();
|
||||
break;
|
||||
}
|
||||
memcpy(&this->frame_[this->frame_len_], param->notify.value, value_len);
|
||||
this->frame_len_ += value_len;
|
||||
if (this->frame_len_ < 3) {
|
||||
break;
|
||||
}
|
||||
// An exception frame ([id][func|0x80][exc][crc][crc]) is 5 bytes; a normal response is
|
||||
// [id][func][byte_count][...data][crc][crc]
|
||||
const uint16_t expected_len = ((this->frame_[1] & 0x80) != 0) ? 5 : 3 + this->frame_[2] + 2;
|
||||
if (this->frame_len_ >= expected_len) {
|
||||
this->on_frame_complete_(expected_len);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::renogy_inverter_ble
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
#include <array>
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome::renogy_inverter_ble {
|
||||
|
||||
namespace espbt = esphome::esp32_ble_tracker;
|
||||
|
||||
// Largest Modbus response: 3 header + 2 * 32 data + 2 CRC bytes
|
||||
static constexpr size_t MAX_FRAME = 69;
|
||||
|
||||
// Renogy inverter BLE GATT (RIV1230PU). Notify char 0xFFF1 (svc 0xFFF0), write char 0xFFD1
|
||||
// (svc 0xFFD0), init char 0xFFD4 (svc 0xFFD0). Modbus device id 0x20, function 0x03.
|
||||
class RenogyInverterBle : public ble_client::BLEClientNode, public PollingComponent {
|
||||
SUB_SENSOR(ac_input_voltage)
|
||||
SUB_SENSOR(ac_output_voltage)
|
||||
SUB_SENSOR(ac_output_current)
|
||||
SUB_SENSOR(ac_output_frequency)
|
||||
SUB_SENSOR(input_frequency)
|
||||
SUB_SENSOR(battery_voltage)
|
||||
SUB_SENSOR(temperature)
|
||||
SUB_SENSOR(load_current)
|
||||
SUB_SENSOR(load_active_power)
|
||||
SUB_SENSOR(load_apparent_power)
|
||||
|
||||
public:
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
|
||||
protected:
|
||||
// Read cycle: IDLE -> (update) INIT read(0xFFD4) -> MAIN read(4000) -> LOAD read(4408) -> IDLE
|
||||
enum class State : uint8_t { IDLE, INIT, MAIN, LOAD };
|
||||
|
||||
void start_cycle_();
|
||||
void read_register_(uint16_t start_register, uint16_t word_count);
|
||||
void on_frame_complete_(uint16_t len);
|
||||
void abort_cycle_();
|
||||
|
||||
State state_{State::IDLE};
|
||||
uint8_t frame_len_{0};
|
||||
uint16_t notify_handle_{0};
|
||||
uint16_t write_handle_{0};
|
||||
uint16_t init_handle_{0};
|
||||
// Reassembles the Modbus response, which arrives in MTU-sized notification chunks
|
||||
std::array<uint8_t, MAX_FRAME> frame_;
|
||||
};
|
||||
|
||||
} // namespace esphome::renogy_inverter_ble
|
||||
|
||||
#endif // USE_ESP32
|
||||
@@ -0,0 +1,99 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BATTERY_VOLTAGE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_APPARENT_POWER,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_FREQUENCY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HERTZ,
|
||||
UNIT_VOLT,
|
||||
UNIT_VOLT_AMPS,
|
||||
UNIT_WATT,
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from . import CONF_RENOGY_INVERTER_BLE_ID, RENOGY_INVERTER_BLE_COMPONENT_SCHEMA
|
||||
|
||||
CODEOWNERS = ["@emilioaray-dev"]
|
||||
DEPENDENCIES = ["renogy_inverter_ble"]
|
||||
|
||||
CONF_AC_INPUT_VOLTAGE = "ac_input_voltage"
|
||||
CONF_AC_OUTPUT_VOLTAGE = "ac_output_voltage"
|
||||
CONF_AC_OUTPUT_CURRENT = "ac_output_current"
|
||||
CONF_AC_OUTPUT_FREQUENCY = "ac_output_frequency"
|
||||
CONF_INPUT_FREQUENCY = "input_frequency"
|
||||
CONF_LOAD_CURRENT = "load_current"
|
||||
CONF_LOAD_ACTIVE_POWER = "load_active_power"
|
||||
CONF_LOAD_APPARENT_POWER = "load_apparent_power"
|
||||
|
||||
VOLTAGE_SCHEMA = sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
CURRENT_SCHEMA = sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
FREQUENCY_SCHEMA = sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HERTZ,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_FREQUENCY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = RENOGY_INVERTER_BLE_COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(CONF_AC_INPUT_VOLTAGE): VOLTAGE_SCHEMA,
|
||||
cv.Optional(CONF_AC_OUTPUT_VOLTAGE): VOLTAGE_SCHEMA,
|
||||
cv.Optional(CONF_AC_OUTPUT_CURRENT): CURRENT_SCHEMA,
|
||||
cv.Optional(CONF_AC_OUTPUT_FREQUENCY): FREQUENCY_SCHEMA,
|
||||
cv.Optional(CONF_INPUT_FREQUENCY): FREQUENCY_SCHEMA,
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): VOLTAGE_SCHEMA,
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_LOAD_CURRENT): CURRENT_SCHEMA,
|
||||
cv.Optional(CONF_LOAD_ACTIVE_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_LOAD_APPARENT_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT_AMPS,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_APPARENT_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
hub = await cg.get_variable(config[CONF_RENOGY_INVERTER_BLE_ID])
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_AC_INPUT_VOLTAGE, hub.set_ac_input_voltage_sensor)
|
||||
await sensors(CONF_AC_OUTPUT_VOLTAGE, hub.set_ac_output_voltage_sensor)
|
||||
await sensors(CONF_AC_OUTPUT_CURRENT, hub.set_ac_output_current_sensor)
|
||||
await sensors(CONF_AC_OUTPUT_FREQUENCY, hub.set_ac_output_frequency_sensor)
|
||||
await sensors(CONF_INPUT_FREQUENCY, hub.set_input_frequency_sensor)
|
||||
await sensors(CONF_BATTERY_VOLTAGE, hub.set_battery_voltage_sensor)
|
||||
await sensors(CONF_TEMPERATURE, hub.set_temperature_sensor)
|
||||
await sensors(CONF_LOAD_CURRENT, hub.set_load_current_sensor)
|
||||
await sensors(CONF_LOAD_ACTIVE_POWER, hub.set_load_active_power_sensor)
|
||||
await sensors(CONF_LOAD_APPARENT_POWER, hub.set_load_apparent_power_sensor)
|
||||
@@ -0,0 +1,31 @@
|
||||
ble_client:
|
||||
- mac_address: 14:9C:EF:01:02:03
|
||||
id: inverter_client
|
||||
|
||||
renogy_inverter_ble:
|
||||
- id: inv0
|
||||
ble_client_id: inverter_client
|
||||
|
||||
sensor:
|
||||
- platform: renogy_inverter_ble
|
||||
renogy_inverter_ble_id: inv0
|
||||
ac_input_voltage:
|
||||
name: AC input voltage
|
||||
ac_output_voltage:
|
||||
name: AC output voltage
|
||||
ac_output_current:
|
||||
name: AC output current
|
||||
ac_output_frequency:
|
||||
name: AC output frequency
|
||||
input_frequency:
|
||||
name: Input frequency
|
||||
battery_voltage:
|
||||
name: Battery voltage
|
||||
temperature:
|
||||
name: Temperature
|
||||
load_current:
|
||||
name: Load current
|
||||
load_active_power:
|
||||
name: Load active power
|
||||
load_apparent_power:
|
||||
name: Load apparent power
|
||||
@@ -0,0 +1,3 @@
|
||||
packages:
|
||||
ble: !include ../../test_build_components/common/ble/esp32-idf.yaml
|
||||
renogy_inverter_ble: !include common.yaml
|
||||
Reference in New Issue
Block a user