ESPHome  2024.12.2
bl0942.cpp
Go to the documentation of this file.
1 #include "bl0942.h"
2 #include "esphome/core/log.h"
3 #include <cinttypes>
4 
5 // Datasheet: https://www.belling.com.cn/media/file_object/bel_product/BL0942/datasheet/BL0942_V1.06_en.pdf
6 
7 namespace esphome {
8 namespace bl0942 {
9 
10 static const char *const TAG = "bl0942";
11 
12 static const uint8_t BL0942_READ_COMMAND = 0x58;
13 static const uint8_t BL0942_FULL_PACKET = 0xAA;
14 static const uint8_t BL0942_PACKET_HEADER = 0x55;
15 
16 static const uint8_t BL0942_WRITE_COMMAND = 0xA8;
17 
18 static const uint8_t BL0942_REG_I_RMSOS = 0x12;
19 static const uint8_t BL0942_REG_WA_CREEP = 0x14;
20 static const uint8_t BL0942_REG_I_FAST_RMS_TH = 0x15;
21 static const uint8_t BL0942_REG_I_FAST_RMS_CYC = 0x16;
22 static const uint8_t BL0942_REG_FREQ_CYC = 0x17;
23 static const uint8_t BL0942_REG_OT_FUNX = 0x18;
24 static const uint8_t BL0942_REG_MODE = 0x19;
25 static const uint8_t BL0942_REG_SOFT_RESET = 0x1C;
26 static const uint8_t BL0942_REG_USR_WRPROT = 0x1D;
27 static const uint8_t BL0942_REG_TPS_CTRL = 0x1B;
28 
29 static const uint32_t BL0942_REG_MODE_RESV = 0x03;
30 static const uint32_t BL0942_REG_MODE_CF_EN = 0x04;
31 static const uint32_t BL0942_REG_MODE_RMS_UPDATE_SEL = 0x08;
32 static const uint32_t BL0942_REG_MODE_FAST_RMS_SEL = 0x10;
33 static const uint32_t BL0942_REG_MODE_AC_FREQ_SEL = 0x20;
34 static const uint32_t BL0942_REG_MODE_CF_CNT_CLR_SEL = 0x40;
35 static const uint32_t BL0942_REG_MODE_CF_CNT_ADD_SEL = 0x80;
36 static const uint32_t BL0942_REG_MODE_UART_RATE_19200 = 0x200;
37 static const uint32_t BL0942_REG_MODE_UART_RATE_38400 = 0x300;
38 static const uint32_t BL0942_REG_MODE_DEFAULT =
39  BL0942_REG_MODE_RESV | BL0942_REG_MODE_CF_EN | BL0942_REG_MODE_CF_CNT_ADD_SEL;
40 
41 static const uint32_t BL0942_REG_SOFT_RESET_MAGIC = 0x5a5a5a;
42 static const uint32_t BL0942_REG_USR_WRPROT_MAGIC = 0x55;
43 
44 // 23-byte packet, 11 bits per byte, 2400 baud: about 105ms
45 static const uint32_t PKT_TIMEOUT_MS = 200;
46 
47 void BL0942::loop() {
48  DataPacket buffer;
49  int avail = this->available();
50 
51  if (!avail) {
52  return;
53  }
54  if (avail < sizeof(buffer)) {
55  if (!this->rx_start_) {
56  this->rx_start_ = millis();
57  } else if (millis() > this->rx_start_ + PKT_TIMEOUT_MS) {
58  ESP_LOGW(TAG, "Junk on wire. Throwing away partial message (%d bytes)", avail);
59  this->read_array((uint8_t *) &buffer, avail);
60  this->rx_start_ = 0;
61  }
62  return;
63  }
64 
65  if (this->read_array((uint8_t *) &buffer, sizeof(buffer))) {
66  if (this->validate_checksum_(&buffer)) {
67  this->received_package_(&buffer);
68  }
69  }
70  this->rx_start_ = 0;
71 }
72 
74  uint8_t checksum = BL0942_READ_COMMAND | this->address_;
75  // Whole package but checksum
76  uint8_t *raw = (uint8_t *) data;
77  for (uint32_t i = 0; i < sizeof(*data) - 1; i++) {
78  checksum += raw[i];
79  }
80  checksum ^= 0xFF;
81  if (checksum != data->checksum) {
82  ESP_LOGW(TAG, "BL0942 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
83  }
84  return checksum == data->checksum;
85 }
86 
87 void BL0942::write_reg_(uint8_t reg, uint32_t val) {
88  uint8_t pkt[6];
89 
90  this->flush();
91  pkt[0] = BL0942_WRITE_COMMAND | this->address_;
92  pkt[1] = reg;
93  pkt[2] = (val & 0xff);
94  pkt[3] = (val >> 8) & 0xff;
95  pkt[4] = (val >> 16) & 0xff;
96  pkt[5] = (pkt[0] + pkt[1] + pkt[2] + pkt[3] + pkt[4]) ^ 0xff;
97  this->write_array(pkt, 6);
98  delay(1);
99 }
100 
101 int BL0942::read_reg_(uint8_t reg) {
102  union {
103  uint8_t b[4];
104  uint32_le_t le32;
105  } resp;
106 
107  this->write_byte(BL0942_READ_COMMAND | this->address_);
108  this->write_byte(reg);
109  this->flush();
110  if (this->read_array(resp.b, 4) &&
111  resp.b[3] ==
112  (uint8_t) ((BL0942_READ_COMMAND + this->address_ + reg + resp.b[0] + resp.b[1] + resp.b[2]) ^ 0xff)) {
113  resp.b[3] = 0;
114  return resp.le32;
115  }
116  return -1;
117 }
118 
120  this->write_byte(BL0942_READ_COMMAND | this->address_);
121  this->write_byte(BL0942_FULL_PACKET);
122 }
123 
125  // If either current or voltage references are set explicitly by the user,
126  // calculate the power reference from it unless that is also explicitly set.
127  if ((this->current_reference_set_ || this->voltage_reference_set_) && !this->power_reference_set_) {
128  this->power_reference_ = (this->voltage_reference_ * this->current_reference_ * 3537.0 / 305978.0) / 73989.0;
129  this->power_reference_set_ = true;
130  }
131 
132  // Similarly for energy reference, if the power reference was set by the user
133  // either implicitly or explicitly.
134  if (this->power_reference_set_ && !this->energy_reference_set_) {
135  this->energy_reference_ = this->power_reference_ * 3600000 / 419430.4;
136  this->energy_reference_set_ = true;
137  }
138 
139  this->write_reg_(BL0942_REG_USR_WRPROT, BL0942_REG_USR_WRPROT_MAGIC);
140  if (this->reset_)
141  this->write_reg_(BL0942_REG_SOFT_RESET, BL0942_REG_SOFT_RESET_MAGIC);
142 
143  uint32_t mode = BL0942_REG_MODE_DEFAULT;
144  mode |= BL0942_REG_MODE_RMS_UPDATE_SEL; /* 800ms refresh time */
145  if (this->line_freq_ == LINE_FREQUENCY_60HZ)
146  mode |= BL0942_REG_MODE_AC_FREQ_SEL;
147  this->write_reg_(BL0942_REG_MODE, mode);
148 
149  this->write_reg_(BL0942_REG_USR_WRPROT, 0);
150 
151  if (this->read_reg_(BL0942_REG_MODE) != mode)
152  this->status_set_warning("BL0942 setup failed!");
153 
154  this->flush();
155 }
156 
158  // Bad header
159  if (data->frame_header != BL0942_PACKET_HEADER) {
160  ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header);
161  return;
162  }
163 
164  // cf_cnt is only 24 bits, so track overflows
165  uint32_t cf_cnt = (uint24_t) data->cf_cnt;
166  cf_cnt |= this->prev_cf_cnt_ & 0xff000000;
167  if (cf_cnt < this->prev_cf_cnt_) {
168  cf_cnt += 0x1000000;
169  }
170  this->prev_cf_cnt_ = cf_cnt;
171 
172  float v_rms = (uint24_t) data->v_rms / voltage_reference_;
173  float i_rms = (uint24_t) data->i_rms / current_reference_;
174  float watt = (int24_t) data->watt / power_reference_;
175  float total_energy_consumption = cf_cnt / energy_reference_;
176  float frequency = 1000000.0f / data->frequency;
177 
178  if (voltage_sensor_ != nullptr) {
180  }
181  if (current_sensor_ != nullptr) {
183  }
184  if (power_sensor_ != nullptr) {
186  }
187  if (energy_sensor_ != nullptr) {
188  energy_sensor_->publish_state(total_energy_consumption);
189  }
190  if (frequency_sensor_ != nullptr) {
192  }
193  this->status_clear_warning();
194  ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, frequency %fHz, status 0x%08X", v_rms, i_rms,
195  watt, cf_cnt, total_energy_consumption, frequency, data->status);
196 }
197 
198 void BL0942::dump_config() { // NOLINT(readability-function-cognitive-complexity)
199  ESP_LOGCONFIG(TAG, "BL0942:");
200  ESP_LOGCONFIG(TAG, " Reset: %s", TRUEFALSE(this->reset_));
201  ESP_LOGCONFIG(TAG, " Address: %d", this->address_);
202  ESP_LOGCONFIG(TAG, " Nominal line frequency: %d Hz", this->line_freq_);
203  ESP_LOGCONFIG(TAG, " Current reference: %f", this->current_reference_);
204  ESP_LOGCONFIG(TAG, " Energy reference: %f", this->energy_reference_);
205  ESP_LOGCONFIG(TAG, " Power reference: %f", this->power_reference_);
206  ESP_LOGCONFIG(TAG, " Voltage reference: %f", this->voltage_reference_);
207  LOG_SENSOR("", "Voltage", this->voltage_sensor_);
208  LOG_SENSOR("", "Current", this->current_sensor_);
209  LOG_SENSOR("", "Power", this->power_sensor_);
210  LOG_SENSOR("", "Energy", this->energy_sensor_);
211  LOG_SENSOR("", "Frequency", this->frequency_sensor_);
212 }
213 
214 } // namespace bl0942
215 } // namespace esphome
ube24_t i_rms
Definition: bl0940.h:21
void setup() override
Definition: bl0942.cpp:124
sensor::Sensor * frequency_sensor_
Definition: bl0942.h:126
24-bit unsigned integer type, transparently converting to 32-bit.
Definition: datatypes.h:32
uint8_t raw[35]
Definition: bl0939.h:19
ube24_t cf_cnt
Definition: bl0940.h:27
optional< std::array< uint8_t, N > > read_array()
Definition: uart.h:33
void write_array(const uint8_t *data, size_t len)
Definition: uart.h:21
void status_set_warning(const char *message="unspecified")
Definition: component.cpp:151
void write_byte(uint8_t data)
Definition: uart.h:19
bool validate_checksum_(DataPacket *data)
Definition: bl0942.cpp:73
uint8_t checksum
Definition: bl0906.h:210
sensor::Sensor * power_sensor_
Definition: bl0942.h:124
void loop() override
Definition: bl0942.cpp:47
uint16_le_t frequency
Definition: bl0942.h:74
sensor::Sensor * current_sensor_
Definition: bl0942.h:121
mopeka_std_values val[4]
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
int read_reg_(uint8_t reg)
Definition: bl0942.cpp:101
uint32_t prev_cf_cnt_
Definition: bl0942.h:144
void status_clear_warning()
Definition: component.cpp:166
24-bit signed integer type, transparently converting to 32-bit.
Definition: datatypes.h:38
BedjetMode mode
BedJet operating mode.
Definition: bedjet_codec.h:183
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
LineFrequency line_freq_
Definition: bl0942.h:142
void update() override
Definition: bl0942.cpp:119
sensor::Sensor * voltage_sensor_
Definition: bl0942.h:120
uint16_le_t frequency
Definition: bl0942.h:72
sensor::Sensor * energy_sensor_
Definition: bl0942.h:125
void write_reg_(uint8_t reg, uint32_t val)
Definition: bl0942.cpp:87
void dump_config() override
Definition: bl0942.cpp:198
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
Wrapper class for memory using big endian data layout, transparently converting it to native order...
Definition: datatypes.h:21
void received_package_(DataPacket *data)
Definition: bl0942.cpp:157
sbe24_t watt
Definition: bl0940.h:25
ube24_t v_rms
Definition: bl0939.h:25
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26