ESPHome  2024.12.2
bl0942.h
Go to the documentation of this file.
1 #pragma once
2 
7 
8 namespace esphome {
9 namespace bl0942 {
10 
11 // The BL0942 IC is "calibration-free", which means that it doesn't care
12 // at all about calibration, and that's left to software. It measures a
13 // voltage differential on its IP/IN pins which linearly proportional to
14 // the current flow, and another on its VP pin which is proportional to
15 // the line voltage. It never knows the actual calibration; the values
16 // it reports are solely in terms of those inputs.
17 //
18 // The datasheet refers to the input voltages as I(A) and V(V), both
19 // in millivolts. It measures them against a reference voltage Vref,
20 // which is typically 1.218V (but that absolute value is meaningless
21 // without the actual calibration anyway).
22 //
23 // The reported I_RMS value is 305978 I(A)/Vref, and the reported V_RMS
24 // value is 73989 V(V)/Vref. So we can calibrate those by applying a
25 // simple meter with a resistive load.
26 //
27 // The chip also measures the phase difference between voltage and
28 // current, and uses it to calculate the power factor (cos φ). It
29 // reports the WATT value of 3537 * I_RMS * V_RMS * cos φ).
30 //
31 // It also integrates total energy based on the WATT value. The time for
32 // one CF_CNT pulse is 1638.4*256 / WATT.
33 //
34 // So... how do we calibrate that?
35 //
36 // Using a simple resistive load and an external meter, we can measure
37 // the true voltage and current for a given V_RMS and I_RMS reading,
38 // to calculate BL0942_UREF and BL0942_IREF. Those are in units of
39 // "305978 counts per amp" or "73989 counts per volt" respectively.
40 //
41 // We can derive BL0942_PREF from those. Let's eliminate the weird
42 // factors and express the calibration in plain counts per volt/amp:
43 // UREF1 = UREF/73989, IREF1 = IREF/305978.
44 //
45 // Next... the true power in Watts is V * I * cos φ, so that's equal
46 // to WATT/3537 * IREF1 * UREF1. Which means
47 // BL0942_PREF = BL0942_UREF * BL0942_IREF * 3537 / 305978 / 73989.
48 //
49 // Finally the accumulated energy. The period of a CF_CNT count is
50 // 1638.4*256 / WATT seconds, or 419230.4 / WATT seconds. Which means
51 // the energy represented by a CN_CNT pulse is 419230.4 WATT-seconds.
52 // Factoring in the calibration, that's 419230.4 / BL0942_PREF actual
53 // Watt-seconds (or Joules, as the physicists like to call them).
54 //
55 // But we're not being physicists today; we we're being engineers, so
56 // we want to convert to kWh instead. Which we do by dividing by 1000
57 // and then by 3600, so the energy in kWh is
58 // CF_CNT * 419230.4 / BL0942_PREF / 3600000
59 //
60 // Which makes BL0952_EREF = BL0942_PREF * 3600000 / 419430.4
61 
62 static const float BL0942_PREF = 596; // taken from tasmota
63 static const float BL0942_UREF = 15873.35944299; // should be 73989/1.218
64 static const float BL0942_IREF = 251213.46469622; // 305978/1.218
65 static const float BL0942_EREF = 3304.61127328; // Measured
66 
67 struct DataPacket {
68  uint8_t frame_header;
75  uint8_t reserved1;
76  uint8_t status;
77  uint8_t reserved2;
78  uint8_t reserved3;
79  uint8_t checksum;
80 } __attribute__((packed));
81 
82 enum LineFrequency : uint8_t {
85 };
86 
87 class BL0942 : public PollingComponent, public uart::UARTDevice {
88  public:
89  void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
90  void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; }
91  void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; }
92  void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; }
93  void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; }
94  void set_line_freq(LineFrequency freq) { this->line_freq_ = freq; }
95  void set_address(uint8_t address) { this->address_ = address; }
96  void set_reset(bool reset) { this->reset_ = reset; }
97  void set_current_reference(float current_ref) {
98  this->current_reference_ = current_ref;
99  this->current_reference_set_ = true;
100  }
101  void set_energy_reference(float energy_ref) {
102  this->energy_reference_ = energy_ref;
103  this->energy_reference_set_ = true;
104  }
105  void set_power_reference(float power_ref) {
106  this->power_reference_ = power_ref;
107  this->power_reference_set_ = true;
108  }
109  void set_voltage_reference(float voltage_ref) {
110  this->voltage_reference_ = voltage_ref;
111  this->voltage_reference_set_ = true;
112  }
113 
114  void loop() override;
115  void update() override;
116  void setup() override;
117  void dump_config() override;
118 
119  protected:
120  sensor::Sensor *voltage_sensor_{nullptr};
121  sensor::Sensor *current_sensor_{nullptr};
122  // NB This may be negative as the circuits is seemingly able to measure
123  // power in both directions
124  sensor::Sensor *power_sensor_{nullptr};
125  sensor::Sensor *energy_sensor_{nullptr};
126  sensor::Sensor *frequency_sensor_{nullptr};
127 
128  // Divide by this to turn into Watt
129  float power_reference_ = BL0942_PREF;
130  bool power_reference_set_ = false;
131  // Divide by this to turn into Volt
132  float voltage_reference_ = BL0942_UREF;
133  bool voltage_reference_set_ = false;
134  // Divide by this to turn into Ampere
135  float current_reference_ = BL0942_IREF;
136  bool current_reference_set_ = false;
137  // Divide by this to turn into kWh
138  float energy_reference_ = BL0942_EREF;
139  bool energy_reference_set_ = false;
140  uint8_t address_ = 0;
141  bool reset_ = false;
143  uint32_t rx_start_ = 0;
144  uint32_t prev_cf_cnt_ = 0;
145 
146  bool validate_checksum_(DataPacket *data);
147  int read_reg_(uint8_t reg);
148  void write_reg_(uint8_t reg, uint32_t val);
149  void received_package_(DataPacket *data);
150 };
151 } // namespace bl0942
152 } // namespace esphome
void setup()
void loop()
internal::LittleEndianLayout< uint16_t > uint16_le_t
Definition: datatypes.h:55
void set_voltage_reference(float voltage_ref)
Definition: bl0942.h:109
uint24_le_t i_fast_rms
Definition: bl0942.h:71
uint16_le_t frequency
Definition: bl0942.h:74
mopeka_std_values val[4]
enum esphome::bl0942::LineFrequency __attribute__
This class simplifies creating components that periodically check a state.
Definition: component.h:283
void set_address(uint8_t address)
Definition: bl0942.h:95
void set_power_reference(float power_ref)
Definition: bl0942.h:105
void set_current_sensor(sensor::Sensor *current_sensor)
Definition: bl0942.h:90
uint16_t reset
Definition: ina226.h:39
void set_frequency_sensor(sensor::Sensor *frequency_sensor)
Definition: bl0942.h:93
void set_line_freq(LineFrequency freq)
Definition: bl0942.h:94
internal::LittleEndianLayout< uint24_t > uint24_le_t
Definition: datatypes.h:54
void set_energy_sensor(sensor::Sensor *energy_sensor)
Definition: bl0942.h:92
void set_power_sensor(sensor::Sensor *power_sensor)
Definition: bl0942.h:91
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
uint8_t address
Definition: bl0906.h:211
internal::LittleEndianLayout< int24_t > int24_le_t
Definition: datatypes.h:58
void set_reset(bool reset)
Definition: bl0942.h:96
Base-class for all sensors.
Definition: sensor.h:57
void set_current_reference(float current_ref)
Definition: bl0942.h:97
void set_voltage_sensor(sensor::Sensor *voltage_sensor)
Definition: bl0942.h:89
void set_energy_reference(float energy_ref)
Definition: bl0942.h:101