bl0942: Fix init sequence, add address and line_frequency options (#7250)

This commit is contained in:
David Woodhouse 2024-08-26 23:41:09 +01:00 committed by GitHub
parent 0f2064193f
commit e10f8128c8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 138 additions and 36 deletions

View file

@ -2,6 +2,8 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include <cinttypes> #include <cinttypes>
// Datasheet: https://www.belling.com.cn/media/file_object/bel_product/BL0942/datasheet/BL0942_V1.06_en.pdf
namespace esphome { namespace esphome {
namespace bl0942 { namespace bl0942 {
@ -12,33 +14,41 @@ static const uint8_t BL0942_FULL_PACKET = 0xAA;
static const uint8_t BL0942_PACKET_HEADER = 0x55; static const uint8_t BL0942_PACKET_HEADER = 0x55;
static const uint8_t BL0942_WRITE_COMMAND = 0xA8; static const uint8_t BL0942_WRITE_COMMAND = 0xA8;
static const uint8_t BL0942_REG_I_FAST_RMS_CTRL = 0x10;
static const uint8_t BL0942_REG_MODE = 0x18; static const uint8_t BL0942_REG_I_RMSOS = 0x12;
static const uint8_t BL0942_REG_SOFT_RESET = 0x19; static const uint8_t BL0942_REG_WA_CREEP = 0x14;
static const uint8_t BL0942_REG_USR_WRPROT = 0x1A; static const uint8_t BL0942_REG_I_FAST_RMS_TH = 0x15;
static const uint8_t BL0942_REG_I_FAST_RMS_CYC = 0x16;
static const uint8_t BL0942_REG_FREQ_CYC = 0x17;
static const uint8_t BL0942_REG_OT_FUNX = 0x18;
static const uint8_t BL0942_REG_MODE = 0x19;
static const uint8_t BL0942_REG_SOFT_RESET = 0x1C;
static const uint8_t BL0942_REG_USR_WRPROT = 0x1D;
static const uint8_t BL0942_REG_TPS_CTRL = 0x1B; static const uint8_t BL0942_REG_TPS_CTRL = 0x1B;
// TODO: Confirm insialisation works as intended static const uint32_t BL0942_REG_MODE_RESV = 0x03;
const uint8_t BL0942_INIT[5][6] = { static const uint32_t BL0942_REG_MODE_CF_EN = 0x04;
// Reset to default static const uint32_t BL0942_REG_MODE_RMS_UPDATE_SEL = 0x08;
{BL0942_WRITE_COMMAND, BL0942_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38}, static const uint32_t BL0942_REG_MODE_FAST_RMS_SEL = 0x10;
// Enable User Operation Write static const uint32_t BL0942_REG_MODE_AC_FREQ_SEL = 0x20;
{BL0942_WRITE_COMMAND, BL0942_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0}, static const uint32_t BL0942_REG_MODE_CF_CNT_CLR_SEL = 0x40;
// 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS static const uint32_t BL0942_REG_MODE_CF_CNT_ADD_SEL = 0x80;
{BL0942_WRITE_COMMAND, BL0942_REG_MODE, 0x00, 0x10, 0x00, 0x37}, static const uint32_t BL0942_REG_MODE_UART_RATE_19200 = 0x200;
// 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS static const uint32_t BL0942_REG_MODE_UART_RATE_38400 = 0x300;
{BL0942_WRITE_COMMAND, BL0942_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE}, static const uint32_t BL0942_REG_MODE_DEFAULT =
// 0x181C = Half cycle, Fast RMS threshold 6172 BL0942_REG_MODE_RESV | BL0942_REG_MODE_CF_EN | BL0942_REG_MODE_CF_CNT_ADD_SEL;
{BL0942_WRITE_COMMAND, BL0942_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}};
static const uint32_t BL0942_REG_SOFT_RESET_MAGIC = 0x5a5a5a;
static const uint32_t BL0942_REG_USR_WRPROT_MAGIC = 0x55;
void BL0942::loop() { void BL0942::loop() {
DataPacket buffer; DataPacket buffer;
if (!this->available()) { if (!this->available()) {
return; return;
} }
if (read_array((uint8_t *) &buffer, sizeof(buffer))) { if (this->read_array((uint8_t *) &buffer, sizeof(buffer))) {
if (validate_checksum(&buffer)) { if (this->validate_checksum_(&buffer)) {
received_package_(&buffer); this->received_package_(&buffer);
} }
} else { } else {
ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
@ -47,8 +57,8 @@ void BL0942::loop() {
} }
} }
bool BL0942::validate_checksum(DataPacket *data) { bool BL0942::validate_checksum_(DataPacket *data) {
uint8_t checksum = BL0942_READ_COMMAND; uint8_t checksum = BL0942_READ_COMMAND | this->address_;
// Whole package but checksum // Whole package but checksum
uint8_t *raw = (uint8_t *) data; uint8_t *raw = (uint8_t *) data;
for (uint32_t i = 0; i < sizeof(*data) - 1; i++) { for (uint32_t i = 0; i < sizeof(*data) - 1; i++) {
@ -61,17 +71,58 @@ bool BL0942::validate_checksum(DataPacket *data) {
return checksum == data->checksum; return checksum == data->checksum;
} }
void BL0942::update() { void BL0942::write_reg_(uint8_t reg, uint32_t val) {
uint8_t pkt[6];
this->flush(); this->flush();
this->write_byte(BL0942_READ_COMMAND); pkt[0] = BL0942_WRITE_COMMAND | this->address_;
pkt[1] = reg;
pkt[2] = (val & 0xff);
pkt[3] = (val >> 8) & 0xff;
pkt[4] = (val >> 16) & 0xff;
pkt[5] = (pkt[0] + pkt[1] + pkt[2] + pkt[3] + pkt[4]) ^ 0xff;
this->write_array(pkt, 6);
delay(1);
}
int BL0942::read_reg_(uint8_t reg) {
union {
uint8_t b[4];
uint32_le_t le32;
} resp;
this->write_byte(BL0942_READ_COMMAND | this->address_);
this->write_byte(reg);
this->flush();
if (this->read_array(resp.b, 4) &&
resp.b[3] ==
(uint8_t) ((BL0942_READ_COMMAND + this->address_ + reg + resp.b[0] + resp.b[1] + resp.b[2]) ^ 0xff)) {
resp.b[3] = 0;
return resp.le32;
}
return -1;
}
void BL0942::update() {
this->write_byte(BL0942_READ_COMMAND | this->address_);
this->write_byte(BL0942_FULL_PACKET); this->write_byte(BL0942_FULL_PACKET);
} }
void BL0942::setup() { void BL0942::setup() {
for (auto *i : BL0942_INIT) { this->write_reg_(BL0942_REG_USR_WRPROT, BL0942_REG_USR_WRPROT_MAGIC);
this->write_array(i, 6); this->write_reg_(BL0942_REG_SOFT_RESET, BL0942_REG_SOFT_RESET_MAGIC);
delay(1);
} uint32_t mode = BL0942_REG_MODE_DEFAULT;
mode |= BL0942_REG_MODE_RMS_UPDATE_SEL; /* 800ms refresh time */
if (this->line_freq_ == LINE_FREQUENCY_60HZ)
mode |= BL0942_REG_MODE_AC_FREQ_SEL;
this->write_reg_(BL0942_REG_MODE, mode);
this->write_reg_(BL0942_REG_USR_WRPROT, 0);
if (this->read_reg_(BL0942_REG_MODE) != mode)
this->status_set_warning("BL0942 setup failed!");
this->flush(); this->flush();
} }
@ -104,13 +155,15 @@ void BL0942::received_package_(DataPacket *data) {
if (frequency_sensor_ != nullptr) { if (frequency_sensor_ != nullptr) {
frequency_sensor_->publish_state(frequency); frequency_sensor_->publish_state(frequency);
} }
this->status_clear_warning();
ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, frequency %fHz, status 0x%08X", v_rms, i_rms, ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, frequency %fHz, status 0x%08X", v_rms, i_rms,
watt, cf_cnt, total_energy_consumption, frequency, data->status); watt, cf_cnt, total_energy_consumption, frequency, data->status);
} }
void BL0942::dump_config() { // NOLINT(readability-function-cognitive-complexity) void BL0942::dump_config() { // NOLINT(readability-function-cognitive-complexity)
ESP_LOGCONFIG(TAG, "BL0942:"); ESP_LOGCONFIG(TAG, "BL0942:");
ESP_LOGCONFIG(TAG, " Address: %d", this->address_);
ESP_LOGCONFIG(TAG, " Nominal line frequency: %d Hz", this->line_freq_);
LOG_SENSOR("", "Voltage", this->voltage_sensor_); LOG_SENSOR("", "Voltage", this->voltage_sensor_);
LOG_SENSOR("", "Current", this->current_sensor_); LOG_SENSOR("", "Current", this->current_sensor_);
LOG_SENSOR("", "Power", this->power_sensor_); LOG_SENSOR("", "Power", this->power_sensor_);

View file

@ -28,6 +28,11 @@ struct DataPacket {
uint8_t checksum; uint8_t checksum;
} __attribute__((packed)); } __attribute__((packed));
enum LineFrequency : uint8_t {
LINE_FREQUENCY_50HZ = 50,
LINE_FREQUENCY_60HZ = 60,
};
class BL0942 : public PollingComponent, public uart::UARTDevice { class BL0942 : public PollingComponent, public uart::UARTDevice {
public: public:
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
@ -35,9 +40,10 @@ class BL0942 : public PollingComponent, public uart::UARTDevice {
void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; } void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; }
void set_line_freq(LineFrequency freq) { this->line_freq_ = freq; }
void set_address(uint8_t address) { this->address_ = address; }
void loop() override; void loop() override;
void update() override; void update() override;
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;
@ -59,9 +65,12 @@ class BL0942 : public PollingComponent, public uart::UARTDevice {
float current_reference_ = BL0942_IREF; float current_reference_ = BL0942_IREF;
// Divide by this to turn into kWh // Divide by this to turn into kWh
float energy_reference_ = BL0942_EREF; float energy_reference_ = BL0942_EREF;
uint8_t address_ = 0;
LineFrequency line_freq_ = LINE_FREQUENCY_50HZ;
static bool validate_checksum(DataPacket *data); bool validate_checksum_(DataPacket *data);
int read_reg_(uint8_t reg);
void write_reg_(uint8_t reg, uint32_t val);
void received_package_(DataPacket *data); void received_package_(DataPacket *data);
}; };
} // namespace bl0942 } // namespace bl0942

View file

@ -1,25 +1,27 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, uart from esphome.components import sensor, uart
import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
CONF_ADDRESS,
CONF_CURRENT, CONF_CURRENT,
CONF_ENERGY, CONF_ENERGY,
CONF_FREQUENCY,
CONF_ID, CONF_ID,
CONF_LINE_FREQUENCY,
CONF_POWER, CONF_POWER,
CONF_VOLTAGE, CONF_VOLTAGE,
CONF_FREQUENCY,
DEVICE_CLASS_CURRENT, DEVICE_CLASS_CURRENT,
DEVICE_CLASS_ENERGY, DEVICE_CLASS_ENERGY,
DEVICE_CLASS_FREQUENCY,
DEVICE_CLASS_POWER, DEVICE_CLASS_POWER,
DEVICE_CLASS_VOLTAGE, DEVICE_CLASS_VOLTAGE,
DEVICE_CLASS_FREQUENCY,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
UNIT_AMPERE, UNIT_AMPERE,
UNIT_HERTZ,
UNIT_KILOWATT_HOURS, UNIT_KILOWATT_HOURS,
UNIT_VOLT, UNIT_VOLT,
UNIT_WATT, UNIT_WATT,
UNIT_HERTZ,
STATE_CLASS_TOTAL_INCREASING,
) )
DEPENDENCIES = ["uart"] DEPENDENCIES = ["uart"]
@ -27,6 +29,12 @@ DEPENDENCIES = ["uart"]
bl0942_ns = cg.esphome_ns.namespace("bl0942") bl0942_ns = cg.esphome_ns.namespace("bl0942")
BL0942 = bl0942_ns.class_("BL0942", cg.PollingComponent, uart.UARTDevice) BL0942 = bl0942_ns.class_("BL0942", cg.PollingComponent, uart.UARTDevice)
LineFrequency = bl0942_ns.enum("LineFrequency")
LINE_FREQS = {
50: LineFrequency.LINE_FREQUENCY_50HZ,
60: LineFrequency.LINE_FREQUENCY_60HZ,
}
CONFIG_SCHEMA = ( CONFIG_SCHEMA = (
cv.Schema( cv.Schema(
{ {
@ -61,6 +69,14 @@ CONFIG_SCHEMA = (
device_class=DEVICE_CLASS_FREQUENCY, device_class=DEVICE_CLASS_FREQUENCY,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
cv.Optional(CONF_LINE_FREQUENCY, default="50HZ"): cv.All(
cv.frequency,
cv.enum(
LINE_FREQS,
int=True,
),
),
cv.Optional(CONF_ADDRESS, default=0): cv.int_range(min=0, max=3),
} }
) )
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
@ -88,3 +104,5 @@ async def to_code(config):
if frequency_config := config.get(CONF_FREQUENCY): if frequency_config := config.get(CONF_FREQUENCY):
sens = await sensor.new_sensor(frequency_config) sens = await sensor.new_sensor(frequency_config)
cg.add(var.set_frequency_sensor(sens)) cg.add(var.set_frequency_sensor(sens))
cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY]))
cg.add(var.set_address(config[CONF_ADDRESS]))

View file

@ -0,0 +1,22 @@
uart:
- id: uart_bl0942
tx_pin:
number: TX1
rx_pin:
number: RX1
baud_rate: 2400
sensor:
- platform: bl0942
address: 0
line_frequency: 50Hz
voltage:
name: BL0942 Voltage
current:
name: BL0942 Current
power:
name: BL0942 Power
energy:
name: BL0942 Energy
frequency:
name: BL0942 Frequency