mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 00:18:11 +01:00
Add support for BL0906 energy meter (#7339)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
b3f03c07c6
commit
0375072bdf
14 changed files with 738 additions and 0 deletions
|
@ -58,6 +58,7 @@ esphome/components/beken_spi_led_strip/* @Mat931
|
||||||
esphome/components/bh1750/* @OttoWinter
|
esphome/components/bh1750/* @OttoWinter
|
||||||
esphome/components/binary_sensor/* @esphome/core
|
esphome/components/binary_sensor/* @esphome/core
|
||||||
esphome/components/bk72xx/* @kuba2k2
|
esphome/components/bk72xx/* @kuba2k2
|
||||||
|
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
|
||||||
esphome/components/bl0939/* @ziceva
|
esphome/components/bl0939/* @ziceva
|
||||||
esphome/components/bl0940/* @tobias-
|
esphome/components/bl0940/* @tobias-
|
||||||
esphome/components/bl0942/* @dbuezas
|
esphome/components/bl0942/* @dbuezas
|
||||||
|
|
1
esphome/components/bl0906/__init__.py
Normal file
1
esphome/components/bl0906/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
CODEOWNERS = ["@athom-tech", "@tarontop", "@jesserockz"]
|
238
esphome/components/bl0906/bl0906.cpp
Normal file
238
esphome/components/bl0906/bl0906.cpp
Normal file
|
@ -0,0 +1,238 @@
|
||||||
|
#include "bl0906.h"
|
||||||
|
#include "constants.h"
|
||||||
|
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bl0906 {
|
||||||
|
|
||||||
|
static const char *const TAG = "bl0906";
|
||||||
|
|
||||||
|
constexpr uint32_t to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
|
||||||
|
|
||||||
|
constexpr int32_t to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
|
||||||
|
|
||||||
|
// The SUM byte is (Addr+Data_L+Data_M+Data_H)&0xFF negated;
|
||||||
|
constexpr uint8_t bl0906_checksum(const uint8_t address, const DataPacket *data) {
|
||||||
|
return (address + data->l + data->m + data->h) ^ 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BL0906::loop() {
|
||||||
|
if (this->current_channel_ == UINT8_MAX) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (this->available())
|
||||||
|
this->flush();
|
||||||
|
|
||||||
|
if (this->current_channel_ == 0) {
|
||||||
|
// Temperature
|
||||||
|
this->read_data_(BL0906_TEMPERATURE, BL0906_TREF, this->temperature_sensor_);
|
||||||
|
} else if (this->current_channel_ == 1) {
|
||||||
|
this->read_data_(BL0906_I_1_RMS, BL0906_IREF, this->current_1_sensor_);
|
||||||
|
this->read_data_(BL0906_WATT_1, BL0906_PREF, this->power_1_sensor_);
|
||||||
|
this->read_data_(BL0906_CF_1_CNT, BL0906_EREF, this->energy_1_sensor_);
|
||||||
|
} else if (this->current_channel_ == 2) {
|
||||||
|
this->read_data_(BL0906_I_2_RMS, BL0906_IREF, this->current_2_sensor_);
|
||||||
|
this->read_data_(BL0906_WATT_2, BL0906_PREF, this->power_2_sensor_);
|
||||||
|
this->read_data_(BL0906_CF_2_CNT, BL0906_EREF, this->energy_2_sensor_);
|
||||||
|
} else if (this->current_channel_ == 3) {
|
||||||
|
this->read_data_(BL0906_I_3_RMS, BL0906_IREF, this->current_3_sensor_);
|
||||||
|
this->read_data_(BL0906_WATT_3, BL0906_PREF, this->power_3_sensor_);
|
||||||
|
this->read_data_(BL0906_CF_3_CNT, BL0906_EREF, this->energy_3_sensor_);
|
||||||
|
} else if (this->current_channel_ == 4) {
|
||||||
|
this->read_data_(BL0906_I_4_RMS, BL0906_IREF, this->current_4_sensor_);
|
||||||
|
this->read_data_(BL0906_WATT_4, BL0906_PREF, this->power_4_sensor_);
|
||||||
|
this->read_data_(BL0906_CF_4_CNT, BL0906_EREF, this->energy_4_sensor_);
|
||||||
|
} else if (this->current_channel_ == 5) {
|
||||||
|
this->read_data_(BL0906_I_5_RMS, BL0906_IREF, this->current_5_sensor_);
|
||||||
|
this->read_data_(BL0906_WATT_5, BL0906_PREF, this->power_5_sensor_);
|
||||||
|
this->read_data_(BL0906_CF_5_CNT, BL0906_EREF, this->energy_5_sensor_);
|
||||||
|
} else if (this->current_channel_ == 6) {
|
||||||
|
this->read_data_(BL0906_I_6_RMS, BL0906_IREF, this->current_6_sensor_);
|
||||||
|
this->read_data_(BL0906_WATT_6, BL0906_PREF, this->power_6_sensor_);
|
||||||
|
this->read_data_(BL0906_CF_6_CNT, BL0906_EREF, this->energy_6_sensor_);
|
||||||
|
} else if (this->current_channel_ == UINT8_MAX - 2) {
|
||||||
|
// Frequency
|
||||||
|
this->read_data_(BL0906_FREQUENCY, BL0906_FREF, frequency_sensor_);
|
||||||
|
// Voltage
|
||||||
|
this->read_data_(BL0906_V_RMS, BL0906_UREF, voltage_sensor_);
|
||||||
|
} else if (this->current_channel_ == UINT8_MAX - 1) {
|
||||||
|
// Total power
|
||||||
|
this->read_data_(BL0906_WATT_SUM, BL0906_WATT, this->total_power_sensor_);
|
||||||
|
// Total Energy
|
||||||
|
this->read_data_(BL0906_CF_SUM_CNT, BL0906_CF, this->total_energy_sensor_);
|
||||||
|
} else {
|
||||||
|
this->current_channel_ = UINT8_MAX - 2; // Go to frequency and voltage
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->current_channel_++;
|
||||||
|
this->handle_actions_();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BL0906::setup() {
|
||||||
|
while (this->available())
|
||||||
|
this->flush();
|
||||||
|
this->write_array(USR_WRPROT_WITABLE, sizeof(USR_WRPROT_WITABLE));
|
||||||
|
// Calibration (1: register address; 2: value before calibration; 3: value after calibration)
|
||||||
|
this->bias_correction_(BL0906_RMSOS_1, 0.01600, 0); // Calibration current_1
|
||||||
|
this->bias_correction_(BL0906_RMSOS_2, 0.01500, 0);
|
||||||
|
this->bias_correction_(BL0906_RMSOS_3, 0.01400, 0);
|
||||||
|
this->bias_correction_(BL0906_RMSOS_4, 0.01300, 0);
|
||||||
|
this->bias_correction_(BL0906_RMSOS_5, 0.01200, 0);
|
||||||
|
this->bias_correction_(BL0906_RMSOS_6, 0.01200, 0); // Calibration current_6
|
||||||
|
|
||||||
|
this->write_array(USR_WRPROT_ONLYREAD, sizeof(USR_WRPROT_ONLYREAD));
|
||||||
|
}
|
||||||
|
|
||||||
|
void BL0906::update() { this->current_channel_ = 0; }
|
||||||
|
|
||||||
|
size_t BL0906::enqueue_action_(ActionCallbackFuncPtr function) {
|
||||||
|
this->action_queue_.push_back(function);
|
||||||
|
return this->action_queue_.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BL0906::handle_actions_() {
|
||||||
|
if (this->action_queue_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ActionCallbackFuncPtr ptr_func = nullptr;
|
||||||
|
for (int i = 0; i < this->action_queue_.size(); i++) {
|
||||||
|
ptr_func = this->action_queue_[i];
|
||||||
|
if (ptr_func) {
|
||||||
|
ESP_LOGI(TAG, "HandleActionCallback[%d]...", i);
|
||||||
|
(this->*ptr_func)();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (this->available()) {
|
||||||
|
this->read();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->action_queue_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset energy
|
||||||
|
void BL0906::reset_energy_() {
|
||||||
|
this->write_array(BL0906_INIT[0], 6);
|
||||||
|
delay(1);
|
||||||
|
this->flush();
|
||||||
|
|
||||||
|
ESP_LOGW(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_INIT[0][0], BL0906_INIT[0][1], BL0906_INIT[0][2],
|
||||||
|
BL0906_INIT[0][3], BL0906_INIT[0][4], BL0906_INIT[0][5]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read data
|
||||||
|
void BL0906::read_data_(const uint8_t address, const float reference, sensor::Sensor *sensor) {
|
||||||
|
if (sensor == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DataPacket buffer;
|
||||||
|
ube24_t data_u24;
|
||||||
|
sbe24_t data_s24;
|
||||||
|
float value = 0;
|
||||||
|
|
||||||
|
bool signed_result = reference == BL0906_TREF || reference == BL0906_WATT || reference == BL0906_PREF;
|
||||||
|
|
||||||
|
this->write_byte(BL0906_READ_COMMAND);
|
||||||
|
this->write_byte(address);
|
||||||
|
if (this->read_array((uint8_t *) &buffer, sizeof(buffer) - 1)) {
|
||||||
|
if (bl0906_checksum(address, &buffer) == buffer.checksum) {
|
||||||
|
if (signed_result) {
|
||||||
|
data_s24.l = buffer.l;
|
||||||
|
data_s24.m = buffer.m;
|
||||||
|
data_s24.h = buffer.h;
|
||||||
|
} else {
|
||||||
|
data_u24.l = buffer.l;
|
||||||
|
data_u24.m = buffer.m;
|
||||||
|
data_u24.h = buffer.h;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
|
||||||
|
while (read() >= 0)
|
||||||
|
;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Power
|
||||||
|
if (reference == BL0906_PREF) {
|
||||||
|
value = (float) to_int32_t(data_s24) * reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total power
|
||||||
|
if (reference == BL0906_WATT) {
|
||||||
|
value = (float) to_int32_t(data_s24) * reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Voltage, current, power, total power
|
||||||
|
if (reference == BL0906_UREF || reference == BL0906_IREF || reference == BL0906_EREF || reference == BL0906_CF) {
|
||||||
|
value = (float) to_uint32_t(data_u24) * reference;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frequency
|
||||||
|
if (reference == BL0906_FREF) {
|
||||||
|
value = reference / (float) to_uint32_t(data_u24);
|
||||||
|
}
|
||||||
|
// Chip temperature
|
||||||
|
if (reference == BL0906_TREF) {
|
||||||
|
value = (float) to_int32_t(data_s24);
|
||||||
|
value = (value - 64) * 12.5 / 59 - 40;
|
||||||
|
}
|
||||||
|
sensor->publish_state(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// RMS offset correction
|
||||||
|
void BL0906::bias_correction_(uint8_t address, float measurements, float correction) {
|
||||||
|
DataPacket data;
|
||||||
|
float ki = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097; // Current coefficient
|
||||||
|
float i_rms0 = measurements * ki;
|
||||||
|
float i_rms = correction * ki;
|
||||||
|
int32_t value = (i_rms * i_rms - i_rms0 * i_rms0) / 256;
|
||||||
|
data.l = value << 24 >> 24;
|
||||||
|
data.m = value << 16 >> 24;
|
||||||
|
if (value < 0) {
|
||||||
|
data.h = (value << 8 >> 24) | 0b10000000;
|
||||||
|
}
|
||||||
|
data.address = bl0906_checksum(address, &data);
|
||||||
|
ESP_LOGV(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_WRITE_COMMAND, address, data.l, data.m, data.h, data.address);
|
||||||
|
this->write_byte(BL0906_WRITE_COMMAND);
|
||||||
|
this->write_byte(address);
|
||||||
|
this->write_byte(data.l);
|
||||||
|
this->write_byte(data.m);
|
||||||
|
this->write_byte(data.h);
|
||||||
|
this->write_byte(data.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BL0906::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "BL0906:");
|
||||||
|
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
|
||||||
|
|
||||||
|
LOG_SENSOR(" ", "Current1", this->current_1_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Current2", this->current_2_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Current3", this->current_3_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Current4", this->current_4_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Current5", this->current_5_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Current6", this->current_6_sensor_);
|
||||||
|
|
||||||
|
LOG_SENSOR(" ", "Power1", this->power_1_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Power2", this->power_2_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Power3", this->power_3_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Power4", this->power_4_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Power5", this->power_5_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Power6", this->power_6_sensor_);
|
||||||
|
|
||||||
|
LOG_SENSOR(" ", "Energy1", this->energy_1_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Energy2", this->energy_2_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Energy3", this->energy_3_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Energy4", this->energy_4_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Energy5", this->energy_5_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Energy6", this->energy_6_sensor_);
|
||||||
|
|
||||||
|
LOG_SENSOR(" ", "Total Power", this->total_power_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Total Energy", this->total_energy_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Frequency", this->frequency_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace bl0906
|
||||||
|
} // namespace esphome
|
96
esphome/components/bl0906/bl0906.h
Normal file
96
esphome/components/bl0906/bl0906.h
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
#include "esphome/components/uart/uart.h"
|
||||||
|
#include "esphome/core/automation.h"
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/datatypes.h"
|
||||||
|
|
||||||
|
// https://www.belling.com.cn/media/file_object/bel_product/BL0906/datasheet/BL0906_V1.02_cn.pdf
|
||||||
|
// https://www.belling.com.cn/media/file_object/bel_product/BL0906/guide/BL0906%20APP%20Note_V1.02.pdf
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bl0906 {
|
||||||
|
|
||||||
|
struct DataPacket { // NOLINT(altera-struct-pack-align)
|
||||||
|
uint8_t l{0};
|
||||||
|
uint8_t m{0};
|
||||||
|
uint8_t h{0};
|
||||||
|
uint8_t checksum; // checksum
|
||||||
|
uint8_t address;
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
struct ube24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
||||||
|
uint8_t l{0};
|
||||||
|
uint8_t m{0};
|
||||||
|
uint8_t h{0};
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-align)
|
||||||
|
uint8_t l{0};
|
||||||
|
uint8_t m{0};
|
||||||
|
int8_t h{0};
|
||||||
|
} __attribute__((packed));
|
||||||
|
|
||||||
|
template<typename... Ts> class ResetEnergyAction;
|
||||||
|
|
||||||
|
class BL0906;
|
||||||
|
|
||||||
|
using ActionCallbackFuncPtr = void (BL0906::*)();
|
||||||
|
|
||||||
|
class BL0906 : public PollingComponent, public uart::UARTDevice {
|
||||||
|
SUB_SENSOR(voltage)
|
||||||
|
SUB_SENSOR(current_1)
|
||||||
|
SUB_SENSOR(current_2)
|
||||||
|
SUB_SENSOR(current_3)
|
||||||
|
SUB_SENSOR(current_4)
|
||||||
|
SUB_SENSOR(current_5)
|
||||||
|
SUB_SENSOR(current_6)
|
||||||
|
SUB_SENSOR(power_1)
|
||||||
|
SUB_SENSOR(power_2)
|
||||||
|
SUB_SENSOR(power_3)
|
||||||
|
SUB_SENSOR(power_4)
|
||||||
|
SUB_SENSOR(power_5)
|
||||||
|
SUB_SENSOR(power_6)
|
||||||
|
SUB_SENSOR(total_power)
|
||||||
|
SUB_SENSOR(energy_1)
|
||||||
|
SUB_SENSOR(energy_2)
|
||||||
|
SUB_SENSOR(energy_3)
|
||||||
|
SUB_SENSOR(energy_4)
|
||||||
|
SUB_SENSOR(energy_5)
|
||||||
|
SUB_SENSOR(energy_6)
|
||||||
|
SUB_SENSOR(total_energy)
|
||||||
|
SUB_SENSOR(frequency)
|
||||||
|
SUB_SENSOR(temperature)
|
||||||
|
|
||||||
|
public:
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
void update() override;
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
template<typename... Ts> friend class ResetEnergyAction;
|
||||||
|
|
||||||
|
void reset_energy_();
|
||||||
|
|
||||||
|
void read_data_(uint8_t address, float reference, sensor::Sensor *sensor);
|
||||||
|
|
||||||
|
void bias_correction_(uint8_t address, float measurements, float correction);
|
||||||
|
|
||||||
|
uint8_t current_channel_{0};
|
||||||
|
size_t enqueue_action_(ActionCallbackFuncPtr function);
|
||||||
|
void handle_actions_();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<ActionCallbackFuncPtr> action_queue_{};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... Ts> class ResetEnergyAction : public Action<Ts...>, public Parented<BL0906> {
|
||||||
|
public:
|
||||||
|
void play(Ts... x) override { this->parent_->enqueue_action_(&BL0906::reset_energy_); }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace bl0906
|
||||||
|
} // namespace esphome
|
4
esphome/components/bl0906/const.py
Normal file
4
esphome/components/bl0906/const.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# const.py
|
||||||
|
ICON_ENERGY = "mdi:lightning-bolt"
|
||||||
|
ICON_FREQUENCY = "mdi:cosine-wave"
|
||||||
|
ICON_VOLTAGE = "mdi:sine-wave"
|
122
esphome/components/bl0906/constants.h
Normal file
122
esphome/components/bl0906/constants.h
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace bl0906 {
|
||||||
|
|
||||||
|
// Total power conversion
|
||||||
|
static const float BL0906_WATT = 16 * 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) /
|
||||||
|
(40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000);
|
||||||
|
// Total Energy conversion
|
||||||
|
static const float BL0906_CF = 16 * 4194304 * 0.032768 * 16 /
|
||||||
|
(3600000 * 16 *
|
||||||
|
(40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 /
|
||||||
|
(1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000))));
|
||||||
|
// Frequency conversion
|
||||||
|
static const float BL0906_FREF = 10000000;
|
||||||
|
// Temperature conversion
|
||||||
|
static const float BL0906_TREF = 12.5 / 59 - 40;
|
||||||
|
// Current conversion
|
||||||
|
static const float BL0906_IREF = 1.097 / (12875 * 1 * (5.1 + 5.1) * 1000 / 2000);
|
||||||
|
// Voltage conversion
|
||||||
|
static const float BL0906_UREF = 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / (13162 * 1 * 100 * 1000);
|
||||||
|
// Power conversion
|
||||||
|
static const float BL0906_PREF = 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) /
|
||||||
|
(40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000);
|
||||||
|
// Energy conversion
|
||||||
|
static const float BL0906_EREF = 4194304 * 0.032768 * 16 /
|
||||||
|
(3600000 * 16 *
|
||||||
|
(40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 /
|
||||||
|
(1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000))));
|
||||||
|
// Current coefficient
|
||||||
|
static const float BL0906_KI = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097;
|
||||||
|
// Power coefficient
|
||||||
|
static const float BL0906_KP = 40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / 1.097 / 1.097 /
|
||||||
|
(20000 + 20000 + 20000 + 20000 + 20000);
|
||||||
|
|
||||||
|
static const uint8_t USR_WRPROT_WITABLE[6] = {0xCA, 0x9E, 0x55, 0x55, 0x00, 0xB7};
|
||||||
|
static const uint8_t USR_WRPROT_ONLYREAD[6] = {0xCA, 0x9E, 0x00, 0x00, 0x00, 0x61};
|
||||||
|
|
||||||
|
static const uint8_t BL0906_READ_COMMAND = 0x35;
|
||||||
|
static const uint8_t BL0906_WRITE_COMMAND = 0xCA;
|
||||||
|
|
||||||
|
// Register address
|
||||||
|
// Voltage
|
||||||
|
static const uint8_t BL0906_V_RMS = 0x16;
|
||||||
|
|
||||||
|
// Total power
|
||||||
|
static const uint8_t BL0906_WATT_SUM = 0X2C;
|
||||||
|
|
||||||
|
// Current1~6
|
||||||
|
static const uint8_t BL0906_I_1_RMS = 0x0D; // current_1
|
||||||
|
static const uint8_t BL0906_I_2_RMS = 0x0E;
|
||||||
|
static const uint8_t BL0906_I_3_RMS = 0x0F;
|
||||||
|
static const uint8_t BL0906_I_4_RMS = 0x10;
|
||||||
|
static const uint8_t BL0906_I_5_RMS = 0x13;
|
||||||
|
static const uint8_t BL0906_I_6_RMS = 0x14; // current_6
|
||||||
|
|
||||||
|
// Power1~6
|
||||||
|
static const uint8_t BL0906_WATT_1 = 0X23; // power_1
|
||||||
|
static const uint8_t BL0906_WATT_2 = 0X24;
|
||||||
|
static const uint8_t BL0906_WATT_3 = 0X25;
|
||||||
|
static const uint8_t BL0906_WATT_4 = 0X26;
|
||||||
|
static const uint8_t BL0906_WATT_5 = 0X29;
|
||||||
|
static const uint8_t BL0906_WATT_6 = 0X2A; // power_6
|
||||||
|
|
||||||
|
// Active pulse count, unsigned
|
||||||
|
static const uint8_t BL0906_CF_1_CNT = 0X30; // Channel_1
|
||||||
|
static const uint8_t BL0906_CF_2_CNT = 0X31;
|
||||||
|
static const uint8_t BL0906_CF_3_CNT = 0X32;
|
||||||
|
static const uint8_t BL0906_CF_4_CNT = 0X33;
|
||||||
|
static const uint8_t BL0906_CF_5_CNT = 0X36;
|
||||||
|
static const uint8_t BL0906_CF_6_CNT = 0X37; // Channel_6
|
||||||
|
|
||||||
|
// Total active pulse count, unsigned
|
||||||
|
static const uint8_t BL0906_CF_SUM_CNT = 0X39;
|
||||||
|
|
||||||
|
// Voltage frequency cycle
|
||||||
|
static const uint8_t BL0906_FREQUENCY = 0X4E;
|
||||||
|
|
||||||
|
// Internal temperature
|
||||||
|
static const uint8_t BL0906_TEMPERATURE = 0X5E;
|
||||||
|
|
||||||
|
// Calibration register
|
||||||
|
// RMS gain adjustment register
|
||||||
|
static const uint8_t BL0906_RMSGN_1 = 0x6D; // Channel_1
|
||||||
|
static const uint8_t BL0906_RMSGN_2 = 0x6E;
|
||||||
|
static const uint8_t BL0906_RMSGN_3 = 0x6F;
|
||||||
|
static const uint8_t BL0906_RMSGN_4 = 0x70;
|
||||||
|
static const uint8_t BL0906_RMSGN_5 = 0x73;
|
||||||
|
static const uint8_t BL0906_RMSGN_6 = 0x74; // Channel_6
|
||||||
|
|
||||||
|
// RMS offset correction register
|
||||||
|
static const uint8_t BL0906_RMSOS_1 = 0x78; // Channel_1
|
||||||
|
static const uint8_t BL0906_RMSOS_2 = 0x79;
|
||||||
|
static const uint8_t BL0906_RMSOS_3 = 0x7A;
|
||||||
|
static const uint8_t BL0906_RMSOS_4 = 0x7B;
|
||||||
|
static const uint8_t BL0906_RMSOS_5 = 0x7E;
|
||||||
|
static const uint8_t BL0906_RMSOS_6 = 0x7F; // Channel_6
|
||||||
|
|
||||||
|
// Active power gain adjustment register
|
||||||
|
static const uint8_t BL0906_WATTGN_1 = 0xB7; // Channel_1
|
||||||
|
static const uint8_t BL0906_WATTGN_2 = 0xB8;
|
||||||
|
static const uint8_t BL0906_WATTGN_3 = 0xB9;
|
||||||
|
static const uint8_t BL0906_WATTGN_4 = 0xBA;
|
||||||
|
static const uint8_t BL0906_WATTGN_5 = 0xBD;
|
||||||
|
static const uint8_t BL0906_WATTGN_6 = 0xBE; // Channel_6
|
||||||
|
|
||||||
|
// User write protection setting register,
|
||||||
|
// You must first write 0x5555 to the write protection setting register before writing to other registers.
|
||||||
|
static const uint8_t BL0906_USR_WRPROT = 0x9E;
|
||||||
|
|
||||||
|
// Reset Register
|
||||||
|
static const uint8_t BL0906_SOFT_RESET = 0x9F;
|
||||||
|
|
||||||
|
const uint8_t BL0906_INIT[2][6] = {
|
||||||
|
// Reset to default
|
||||||
|
{BL0906_WRITE_COMMAND, BL0906_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x52},
|
||||||
|
// Enable User Operation Write
|
||||||
|
{BL0906_WRITE_COMMAND, BL0906_USR_WRPROT, 0x55, 0x55, 0x00, 0xB7}};
|
||||||
|
|
||||||
|
} // namespace bl0906
|
||||||
|
} // namespace esphome
|
184
esphome/components/bl0906/sensor.py
Normal file
184
esphome/components/bl0906/sensor.py
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
from esphome import automation
|
||||||
|
from esphome.automation import maybe_simple_id
|
||||||
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import sensor, uart
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_CHANNEL,
|
||||||
|
CONF_CURRENT,
|
||||||
|
CONF_ENERGY,
|
||||||
|
CONF_FREQUENCY,
|
||||||
|
CONF_ID,
|
||||||
|
CONF_NAME,
|
||||||
|
CONF_POWER,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
CONF_TOTAL_POWER,
|
||||||
|
CONF_VOLTAGE,
|
||||||
|
DEVICE_CLASS_CURRENT,
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
DEVICE_CLASS_FREQUENCY,
|
||||||
|
DEVICE_CLASS_POWER,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
ICON_CURRENT_AC,
|
||||||
|
ICON_POWER,
|
||||||
|
ICON_THERMOMETER,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
STATE_CLASS_TOTAL_INCREASING,
|
||||||
|
UNIT_AMPERE,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_HERTZ,
|
||||||
|
UNIT_KILOWATT_HOURS,
|
||||||
|
UNIT_VOLT,
|
||||||
|
UNIT_WATT,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Import ICONS not included in esphome's const.py, from the local components const.py
|
||||||
|
from .const import ICON_ENERGY, ICON_FREQUENCY, ICON_VOLTAGE
|
||||||
|
|
||||||
|
DEPENDENCIES = ["uart"]
|
||||||
|
AUTO_LOAD = ["bl0906"]
|
||||||
|
CONF_TOTAL_ENERGY = "total_energy"
|
||||||
|
|
||||||
|
bl0906_ns = cg.esphome_ns.namespace("bl0906")
|
||||||
|
BL0906 = bl0906_ns.class_("BL0906", cg.PollingComponent, uart.UARTDevice)
|
||||||
|
ResetEnergyAction = bl0906_ns.class_("ResetEnergyAction", automation.Action)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(BL0906),
|
||||||
|
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
||||||
|
icon=ICON_FREQUENCY,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_FREQUENCY,
|
||||||
|
unit_of_measurement=UNIT_HERTZ,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||||
|
icon=ICON_THERMOMETER,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||||
|
icon=ICON_VOLTAGE,
|
||||||
|
accuracy_decimals=1,
|
||||||
|
device_class=DEVICE_CLASS_VOLTAGE,
|
||||||
|
unit_of_measurement=UNIT_VOLT,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_TOTAL_POWER): sensor.sensor_schema(
|
||||||
|
icon=ICON_POWER,
|
||||||
|
accuracy_decimals=3,
|
||||||
|
device_class=DEVICE_CLASS_POWER,
|
||||||
|
unit_of_measurement=UNIT_WATT,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_TOTAL_ENERGY): sensor.sensor_schema(
|
||||||
|
icon=ICON_ENERGY,
|
||||||
|
accuracy_decimals=3,
|
||||||
|
device_class=DEVICE_CLASS_ENERGY,
|
||||||
|
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||||
|
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Optional(f"{CONF_CHANNEL}_{i + 1}"): cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_CURRENT): cv.maybe_simple_value(
|
||||||
|
sensor.sensor_schema(
|
||||||
|
icon=ICON_CURRENT_AC,
|
||||||
|
accuracy_decimals=3,
|
||||||
|
device_class=DEVICE_CLASS_CURRENT,
|
||||||
|
unit_of_measurement=UNIT_AMPERE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
key=CONF_NAME,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_POWER): cv.maybe_simple_value(
|
||||||
|
sensor.sensor_schema(
|
||||||
|
icon=ICON_POWER,
|
||||||
|
accuracy_decimals=0,
|
||||||
|
device_class=DEVICE_CLASS_POWER,
|
||||||
|
unit_of_measurement=UNIT_WATT,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
key=CONF_NAME,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ENERGY): cv.maybe_simple_value(
|
||||||
|
sensor.sensor_schema(
|
||||||
|
icon=ICON_ENERGY,
|
||||||
|
accuracy_decimals=3,
|
||||||
|
device_class=DEVICE_CLASS_ENERGY,
|
||||||
|
unit_of_measurement=UNIT_KILOWATT_HOURS,
|
||||||
|
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||||
|
),
|
||||||
|
key=CONF_NAME,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
for i in range(6)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.extend(uart.UART_DEVICE_SCHEMA)
|
||||||
|
.extend(cv.polling_component_schema("60s"))
|
||||||
|
)
|
||||||
|
|
||||||
|
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||||
|
"bl0906", baud_rate=19200, require_tx=True, require_rx=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@automation.register_action(
|
||||||
|
"bl0906.reset_energy",
|
||||||
|
ResetEnergyAction,
|
||||||
|
maybe_simple_id(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_ID): cv.use_id(BL0906),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
)
|
||||||
|
async def reset_energy_to_code(config, action_id, template_arg, args):
|
||||||
|
paren = await cg.get_variable(config[CONF_ID])
|
||||||
|
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
await uart.register_uart_device(var, config)
|
||||||
|
if frequency_config := config.get(CONF_FREQUENCY):
|
||||||
|
sens = await sensor.new_sensor(frequency_config)
|
||||||
|
cg.add(var.set_frequency_sensor(sens))
|
||||||
|
if temperature_config := config.get(CONF_TEMPERATURE):
|
||||||
|
sens = await sensor.new_sensor(temperature_config)
|
||||||
|
cg.add(var.set_temperature_sensor(sens))
|
||||||
|
if voltage_config := config.get(CONF_VOLTAGE):
|
||||||
|
sens = await sensor.new_sensor(voltage_config)
|
||||||
|
cg.add(var.set_voltage_sensor(sens))
|
||||||
|
|
||||||
|
for i in range(6):
|
||||||
|
if channel_config := config.get(f"{CONF_CHANNEL}_{i + 1}"):
|
||||||
|
if current_config := channel_config.get(CONF_CURRENT):
|
||||||
|
sens = await sensor.new_sensor(current_config)
|
||||||
|
cg.add(getattr(var, f"set_current_{i + 1}_sensor")(sens))
|
||||||
|
if power_config := channel_config.get(CONF_POWER):
|
||||||
|
sens = await sensor.new_sensor(power_config)
|
||||||
|
cg.add(getattr(var, f"set_power_{i + 1}_sensor")(sens))
|
||||||
|
if energy_config := channel_config.get(CONF_ENERGY):
|
||||||
|
sens = await sensor.new_sensor(energy_config)
|
||||||
|
cg.add(getattr(var, f"set_energy_{i + 1}_sensor")(sens))
|
||||||
|
|
||||||
|
if total_power_config := config.get(CONF_TOTAL_POWER):
|
||||||
|
sens = await sensor.new_sensor(total_power_config)
|
||||||
|
cg.add(var.set_total_power_sensor(sens))
|
||||||
|
|
||||||
|
if total_energy_config := config.get(CONF_TOTAL_ENERGY):
|
||||||
|
sens = await sensor.new_sensor(total_energy_config)
|
||||||
|
cg.add(var.set_total_energy_sensor(sens))
|
62
tests/components/bl0906/common.yaml
Normal file
62
tests/components/bl0906/common.yaml
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
uart:
|
||||||
|
- id: uart_bl0906
|
||||||
|
tx_pin:
|
||||||
|
number: ${tx_pin}
|
||||||
|
rx_pin:
|
||||||
|
number: ${rx_pin}
|
||||||
|
baud_rate: 19200
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: bl0906
|
||||||
|
frequency:
|
||||||
|
name: 'Frequency'
|
||||||
|
temperature:
|
||||||
|
name: 'Temperature'
|
||||||
|
voltage:
|
||||||
|
name: 'Voltage'
|
||||||
|
channel_1:
|
||||||
|
current:
|
||||||
|
name: 'Current_1'
|
||||||
|
power:
|
||||||
|
name: 'Power_1'
|
||||||
|
energy:
|
||||||
|
name: 'Energy_1'
|
||||||
|
channel_2:
|
||||||
|
current:
|
||||||
|
name: 'Current_2'
|
||||||
|
power:
|
||||||
|
name: 'Power_2'
|
||||||
|
energy:
|
||||||
|
name: 'Energy_2'
|
||||||
|
channel_3:
|
||||||
|
current:
|
||||||
|
name: 'Current_3'
|
||||||
|
power:
|
||||||
|
name: 'Power_3'
|
||||||
|
energy:
|
||||||
|
name: 'Energy_3'
|
||||||
|
channel_4:
|
||||||
|
current:
|
||||||
|
name: 'Current_4'
|
||||||
|
power:
|
||||||
|
name: 'Power_4'
|
||||||
|
energy:
|
||||||
|
name: 'Energy_4'
|
||||||
|
channel_5:
|
||||||
|
current:
|
||||||
|
name: 'Current_5'
|
||||||
|
power:
|
||||||
|
name: 'Power_5'
|
||||||
|
energy:
|
||||||
|
name: 'Energy_5'
|
||||||
|
channel_6:
|
||||||
|
current:
|
||||||
|
name: 'Current_6'
|
||||||
|
power:
|
||||||
|
name: 'Power_6'
|
||||||
|
energy:
|
||||||
|
name: 'Energy_6'
|
||||||
|
total_energy:
|
||||||
|
name: 'Total_Energy'
|
||||||
|
total_power:
|
||||||
|
name: 'Total_Power'
|
5
tests/components/bl0906/test.esp32-ard.yaml
Normal file
5
tests/components/bl0906/test.esp32-ard.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
substitutions:
|
||||||
|
tx_pin: GPIO12
|
||||||
|
rx_pin: GPIO14
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
5
tests/components/bl0906/test.esp32-c3-ard.yaml
Normal file
5
tests/components/bl0906/test.esp32-c3-ard.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
substitutions:
|
||||||
|
tx_pin: GPIO7
|
||||||
|
rx_pin: GPIO8
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
5
tests/components/bl0906/test.esp32-c3-idf.yaml
Normal file
5
tests/components/bl0906/test.esp32-c3-idf.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
substitutions:
|
||||||
|
tx_pin: GPIO7
|
||||||
|
rx_pin: GPIO8
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
5
tests/components/bl0906/test.esp32-idf.yaml
Normal file
5
tests/components/bl0906/test.esp32-idf.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
substitutions:
|
||||||
|
tx_pin: GPIO12
|
||||||
|
rx_pin: GPIO14
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
5
tests/components/bl0906/test.esp8266-ard.yaml
Normal file
5
tests/components/bl0906/test.esp8266-ard.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
substitutions:
|
||||||
|
tx_pin: GPIO1
|
||||||
|
rx_pin: GPIO3
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
5
tests/components/bl0906/test.rp2040-ard.yaml
Normal file
5
tests/components/bl0906/test.rp2040-ard.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
substitutions:
|
||||||
|
tx_pin: GPIO4
|
||||||
|
rx_pin: GPIO5
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
Loading…
Reference in a new issue