ESPHome  2024.4.1
bl0939.cpp
Go to the documentation of this file.
1 #include "bl0939.h"
2 #include "esphome/core/log.h"
3 #include <cinttypes>
4 
5 namespace esphome {
6 namespace bl0939 {
7 
8 static const char *const TAG = "bl0939";
9 
10 // https://www.belling.com.cn/media/file_object/bel_product/BL0939/datasheet/BL0939_V1.2_cn.pdf
11 // (unfortunately chinese, but the protocol can be understood with some translation tool)
12 static const uint8_t BL0939_READ_COMMAND = 0x55; // 0x5{A4,A3,A2,A1}
13 static const uint8_t BL0939_FULL_PACKET = 0xAA;
14 static const uint8_t BL0939_PACKET_HEADER = 0x55;
15 
16 static const uint8_t BL0939_WRITE_COMMAND = 0xA5; // 0xA{A4,A3,A2,A1}
17 static const uint8_t BL0939_REG_IA_FAST_RMS_CTRL = 0x10;
18 static const uint8_t BL0939_REG_IB_FAST_RMS_CTRL = 0x1E;
19 static const uint8_t BL0939_REG_MODE = 0x18;
20 static const uint8_t BL0939_REG_SOFT_RESET = 0x19;
21 static const uint8_t BL0939_REG_USR_WRPROT = 0x1A;
22 static const uint8_t BL0939_REG_TPS_CTRL = 0x1B;
23 
24 const uint8_t BL0939_INIT[6][6] = {
25  // Reset to default
26  {BL0939_WRITE_COMMAND, BL0939_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x33},
27  // Enable User Operation Write
28  {BL0939_WRITE_COMMAND, BL0939_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xEB},
29  // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS
30  {BL0939_WRITE_COMMAND, BL0939_REG_MODE, 0x00, 0x10, 0x00, 0x32},
31  // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS
32  {BL0939_WRITE_COMMAND, BL0939_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xF9},
33  // 0x181C = Half cycle, Fast RMS threshold 6172
34  {BL0939_WRITE_COMMAND, BL0939_REG_IA_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x16},
35  // 0x181C = Half cycle, Fast RMS threshold 6172
36  {BL0939_WRITE_COMMAND, BL0939_REG_IB_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x08}};
37 
38 void BL0939::loop() {
39  DataPacket buffer;
40  if (!this->available()) {
41  return;
42  }
43  if (read_array((uint8_t *) &buffer, sizeof(buffer))) {
44  if (validate_checksum(&buffer)) {
45  received_package_(&buffer);
46  }
47  } else {
48  ESP_LOGW(TAG, "Junk on wire. Throwing away partial message");
49  while (read() >= 0)
50  ;
51  }
52 }
53 
55  uint8_t checksum = BL0939_READ_COMMAND;
56  // Whole package but checksum
57  for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) {
58  checksum += data->raw[i];
59  }
60  checksum ^= 0xFF;
61  if (checksum != data->checksum) {
62  ESP_LOGW(TAG, "BL0939 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum);
63  }
64  return checksum == data->checksum;
65 }
66 
68  this->flush();
69  this->write_byte(BL0939_READ_COMMAND);
70  this->write_byte(BL0939_FULL_PACKET);
71 }
72 
73 void BL0939::setup() {
74  for (auto *i : BL0939_INIT) {
75  this->write_array(i, 6);
76  delay(1);
77  }
78  this->flush();
79 }
80 
81 void BL0939::received_package_(const DataPacket *data) const {
82  // Bad header
83  if (data->frame_header != BL0939_PACKET_HEADER) {
84  ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header);
85  return;
86  }
87 
88  float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_;
89  float ia_rms = (float) to_uint32_t(data->ia_rms) / current_reference_;
90  float ib_rms = (float) to_uint32_t(data->ib_rms) / current_reference_;
91  float a_watt = (float) to_int32_t(data->a_watt) / power_reference_;
92  float b_watt = (float) to_int32_t(data->b_watt) / power_reference_;
93  int32_t cfa_cnt = to_int32_t(data->cfa_cnt);
94  int32_t cfb_cnt = to_int32_t(data->cfb_cnt);
95  float a_energy_consumption = (float) cfa_cnt / energy_reference_;
96  float b_energy_consumption = (float) cfb_cnt / energy_reference_;
97  float total_energy_consumption = a_energy_consumption + b_energy_consumption;
98 
99  if (voltage_sensor_ != nullptr) {
101  }
102  if (current_sensor_1_ != nullptr) {
104  }
105  if (current_sensor_2_ != nullptr) {
107  }
108  if (power_sensor_1_ != nullptr) {
110  }
111  if (power_sensor_2_ != nullptr) {
113  }
114  if (energy_sensor_1_ != nullptr) {
115  energy_sensor_1_->publish_state(a_energy_consumption);
116  }
117  if (energy_sensor_2_ != nullptr) {
118  energy_sensor_2_->publish_state(b_energy_consumption);
119  }
120  if (energy_sensor_sum_ != nullptr) {
121  energy_sensor_sum_->publish_state(total_energy_consumption);
122  }
123 
124  ESP_LOGV(TAG,
125  "BL0939: U %fV, I1 %fA, I2 %fA, P1 %fW, P2 %fW, CntA %" PRId32 ", CntB %" PRId32 ", ∫P1 %fkWh, ∫P2 %fkWh",
126  v_rms, ia_rms, ib_rms, a_watt, b_watt, cfa_cnt, cfb_cnt, a_energy_consumption, b_energy_consumption);
127 }
128 
129 void BL0939::dump_config() { // NOLINT(readability-function-cognitive-complexity)
130  ESP_LOGCONFIG(TAG, "BL0939:");
131  LOG_SENSOR("", "Voltage", this->voltage_sensor_);
132  LOG_SENSOR("", "Current 1", this->current_sensor_1_);
133  LOG_SENSOR("", "Current 2", this->current_sensor_2_);
134  LOG_SENSOR("", "Power 1", this->power_sensor_1_);
135  LOG_SENSOR("", "Power 2", this->power_sensor_2_);
136  LOG_SENSOR("", "Energy 1", this->energy_sensor_1_);
137  LOG_SENSOR("", "Energy 2", this->energy_sensor_2_);
138  LOG_SENSOR("", "Energy sum", this->energy_sensor_sum_);
139 }
140 
141 uint32_t BL0939::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; }
142 
143 int32_t BL0939::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; }
144 
145 } // namespace bl0939
146 } // namespace esphome
sensor::Sensor * current_sensor_1_
Definition: bl0939.h:79
optional< std::array< uint8_t, N > > read_array()
Definition: uart.h:33
const uint8_t BL0939_INIT[6][6]
Definition: bl0939.cpp:24
void write_array(const uint8_t *data, size_t len)
Definition: uart.h:21
void write_byte(uint8_t data)
Definition: uart.h:19
sensor::Sensor * power_sensor_2_
Definition: bl0939.h:84
void dump_config() override
Definition: bl0939.cpp:129
float voltage_reference_
Definition: bl0939.h:92
static int32_t to_int32_t(sbe24_t input)
Definition: bl0939.cpp:143
sbe24_t a_watt
Definition: bl0939.h:27
sensor::Sensor * power_sensor_1_
Definition: bl0939.h:83
sbe24_t b_watt
Definition: bl0939.h:28
static uint32_t to_uint32_t(ube24_t input)
Definition: bl0939.cpp:141
sbe24_t cfa_cnt
Definition: bl0939.h:29
sensor::Sensor * voltage_sensor_
Definition: bl0939.h:78
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
void loop() override
Definition: bl0939.cpp:38
ube24_t ia_rms
Definition: bl0939.h:23
sensor::Sensor * energy_sensor_2_
Definition: bl0939.h:86
void received_package_(const DataPacket *data) const
Definition: bl0939.cpp:81
uint8_t checksum
Definition: bl0939.h:35
static bool validate_checksum(const DataPacket *data)
Definition: bl0939.cpp:54
sensor::Sensor * energy_sensor_1_
Definition: bl0939.h:85
sensor::Sensor * current_sensor_2_
Definition: bl0939.h:80
void setup() override
Definition: bl0939.cpp:73
float current_reference_
Definition: bl0939.h:94
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
void update() override
Definition: bl0939.cpp:67
ube24_t ib_rms
Definition: bl0939.h:24
sensor::Sensor * energy_sensor_sum_
Definition: bl0939.h:87
ube24_t v_rms
Definition: bl0939.h:25
sbe24_t cfb_cnt
Definition: bl0939.h:30
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26