mirror of
https://github.com/esphome/esphome.git
synced 2024-12-25 17:07:50 +01:00
Support BL0942 calibration (#7299)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
e882cea47e
commit
71a7f6383f
@ -61,7 +61,7 @@ esphome/components/bk72xx/* @kuba2k2
|
||||
esphome/components/bl0906/* @athom-tech @jesserockz @tarontop
|
||||
esphome/components/bl0939/* @ziceva
|
||||
esphome/components/bl0940/* @tobias-
|
||||
esphome/components/bl0942/* @dbuezas
|
||||
esphome/components/bl0942/* @dbuezas @dwmw2
|
||||
esphome/components/ble_client/* @buxtronix @clydebarrow
|
||||
esphome/components/bluetooth_proxy/* @jesserockz
|
||||
esphome/components/bme280_base/* @esphome/core
|
||||
|
@ -1 +1 @@
|
||||
CODEOWNERS = ["@dbuezas"]
|
||||
CODEOWNERS = ["@dbuezas", "@dwmw2"]
|
||||
|
@ -122,6 +122,20 @@ void BL0942::update() {
|
||||
}
|
||||
|
||||
void BL0942::setup() {
|
||||
// If either current or voltage references are set explicitly by the user,
|
||||
// calculate the power reference from it unless that is also explicitly set.
|
||||
if ((this->current_reference_set_ || this->voltage_reference_set_) && !this->power_reference_set_) {
|
||||
this->power_reference_ = (this->voltage_reference_ * this->current_reference_ * 3537.0 / 305978.0) / 73989.0;
|
||||
this->power_reference_set_ = true;
|
||||
}
|
||||
|
||||
// Similarly for energy reference, if the power reference was set by the user
|
||||
// either implicitly or explicitly.
|
||||
if (this->power_reference_set_ && !this->energy_reference_set_) {
|
||||
this->energy_reference_ = this->power_reference_ * 3600000 / 419430.4;
|
||||
this->energy_reference_set_ = true;
|
||||
}
|
||||
|
||||
this->write_reg_(BL0942_REG_USR_WRPROT, BL0942_REG_USR_WRPROT_MAGIC);
|
||||
this->write_reg_(BL0942_REG_SOFT_RESET, BL0942_REG_SOFT_RESET_MAGIC);
|
||||
|
||||
@ -184,11 +198,15 @@ void BL0942::dump_config() { // NOLINT(readability-function-cognitive-complexit
|
||||
ESP_LOGCONFIG(TAG, "BL0942:");
|
||||
ESP_LOGCONFIG(TAG, " Address: %d", this->address_);
|
||||
ESP_LOGCONFIG(TAG, " Nominal line frequency: %d Hz", this->line_freq_);
|
||||
ESP_LOGCONFIG(TAG, " Current reference: %f", this->current_reference_);
|
||||
ESP_LOGCONFIG(TAG, " Energy reference: %f", this->energy_reference_);
|
||||
ESP_LOGCONFIG(TAG, " Power reference: %f", this->power_reference_);
|
||||
ESP_LOGCONFIG(TAG, " Voltage reference: %f", this->voltage_reference_);
|
||||
LOG_SENSOR("", "Voltage", this->voltage_sensor_);
|
||||
LOG_SENSOR("", "Current", this->current_sensor_);
|
||||
LOG_SENSOR("", "Power", this->power_sensor_);
|
||||
LOG_SENSOR("", "Energy", this->energy_sensor_);
|
||||
LOG_SENSOR("", "frequency", this->frequency_sensor_);
|
||||
LOG_SENSOR("", "Frequency", this->frequency_sensor_);
|
||||
}
|
||||
|
||||
} // namespace bl0942
|
||||
|
@ -8,6 +8,57 @@
|
||||
namespace esphome {
|
||||
namespace bl0942 {
|
||||
|
||||
// The BL0942 IC is "calibration-free", which means that it doesn't care
|
||||
// at all about calibration, and that's left to software. It measures a
|
||||
// voltage differential on its IP/IN pins which linearly proportional to
|
||||
// the current flow, and another on its VP pin which is proportional to
|
||||
// the line voltage. It never knows the actual calibration; the values
|
||||
// it reports are solely in terms of those inputs.
|
||||
//
|
||||
// The datasheet refers to the input voltages as I(A) and V(V), both
|
||||
// in millivolts. It measures them against a reference voltage Vref,
|
||||
// which is typically 1.218V (but that absolute value is meaningless
|
||||
// without the actual calibration anyway).
|
||||
//
|
||||
// The reported I_RMS value is 305978 I(A)/Vref, and the reported V_RMS
|
||||
// value is 73989 V(V)/Vref. So we can calibrate those by applying a
|
||||
// simple meter with a resistive load.
|
||||
//
|
||||
// The chip also measures the phase difference between voltage and
|
||||
// current, and uses it to calculate the power factor (cos φ). It
|
||||
// reports the WATT value of 3537 * I_RMS * V_RMS * cos φ).
|
||||
//
|
||||
// It also integrates total energy based on the WATT value. The time for
|
||||
// one CF_CNT pulse is 1638.4*256 / WATT.
|
||||
//
|
||||
// So... how do we calibrate that?
|
||||
//
|
||||
// Using a simple resistive load and an external meter, we can measure
|
||||
// the true voltage and current for a given V_RMS and I_RMS reading,
|
||||
// to calculate BL0942_UREF and BL0942_IREF. Those are in units of
|
||||
// "305978 counts per amp" or "73989 counts per volt" respectively.
|
||||
//
|
||||
// We can derive BL0942_PREF from those. Let's eliminate the weird
|
||||
// factors and express the calibration in plain counts per volt/amp:
|
||||
// UREF1 = UREF/73989, IREF1 = IREF/305978.
|
||||
//
|
||||
// Next... the true power in Watts is V * I * cos φ, so that's equal
|
||||
// to WATT/3537 * IREF1 * UREF1. Which means
|
||||
// BL0942_PREF = BL0942_UREF * BL0942_IREF * 3537 / 305978 / 73989.
|
||||
//
|
||||
// Finally the accumulated energy. The period of a CF_CNT count is
|
||||
// 1638.4*256 / WATT seconds, or 419230.4 / WATT seconds. Which means
|
||||
// the energy represented by a CN_CNT pulse is 419230.4 WATT-seconds.
|
||||
// Factoring in the calibration, that's 419230.4 / BL0942_PREF actual
|
||||
// Watt-seconds (or Joules, as the physicists like to call them).
|
||||
//
|
||||
// But we're not being physicists today; we we're being engineers, so
|
||||
// we want to convert to kWh instead. Which we do by dividing by 1000
|
||||
// and then by 3600, so the energy in kWh is
|
||||
// CF_CNT * 419230.4 / BL0942_PREF / 3600000
|
||||
//
|
||||
// Which makes BL0952_EREF = BL0942_PREF * 3600000 / 419430.4
|
||||
|
||||
static const float BL0942_PREF = 596; // taken from tasmota
|
||||
static const float BL0942_UREF = 15873.35944299; // should be 73989/1.218
|
||||
static const float BL0942_IREF = 251213.46469622; // 305978/1.218
|
||||
@ -42,6 +93,22 @@ class BL0942 : public PollingComponent, public uart::UARTDevice {
|
||||
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 set_current_reference(float current_ref) {
|
||||
this->current_reference_ = current_ref;
|
||||
this->current_reference_set_ = true;
|
||||
}
|
||||
void set_energy_reference(float energy_ref) {
|
||||
this->energy_reference_ = energy_ref;
|
||||
this->energy_reference_set_ = true;
|
||||
}
|
||||
void set_power_reference(float power_ref) {
|
||||
this->power_reference_ = power_ref;
|
||||
this->power_reference_set_ = true;
|
||||
}
|
||||
void set_voltage_reference(float voltage_ref) {
|
||||
this->voltage_reference_ = voltage_ref;
|
||||
this->voltage_reference_set_ = true;
|
||||
}
|
||||
|
||||
void loop() override;
|
||||
void update() override;
|
||||
@ -59,12 +126,16 @@ class BL0942 : public PollingComponent, public uart::UARTDevice {
|
||||
|
||||
// Divide by this to turn into Watt
|
||||
float power_reference_ = BL0942_PREF;
|
||||
bool power_reference_set_ = false;
|
||||
// Divide by this to turn into Volt
|
||||
float voltage_reference_ = BL0942_UREF;
|
||||
bool voltage_reference_set_ = false;
|
||||
// Divide by this to turn into Ampere
|
||||
float current_reference_ = BL0942_IREF;
|
||||
bool current_reference_set_ = false;
|
||||
// Divide by this to turn into kWh
|
||||
float energy_reference_ = BL0942_EREF;
|
||||
bool energy_reference_set_ = false;
|
||||
uint8_t address_ = 0;
|
||||
LineFrequency line_freq_ = LINE_FREQUENCY_50HZ;
|
||||
uint32_t rx_start_ = 0;
|
||||
|
@ -24,6 +24,11 @@ from esphome.const import (
|
||||
UNIT_WATT,
|
||||
)
|
||||
|
||||
CONF_CURRENT_REFERENCE = "current_reference"
|
||||
CONF_ENERGY_REFERENCE = "energy_reference"
|
||||
CONF_POWER_REFERENCE = "power_reference"
|
||||
CONF_VOLTAGE_REFERENCE = "voltage_reference"
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
bl0942_ns = cg.esphome_ns.namespace("bl0942")
|
||||
@ -77,6 +82,10 @@ CONFIG_SCHEMA = (
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_ADDRESS, default=0): cv.int_range(min=0, max=3),
|
||||
cv.Optional(CONF_CURRENT_REFERENCE): cv.float_,
|
||||
cv.Optional(CONF_ENERGY_REFERENCE): cv.float_,
|
||||
cv.Optional(CONF_POWER_REFERENCE): cv.float_,
|
||||
cv.Optional(CONF_VOLTAGE_REFERENCE): cv.float_,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
@ -106,3 +115,11 @@ async def to_code(config):
|
||||
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]))
|
||||
if (current_reference := config.get(CONF_CURRENT_REFERENCE, None)) is not None:
|
||||
cg.add(var.set_current_reference(current_reference))
|
||||
if (voltage_reference := config.get(CONF_VOLTAGE_REFERENCE, None)) is not None:
|
||||
cg.add(var.set_voltage_reference(voltage_reference))
|
||||
if (power_reference := config.get(CONF_POWER_REFERENCE, None)) is not None:
|
||||
cg.add(var.set_power_reference(power_reference))
|
||||
if (energy_reference := config.get(CONF_ENERGY_REFERENCE, None)) is not None:
|
||||
cg.add(var.set_energy_reference(energy_reference))
|
||||
|
@ -20,3 +20,7 @@ sensor:
|
||||
name: BL0942 Energy
|
||||
frequency:
|
||||
name: BL0942 Frequency
|
||||
voltage_reference: 15968
|
||||
current_reference: 124180
|
||||
power_reference: 309.1
|
||||
energy_reference: 2653
|
||||
|
@ -18,3 +18,5 @@ sensor:
|
||||
name: BL0942 Energy
|
||||
frequency:
|
||||
name: BL0942 Frequency
|
||||
voltage_reference: 15968
|
||||
current_reference: 124180
|
||||
|
Loading…
Reference in New Issue
Block a user