mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 23:21:54 +00:00 
			
		
		
		
	Add support for BL0906 energy meter (#7339)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -58,6 +58,7 @@ esphome/components/beken_spi_led_strip/* @Mat931 | ||||
| esphome/components/bh1750/* @OttoWinter | ||||
| esphome/components/binary_sensor/* @esphome/core | ||||
| esphome/components/bk72xx/* @kuba2k2 | ||||
| esphome/components/bl0906/* @athom-tech @jesserockz @tarontop | ||||
| esphome/components/bl0939/* @ziceva | ||||
| esphome/components/bl0940/* @tobias- | ||||
| esphome/components/bl0942/* @dbuezas | ||||
|   | ||||
							
								
								
									
										1
									
								
								esphome/components/bl0906/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/bl0906/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| CODEOWNERS = ["@athom-tech", "@tarontop", "@jesserockz"] | ||||
							
								
								
									
										238
									
								
								esphome/components/bl0906/bl0906.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										238
									
								
								esphome/components/bl0906/bl0906.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,238 @@ | ||||
| #include "bl0906.h" | ||||
| #include "constants.h" | ||||
|  | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0906 { | ||||
|  | ||||
| static const char *const TAG = "bl0906"; | ||||
|  | ||||
| constexpr uint32_t to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; } | ||||
|  | ||||
| constexpr int32_t to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; } | ||||
|  | ||||
| // The SUM byte is (Addr+Data_L+Data_M+Data_H)&0xFF negated; | ||||
| constexpr uint8_t bl0906_checksum(const uint8_t address, const DataPacket *data) { | ||||
|   return (address + data->l + data->m + data->h) ^ 0xFF; | ||||
| } | ||||
|  | ||||
| void BL0906::loop() { | ||||
|   if (this->current_channel_ == UINT8_MAX) { | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   while (this->available()) | ||||
|     this->flush(); | ||||
|  | ||||
|   if (this->current_channel_ == 0) { | ||||
|     // Temperature | ||||
|     this->read_data_(BL0906_TEMPERATURE, BL0906_TREF, this->temperature_sensor_); | ||||
|   } else if (this->current_channel_ == 1) { | ||||
|     this->read_data_(BL0906_I_1_RMS, BL0906_IREF, this->current_1_sensor_); | ||||
|     this->read_data_(BL0906_WATT_1, BL0906_PREF, this->power_1_sensor_); | ||||
|     this->read_data_(BL0906_CF_1_CNT, BL0906_EREF, this->energy_1_sensor_); | ||||
|   } else if (this->current_channel_ == 2) { | ||||
|     this->read_data_(BL0906_I_2_RMS, BL0906_IREF, this->current_2_sensor_); | ||||
|     this->read_data_(BL0906_WATT_2, BL0906_PREF, this->power_2_sensor_); | ||||
|     this->read_data_(BL0906_CF_2_CNT, BL0906_EREF, this->energy_2_sensor_); | ||||
|   } else if (this->current_channel_ == 3) { | ||||
|     this->read_data_(BL0906_I_3_RMS, BL0906_IREF, this->current_3_sensor_); | ||||
|     this->read_data_(BL0906_WATT_3, BL0906_PREF, this->power_3_sensor_); | ||||
|     this->read_data_(BL0906_CF_3_CNT, BL0906_EREF, this->energy_3_sensor_); | ||||
|   } else if (this->current_channel_ == 4) { | ||||
|     this->read_data_(BL0906_I_4_RMS, BL0906_IREF, this->current_4_sensor_); | ||||
|     this->read_data_(BL0906_WATT_4, BL0906_PREF, this->power_4_sensor_); | ||||
|     this->read_data_(BL0906_CF_4_CNT, BL0906_EREF, this->energy_4_sensor_); | ||||
|   } else if (this->current_channel_ == 5) { | ||||
|     this->read_data_(BL0906_I_5_RMS, BL0906_IREF, this->current_5_sensor_); | ||||
|     this->read_data_(BL0906_WATT_5, BL0906_PREF, this->power_5_sensor_); | ||||
|     this->read_data_(BL0906_CF_5_CNT, BL0906_EREF, this->energy_5_sensor_); | ||||
|   } else if (this->current_channel_ == 6) { | ||||
|     this->read_data_(BL0906_I_6_RMS, BL0906_IREF, this->current_6_sensor_); | ||||
|     this->read_data_(BL0906_WATT_6, BL0906_PREF, this->power_6_sensor_); | ||||
|     this->read_data_(BL0906_CF_6_CNT, BL0906_EREF, this->energy_6_sensor_); | ||||
|   } else if (this->current_channel_ == UINT8_MAX - 2) { | ||||
|     // Frequency | ||||
|     this->read_data_(BL0906_FREQUENCY, BL0906_FREF, frequency_sensor_); | ||||
|     // Voltage | ||||
|     this->read_data_(BL0906_V_RMS, BL0906_UREF, voltage_sensor_); | ||||
|   } else if (this->current_channel_ == UINT8_MAX - 1) { | ||||
|     // Total power | ||||
|     this->read_data_(BL0906_WATT_SUM, BL0906_WATT, this->total_power_sensor_); | ||||
|     // Total Energy | ||||
|     this->read_data_(BL0906_CF_SUM_CNT, BL0906_CF, this->total_energy_sensor_); | ||||
|   } else { | ||||
|     this->current_channel_ = UINT8_MAX - 2;  // Go to frequency and voltage | ||||
|     return; | ||||
|   } | ||||
|   this->current_channel_++; | ||||
|   this->handle_actions_(); | ||||
| } | ||||
|  | ||||
| void BL0906::setup() { | ||||
|   while (this->available()) | ||||
|     this->flush(); | ||||
|   this->write_array(USR_WRPROT_WITABLE, sizeof(USR_WRPROT_WITABLE)); | ||||
|   // Calibration (1: register address; 2: value before calibration; 3: value after calibration) | ||||
|   this->bias_correction_(BL0906_RMSOS_1, 0.01600, 0);  // Calibration current_1 | ||||
|   this->bias_correction_(BL0906_RMSOS_2, 0.01500, 0); | ||||
|   this->bias_correction_(BL0906_RMSOS_3, 0.01400, 0); | ||||
|   this->bias_correction_(BL0906_RMSOS_4, 0.01300, 0); | ||||
|   this->bias_correction_(BL0906_RMSOS_5, 0.01200, 0); | ||||
|   this->bias_correction_(BL0906_RMSOS_6, 0.01200, 0);  // Calibration current_6 | ||||
|  | ||||
|   this->write_array(USR_WRPROT_ONLYREAD, sizeof(USR_WRPROT_ONLYREAD)); | ||||
| } | ||||
|  | ||||
| void BL0906::update() { this->current_channel_ = 0; } | ||||
|  | ||||
| size_t BL0906::enqueue_action_(ActionCallbackFuncPtr function) { | ||||
|   this->action_queue_.push_back(function); | ||||
|   return this->action_queue_.size(); | ||||
| } | ||||
|  | ||||
| void BL0906::handle_actions_() { | ||||
|   if (this->action_queue_.empty()) { | ||||
|     return; | ||||
|   } | ||||
|   ActionCallbackFuncPtr ptr_func = nullptr; | ||||
|   for (int i = 0; i < this->action_queue_.size(); i++) { | ||||
|     ptr_func = this->action_queue_[i]; | ||||
|     if (ptr_func) { | ||||
|       ESP_LOGI(TAG, "HandleActionCallback[%d]...", i); | ||||
|       (this->*ptr_func)(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   while (this->available()) { | ||||
|     this->read(); | ||||
|   } | ||||
|  | ||||
|   this->action_queue_.clear(); | ||||
| } | ||||
|  | ||||
| // Reset energy | ||||
| void BL0906::reset_energy_() { | ||||
|   this->write_array(BL0906_INIT[0], 6); | ||||
|   delay(1); | ||||
|   this->flush(); | ||||
|  | ||||
|   ESP_LOGW(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_INIT[0][0], BL0906_INIT[0][1], BL0906_INIT[0][2], | ||||
|            BL0906_INIT[0][3], BL0906_INIT[0][4], BL0906_INIT[0][5]); | ||||
| } | ||||
|  | ||||
| // Read data | ||||
| void BL0906::read_data_(const uint8_t address, const float reference, sensor::Sensor *sensor) { | ||||
|   if (sensor == nullptr) { | ||||
|     return; | ||||
|   } | ||||
|   DataPacket buffer; | ||||
|   ube24_t data_u24; | ||||
|   sbe24_t data_s24; | ||||
|   float value = 0; | ||||
|  | ||||
|   bool signed_result = reference == BL0906_TREF || reference == BL0906_WATT || reference == BL0906_PREF; | ||||
|  | ||||
|   this->write_byte(BL0906_READ_COMMAND); | ||||
|   this->write_byte(address); | ||||
|   if (this->read_array((uint8_t *) &buffer, sizeof(buffer) - 1)) { | ||||
|     if (bl0906_checksum(address, &buffer) == buffer.checksum) { | ||||
|       if (signed_result) { | ||||
|         data_s24.l = buffer.l; | ||||
|         data_s24.m = buffer.m; | ||||
|         data_s24.h = buffer.h; | ||||
|       } else { | ||||
|         data_u24.l = buffer.l; | ||||
|         data_u24.m = buffer.m; | ||||
|         data_u24.h = buffer.h; | ||||
|       } | ||||
|     } else { | ||||
|       ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); | ||||
|       while (read() >= 0) | ||||
|         ; | ||||
|       return; | ||||
|     } | ||||
|   } | ||||
|   // Power | ||||
|   if (reference == BL0906_PREF) { | ||||
|     value = (float) to_int32_t(data_s24) * reference; | ||||
|   } | ||||
|  | ||||
|   // Total power | ||||
|   if (reference == BL0906_WATT) { | ||||
|     value = (float) to_int32_t(data_s24) * reference; | ||||
|   } | ||||
|  | ||||
|   // Voltage, current, power, total power | ||||
|   if (reference == BL0906_UREF || reference == BL0906_IREF || reference == BL0906_EREF || reference == BL0906_CF) { | ||||
|     value = (float) to_uint32_t(data_u24) * reference; | ||||
|   } | ||||
|  | ||||
|   // Frequency | ||||
|   if (reference == BL0906_FREF) { | ||||
|     value = reference / (float) to_uint32_t(data_u24); | ||||
|   } | ||||
|   // Chip temperature | ||||
|   if (reference == BL0906_TREF) { | ||||
|     value = (float) to_int32_t(data_s24); | ||||
|     value = (value - 64) * 12.5 / 59 - 40; | ||||
|   } | ||||
|   sensor->publish_state(value); | ||||
| } | ||||
|  | ||||
| // RMS offset correction | ||||
| void BL0906::bias_correction_(uint8_t address, float measurements, float correction) { | ||||
|   DataPacket data; | ||||
|   float ki = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097;  // Current coefficient | ||||
|   float i_rms0 = measurements * ki; | ||||
|   float i_rms = correction * ki; | ||||
|   int32_t value = (i_rms * i_rms - i_rms0 * i_rms0) / 256; | ||||
|   data.l = value << 24 >> 24; | ||||
|   data.m = value << 16 >> 24; | ||||
|   if (value < 0) { | ||||
|     data.h = (value << 8 >> 24) | 0b10000000; | ||||
|   } | ||||
|   data.address = bl0906_checksum(address, &data); | ||||
|   ESP_LOGV(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_WRITE_COMMAND, address, data.l, data.m, data.h, data.address); | ||||
|   this->write_byte(BL0906_WRITE_COMMAND); | ||||
|   this->write_byte(address); | ||||
|   this->write_byte(data.l); | ||||
|   this->write_byte(data.m); | ||||
|   this->write_byte(data.h); | ||||
|   this->write_byte(data.address); | ||||
| } | ||||
|  | ||||
| void BL0906::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "BL0906:"); | ||||
|   LOG_SENSOR("  ", "Voltage", this->voltage_sensor_); | ||||
|  | ||||
|   LOG_SENSOR("  ", "Current1", this->current_1_sensor_); | ||||
|   LOG_SENSOR("  ", "Current2", this->current_2_sensor_); | ||||
|   LOG_SENSOR("  ", "Current3", this->current_3_sensor_); | ||||
|   LOG_SENSOR("  ", "Current4", this->current_4_sensor_); | ||||
|   LOG_SENSOR("  ", "Current5", this->current_5_sensor_); | ||||
|   LOG_SENSOR("  ", "Current6", this->current_6_sensor_); | ||||
|  | ||||
|   LOG_SENSOR("  ", "Power1", this->power_1_sensor_); | ||||
|   LOG_SENSOR("  ", "Power2", this->power_2_sensor_); | ||||
|   LOG_SENSOR("  ", "Power3", this->power_3_sensor_); | ||||
|   LOG_SENSOR("  ", "Power4", this->power_4_sensor_); | ||||
|   LOG_SENSOR("  ", "Power5", this->power_5_sensor_); | ||||
|   LOG_SENSOR("  ", "Power6", this->power_6_sensor_); | ||||
|  | ||||
|   LOG_SENSOR("  ", "Energy1", this->energy_1_sensor_); | ||||
|   LOG_SENSOR("  ", "Energy2", this->energy_2_sensor_); | ||||
|   LOG_SENSOR("  ", "Energy3", this->energy_3_sensor_); | ||||
|   LOG_SENSOR("  ", "Energy4", this->energy_4_sensor_); | ||||
|   LOG_SENSOR("  ", "Energy5", this->energy_5_sensor_); | ||||
|   LOG_SENSOR("  ", "Energy6", this->energy_6_sensor_); | ||||
|  | ||||
|   LOG_SENSOR("  ", "Total Power", this->total_power_sensor_); | ||||
|   LOG_SENSOR("  ", "Total Energy", this->total_energy_sensor_); | ||||
|   LOG_SENSOR("  ", "Frequency", this->frequency_sensor_); | ||||
|   LOG_SENSOR("  ", "Temperature", this->temperature_sensor_); | ||||
| } | ||||
|  | ||||
| }  // namespace bl0906 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										96
									
								
								esphome/components/bl0906/bl0906.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								esphome/components/bl0906/bl0906.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/uart/uart.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/datatypes.h" | ||||
|  | ||||
| // https://www.belling.com.cn/media/file_object/bel_product/BL0906/datasheet/BL0906_V1.02_cn.pdf | ||||
| // https://www.belling.com.cn/media/file_object/bel_product/BL0906/guide/BL0906%20APP%20Note_V1.02.pdf | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0906 { | ||||
|  | ||||
| struct DataPacket {  // NOLINT(altera-struct-pack-align) | ||||
|   uint8_t l{0}; | ||||
|   uint8_t m{0}; | ||||
|   uint8_t h{0}; | ||||
|   uint8_t checksum;  // checksum | ||||
|   uint8_t address; | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| struct ube24_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align) | ||||
|   uint8_t l{0}; | ||||
|   uint8_t m{0}; | ||||
|   uint8_t h{0}; | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| struct sbe24_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align) | ||||
|   uint8_t l{0}; | ||||
|   uint8_t m{0}; | ||||
|   int8_t h{0}; | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| template<typename... Ts> class ResetEnergyAction; | ||||
|  | ||||
| class BL0906; | ||||
|  | ||||
| using ActionCallbackFuncPtr = void (BL0906::*)(); | ||||
|  | ||||
| class BL0906 : public PollingComponent, public uart::UARTDevice { | ||||
|   SUB_SENSOR(voltage) | ||||
|   SUB_SENSOR(current_1) | ||||
|   SUB_SENSOR(current_2) | ||||
|   SUB_SENSOR(current_3) | ||||
|   SUB_SENSOR(current_4) | ||||
|   SUB_SENSOR(current_5) | ||||
|   SUB_SENSOR(current_6) | ||||
|   SUB_SENSOR(power_1) | ||||
|   SUB_SENSOR(power_2) | ||||
|   SUB_SENSOR(power_3) | ||||
|   SUB_SENSOR(power_4) | ||||
|   SUB_SENSOR(power_5) | ||||
|   SUB_SENSOR(power_6) | ||||
|   SUB_SENSOR(total_power) | ||||
|   SUB_SENSOR(energy_1) | ||||
|   SUB_SENSOR(energy_2) | ||||
|   SUB_SENSOR(energy_3) | ||||
|   SUB_SENSOR(energy_4) | ||||
|   SUB_SENSOR(energy_5) | ||||
|   SUB_SENSOR(energy_6) | ||||
|   SUB_SENSOR(total_energy) | ||||
|   SUB_SENSOR(frequency) | ||||
|   SUB_SENSOR(temperature) | ||||
|  | ||||
|  public: | ||||
|   void loop() override; | ||||
|  | ||||
|   void update() override; | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|  | ||||
|  protected: | ||||
|   template<typename... Ts> friend class ResetEnergyAction; | ||||
|  | ||||
|   void reset_energy_(); | ||||
|  | ||||
|   void read_data_(uint8_t address, float reference, sensor::Sensor *sensor); | ||||
|  | ||||
|   void bias_correction_(uint8_t address, float measurements, float correction); | ||||
|  | ||||
|   uint8_t current_channel_{0}; | ||||
|   size_t enqueue_action_(ActionCallbackFuncPtr function); | ||||
|   void handle_actions_(); | ||||
|  | ||||
|  private: | ||||
|   std::vector<ActionCallbackFuncPtr> action_queue_{}; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class ResetEnergyAction : public Action<Ts...>, public Parented<BL0906> { | ||||
|  public: | ||||
|   void play(Ts... x) override { this->parent_->enqueue_action_(&BL0906::reset_energy_); } | ||||
| }; | ||||
|  | ||||
| }  // namespace bl0906 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										4
									
								
								esphome/components/bl0906/const.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								esphome/components/bl0906/const.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| # const.py | ||||
| ICON_ENERGY = "mdi:lightning-bolt" | ||||
| ICON_FREQUENCY = "mdi:cosine-wave" | ||||
| ICON_VOLTAGE = "mdi:sine-wave" | ||||
							
								
								
									
										122
									
								
								esphome/components/bl0906/constants.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								esphome/components/bl0906/constants.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| #pragma once | ||||
| #include <cstdint> | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0906 { | ||||
|  | ||||
| // Total power conversion | ||||
| static const float BL0906_WATT = 16 * 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / | ||||
|                                  (40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000); | ||||
| // Total Energy conversion | ||||
| static const float BL0906_CF = 16 * 4194304 * 0.032768 * 16 / | ||||
|                                (3600000 * 16 * | ||||
|                                 (40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / | ||||
|                                  (1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000)))); | ||||
| // Frequency conversion | ||||
| static const float BL0906_FREF = 10000000; | ||||
| // Temperature conversion | ||||
| static const float BL0906_TREF = 12.5 / 59 - 40; | ||||
| // Current conversion | ||||
| static const float BL0906_IREF = 1.097 / (12875 * 1 * (5.1 + 5.1) * 1000 / 2000); | ||||
| // Voltage conversion | ||||
| static const float BL0906_UREF = 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / (13162 * 1 * 100 * 1000); | ||||
| // Power conversion | ||||
| static const float BL0906_PREF = 1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000) / | ||||
|                                  (40.41259 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000); | ||||
| // Energy conversion | ||||
| static const float BL0906_EREF = 4194304 * 0.032768 * 16 / | ||||
|                                  (3600000 * 16 * | ||||
|                                   (40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / | ||||
|                                    (1.097 * 1.097 * (20000 + 20000 + 20000 + 20000 + 20000)))); | ||||
| // Current coefficient | ||||
| static const float BL0906_KI = 12875 * 1 * (5.1 + 5.1) * 1000 / 2000 / 1.097; | ||||
| // Power coefficient | ||||
| static const float BL0906_KP = 40.4125 * ((5.1 + 5.1) * 1000 / 2000) * 1 * 100 * 1 * 1000 / 1.097 / 1.097 / | ||||
|                                (20000 + 20000 + 20000 + 20000 + 20000); | ||||
|  | ||||
| static const uint8_t USR_WRPROT_WITABLE[6] = {0xCA, 0x9E, 0x55, 0x55, 0x00, 0xB7}; | ||||
| static const uint8_t USR_WRPROT_ONLYREAD[6] = {0xCA, 0x9E, 0x00, 0x00, 0x00, 0x61}; | ||||
|  | ||||
| static const uint8_t BL0906_READ_COMMAND = 0x35; | ||||
| static const uint8_t BL0906_WRITE_COMMAND = 0xCA; | ||||
|  | ||||
| // Register address | ||||
| // Voltage | ||||
| static const uint8_t BL0906_V_RMS = 0x16; | ||||
|  | ||||
| // Total power | ||||
| static const uint8_t BL0906_WATT_SUM = 0X2C; | ||||
|  | ||||
| // Current1~6 | ||||
| static const uint8_t BL0906_I_1_RMS = 0x0D;  // current_1 | ||||
| static const uint8_t BL0906_I_2_RMS = 0x0E; | ||||
| static const uint8_t BL0906_I_3_RMS = 0x0F; | ||||
| static const uint8_t BL0906_I_4_RMS = 0x10; | ||||
| static const uint8_t BL0906_I_5_RMS = 0x13; | ||||
| static const uint8_t BL0906_I_6_RMS = 0x14;  // current_6 | ||||
|  | ||||
| // Power1~6 | ||||
| static const uint8_t BL0906_WATT_1 = 0X23;  // power_1 | ||||
| static const uint8_t BL0906_WATT_2 = 0X24; | ||||
| static const uint8_t BL0906_WATT_3 = 0X25; | ||||
| static const uint8_t BL0906_WATT_4 = 0X26; | ||||
| static const uint8_t BL0906_WATT_5 = 0X29; | ||||
| static const uint8_t BL0906_WATT_6 = 0X2A;  // power_6 | ||||
|  | ||||
| // Active pulse count, unsigned | ||||
| static const uint8_t BL0906_CF_1_CNT = 0X30;  // Channel_1 | ||||
| static const uint8_t BL0906_CF_2_CNT = 0X31; | ||||
| static const uint8_t BL0906_CF_3_CNT = 0X32; | ||||
| static const uint8_t BL0906_CF_4_CNT = 0X33; | ||||
| static const uint8_t BL0906_CF_5_CNT = 0X36; | ||||
| static const uint8_t BL0906_CF_6_CNT = 0X37;  // Channel_6 | ||||
|  | ||||
| // Total active pulse count, unsigned | ||||
| static const uint8_t BL0906_CF_SUM_CNT = 0X39; | ||||
|  | ||||
| // Voltage frequency cycle | ||||
| static const uint8_t BL0906_FREQUENCY = 0X4E; | ||||
|  | ||||
| // Internal temperature | ||||
| static const uint8_t BL0906_TEMPERATURE = 0X5E; | ||||
|  | ||||
| // Calibration register | ||||
| // RMS gain adjustment register | ||||
| static const uint8_t BL0906_RMSGN_1 = 0x6D;  // Channel_1 | ||||
| static const uint8_t BL0906_RMSGN_2 = 0x6E; | ||||
| static const uint8_t BL0906_RMSGN_3 = 0x6F; | ||||
| static const uint8_t BL0906_RMSGN_4 = 0x70; | ||||
| static const uint8_t BL0906_RMSGN_5 = 0x73; | ||||
| static const uint8_t BL0906_RMSGN_6 = 0x74;  // Channel_6 | ||||
|  | ||||
| // RMS offset correction register | ||||
| static const uint8_t BL0906_RMSOS_1 = 0x78;  // Channel_1 | ||||
| static const uint8_t BL0906_RMSOS_2 = 0x79; | ||||
| static const uint8_t BL0906_RMSOS_3 = 0x7A; | ||||
| static const uint8_t BL0906_RMSOS_4 = 0x7B; | ||||
| static const uint8_t BL0906_RMSOS_5 = 0x7E; | ||||
| static const uint8_t BL0906_RMSOS_6 = 0x7F;  // Channel_6 | ||||
|  | ||||
| // Active power gain adjustment register | ||||
| static const uint8_t BL0906_WATTGN_1 = 0xB7;  // Channel_1 | ||||
| static const uint8_t BL0906_WATTGN_2 = 0xB8; | ||||
| static const uint8_t BL0906_WATTGN_3 = 0xB9; | ||||
| static const uint8_t BL0906_WATTGN_4 = 0xBA; | ||||
| static const uint8_t BL0906_WATTGN_5 = 0xBD; | ||||
| static const uint8_t BL0906_WATTGN_6 = 0xBE;  // Channel_6 | ||||
|  | ||||
| // User write protection setting register, | ||||
| // You must first write 0x5555 to the write protection setting register before writing to other registers. | ||||
| static const uint8_t BL0906_USR_WRPROT = 0x9E; | ||||
|  | ||||
| // Reset Register | ||||
| static const uint8_t BL0906_SOFT_RESET = 0x9F; | ||||
|  | ||||
| const uint8_t BL0906_INIT[2][6] = { | ||||
|     // Reset to default | ||||
|     {BL0906_WRITE_COMMAND, BL0906_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x52}, | ||||
|     // Enable User Operation Write | ||||
|     {BL0906_WRITE_COMMAND, BL0906_USR_WRPROT, 0x55, 0x55, 0x00, 0xB7}}; | ||||
|  | ||||
| }  // namespace bl0906 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										184
									
								
								esphome/components/bl0906/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								esphome/components/bl0906/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,184 @@ | ||||
| from esphome import automation | ||||
| from esphome.automation import maybe_simple_id | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import sensor, uart | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_CHANNEL, | ||||
|     CONF_CURRENT, | ||||
|     CONF_ENERGY, | ||||
|     CONF_FREQUENCY, | ||||
|     CONF_ID, | ||||
|     CONF_NAME, | ||||
|     CONF_POWER, | ||||
|     CONF_TEMPERATURE, | ||||
|     CONF_TOTAL_POWER, | ||||
|     CONF_VOLTAGE, | ||||
|     DEVICE_CLASS_CURRENT, | ||||
|     DEVICE_CLASS_ENERGY, | ||||
|     DEVICE_CLASS_FREQUENCY, | ||||
|     DEVICE_CLASS_POWER, | ||||
|     DEVICE_CLASS_TEMPERATURE, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     ICON_CURRENT_AC, | ||||
|     ICON_POWER, | ||||
|     ICON_THERMOMETER, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     STATE_CLASS_TOTAL_INCREASING, | ||||
|     UNIT_AMPERE, | ||||
|     UNIT_CELSIUS, | ||||
|     UNIT_HERTZ, | ||||
|     UNIT_KILOWATT_HOURS, | ||||
|     UNIT_VOLT, | ||||
|     UNIT_WATT, | ||||
| ) | ||||
|  | ||||
| # Import ICONS not included in esphome's const.py, from the local components const.py | ||||
| from .const import ICON_ENERGY, ICON_FREQUENCY, ICON_VOLTAGE | ||||
|  | ||||
| DEPENDENCIES = ["uart"] | ||||
| AUTO_LOAD = ["bl0906"] | ||||
| CONF_TOTAL_ENERGY = "total_energy" | ||||
|  | ||||
| bl0906_ns = cg.esphome_ns.namespace("bl0906") | ||||
| BL0906 = bl0906_ns.class_("BL0906", cg.PollingComponent, uart.UARTDevice) | ||||
| ResetEnergyAction = bl0906_ns.class_("ResetEnergyAction", automation.Action) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BL0906), | ||||
|             cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( | ||||
|                 icon=ICON_FREQUENCY, | ||||
|                 accuracy_decimals=0, | ||||
|                 device_class=DEVICE_CLASS_FREQUENCY, | ||||
|                 unit_of_measurement=UNIT_HERTZ, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( | ||||
|                 icon=ICON_THERMOMETER, | ||||
|                 accuracy_decimals=0, | ||||
|                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||
|                 unit_of_measurement=UNIT_CELSIUS, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( | ||||
|                 icon=ICON_VOLTAGE, | ||||
|                 accuracy_decimals=1, | ||||
|                 device_class=DEVICE_CLASS_VOLTAGE, | ||||
|                 unit_of_measurement=UNIT_VOLT, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_TOTAL_POWER): sensor.sensor_schema( | ||||
|                 icon=ICON_POWER, | ||||
|                 accuracy_decimals=3, | ||||
|                 device_class=DEVICE_CLASS_POWER, | ||||
|                 unit_of_measurement=UNIT_WATT, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_TOTAL_ENERGY): sensor.sensor_schema( | ||||
|                 icon=ICON_ENERGY, | ||||
|                 accuracy_decimals=3, | ||||
|                 device_class=DEVICE_CLASS_ENERGY, | ||||
|                 state_class=STATE_CLASS_TOTAL_INCREASING, | ||||
|                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend( | ||||
|         cv.Schema( | ||||
|             { | ||||
|                 cv.Optional(f"{CONF_CHANNEL}_{i + 1}"): cv.Schema( | ||||
|                     { | ||||
|                         cv.Optional(CONF_CURRENT): cv.maybe_simple_value( | ||||
|                             sensor.sensor_schema( | ||||
|                                 icon=ICON_CURRENT_AC, | ||||
|                                 accuracy_decimals=3, | ||||
|                                 device_class=DEVICE_CLASS_CURRENT, | ||||
|                                 unit_of_measurement=UNIT_AMPERE, | ||||
|                                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                             ), | ||||
|                             key=CONF_NAME, | ||||
|                         ), | ||||
|                         cv.Optional(CONF_POWER): cv.maybe_simple_value( | ||||
|                             sensor.sensor_schema( | ||||
|                                 icon=ICON_POWER, | ||||
|                                 accuracy_decimals=0, | ||||
|                                 device_class=DEVICE_CLASS_POWER, | ||||
|                                 unit_of_measurement=UNIT_WATT, | ||||
|                                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|                             ), | ||||
|                             key=CONF_NAME, | ||||
|                         ), | ||||
|                         cv.Optional(CONF_ENERGY): cv.maybe_simple_value( | ||||
|                             sensor.sensor_schema( | ||||
|                                 icon=ICON_ENERGY, | ||||
|                                 accuracy_decimals=3, | ||||
|                                 device_class=DEVICE_CLASS_ENERGY, | ||||
|                                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||
|                                 state_class=STATE_CLASS_TOTAL_INCREASING, | ||||
|                             ), | ||||
|                             key=CONF_NAME, | ||||
|                         ), | ||||
|                     } | ||||
|                 ) | ||||
|                 for i in range(6) | ||||
|             } | ||||
|         ) | ||||
|     ) | ||||
|     .extend(uart.UART_DEVICE_SCHEMA) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
| ) | ||||
|  | ||||
| FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( | ||||
|     "bl0906", baud_rate=19200, require_tx=True, require_rx=True | ||||
| ) | ||||
|  | ||||
|  | ||||
| @automation.register_action( | ||||
|     "bl0906.reset_energy", | ||||
|     ResetEnergyAction, | ||||
|     maybe_simple_id( | ||||
|         { | ||||
|             cv.Required(CONF_ID): cv.use_id(BL0906), | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| async def reset_energy_to_code(config, action_id, template_arg, args): | ||||
|     paren = await cg.get_variable(config[CONF_ID]) | ||||
|     return cg.new_Pvariable(action_id, template_arg, paren) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await uart.register_uart_device(var, config) | ||||
|     if frequency_config := config.get(CONF_FREQUENCY): | ||||
|         sens = await sensor.new_sensor(frequency_config) | ||||
|         cg.add(var.set_frequency_sensor(sens)) | ||||
|     if temperature_config := config.get(CONF_TEMPERATURE): | ||||
|         sens = await sensor.new_sensor(temperature_config) | ||||
|         cg.add(var.set_temperature_sensor(sens)) | ||||
|     if voltage_config := config.get(CONF_VOLTAGE): | ||||
|         sens = await sensor.new_sensor(voltage_config) | ||||
|         cg.add(var.set_voltage_sensor(sens)) | ||||
|  | ||||
|     for i in range(6): | ||||
|         if channel_config := config.get(f"{CONF_CHANNEL}_{i + 1}"): | ||||
|             if current_config := channel_config.get(CONF_CURRENT): | ||||
|                 sens = await sensor.new_sensor(current_config) | ||||
|                 cg.add(getattr(var, f"set_current_{i + 1}_sensor")(sens)) | ||||
|             if power_config := channel_config.get(CONF_POWER): | ||||
|                 sens = await sensor.new_sensor(power_config) | ||||
|                 cg.add(getattr(var, f"set_power_{i + 1}_sensor")(sens)) | ||||
|             if energy_config := channel_config.get(CONF_ENERGY): | ||||
|                 sens = await sensor.new_sensor(energy_config) | ||||
|                 cg.add(getattr(var, f"set_energy_{i + 1}_sensor")(sens)) | ||||
|  | ||||
|     if total_power_config := config.get(CONF_TOTAL_POWER): | ||||
|         sens = await sensor.new_sensor(total_power_config) | ||||
|         cg.add(var.set_total_power_sensor(sens)) | ||||
|  | ||||
|     if total_energy_config := config.get(CONF_TOTAL_ENERGY): | ||||
|         sens = await sensor.new_sensor(total_energy_config) | ||||
|         cg.add(var.set_total_energy_sensor(sens)) | ||||
							
								
								
									
										62
									
								
								tests/components/bl0906/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								tests/components/bl0906/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| uart: | ||||
|   - id: uart_bl0906 | ||||
|     tx_pin: | ||||
|       number: ${tx_pin} | ||||
|     rx_pin: | ||||
|       number: ${rx_pin} | ||||
|     baud_rate: 19200 | ||||
|  | ||||
| sensor: | ||||
|   - platform: bl0906 | ||||
|     frequency: | ||||
|       name: 'Frequency' | ||||
|     temperature: | ||||
|       name: 'Temperature' | ||||
|     voltage: | ||||
|       name: 'Voltage' | ||||
|     channel_1: | ||||
|       current: | ||||
|         name: 'Current_1' | ||||
|       power: | ||||
|         name: 'Power_1' | ||||
|       energy: | ||||
|         name: 'Energy_1' | ||||
|     channel_2: | ||||
|       current: | ||||
|         name: 'Current_2' | ||||
|       power: | ||||
|         name: 'Power_2' | ||||
|       energy: | ||||
|         name: 'Energy_2' | ||||
|     channel_3: | ||||
|       current: | ||||
|         name: 'Current_3' | ||||
|       power: | ||||
|         name: 'Power_3' | ||||
|       energy: | ||||
|         name: 'Energy_3' | ||||
|     channel_4: | ||||
|       current: | ||||
|         name: 'Current_4' | ||||
|       power: | ||||
|         name: 'Power_4' | ||||
|       energy: | ||||
|         name: 'Energy_4' | ||||
|     channel_5: | ||||
|       current: | ||||
|         name: 'Current_5' | ||||
|       power: | ||||
|         name: 'Power_5' | ||||
|       energy: | ||||
|         name: 'Energy_5' | ||||
|     channel_6: | ||||
|       current: | ||||
|         name: 'Current_6' | ||||
|       power: | ||||
|         name: 'Power_6' | ||||
|       energy: | ||||
|         name: 'Energy_6' | ||||
|     total_energy: | ||||
|       name: 'Total_Energy' | ||||
|     total_power: | ||||
|       name: 'Total_Power' | ||||
							
								
								
									
										5
									
								
								tests/components/bl0906/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/bl0906/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| substitutions: | ||||
|   tx_pin: GPIO12 | ||||
|   rx_pin: GPIO14 | ||||
|  | ||||
| <<: !include common.yaml | ||||
							
								
								
									
										5
									
								
								tests/components/bl0906/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/bl0906/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| substitutions: | ||||
|   tx_pin: GPIO7 | ||||
|   rx_pin: GPIO8 | ||||
|  | ||||
| <<: !include common.yaml | ||||
							
								
								
									
										5
									
								
								tests/components/bl0906/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/bl0906/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| substitutions: | ||||
|   tx_pin: GPIO7 | ||||
|   rx_pin: GPIO8 | ||||
|  | ||||
| <<: !include common.yaml | ||||
							
								
								
									
										5
									
								
								tests/components/bl0906/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/bl0906/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| substitutions: | ||||
|   tx_pin: GPIO12 | ||||
|   rx_pin: GPIO14 | ||||
|  | ||||
| <<: !include common.yaml | ||||
							
								
								
									
										5
									
								
								tests/components/bl0906/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/bl0906/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| substitutions: | ||||
|   tx_pin: GPIO1 | ||||
|   rx_pin: GPIO3 | ||||
|  | ||||
| <<: !include common.yaml | ||||
							
								
								
									
										5
									
								
								tests/components/bl0906/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/bl0906/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| substitutions: | ||||
|   tx_pin: GPIO4 | ||||
|   rx_pin: GPIO5 | ||||
|  | ||||
| <<: !include common.yaml | ||||
		Reference in New Issue
	
	Block a user