mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Add support for BL0942 voltage, current, energy and power Sensor (#3777)
This commit is contained in:
		| @@ -35,6 +35,7 @@ esphome/components/bh1750/* @OttoWinter | ||||
| esphome/components/binary_sensor/* @esphome/core | ||||
| esphome/components/bl0939/* @ziceva | ||||
| esphome/components/bl0940/* @tobias- | ||||
| esphome/components/bl0942/* @dbuezas | ||||
| esphome/components/ble_client/* @buxtronix | ||||
| esphome/components/bluetooth_proxy/* @jesserockz | ||||
| esphome/components/bme680_bsec/* @trvrnrth | ||||
|   | ||||
							
								
								
									
										1
									
								
								esphome/components/bl0942/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/bl0942/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| CODEOWNERS = ["@dbuezas"] | ||||
							
								
								
									
										121
									
								
								esphome/components/bl0942/bl0942.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								esphome/components/bl0942/bl0942.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | ||||
| #include "bl0942.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0942 { | ||||
|  | ||||
| static const char *const TAG = "bl0942"; | ||||
|  | ||||
| static const uint8_t BL0942_READ_COMMAND = 0x58; | ||||
| static const uint8_t BL0942_FULL_PACKET = 0xAA; | ||||
| static const uint8_t BL0942_PACKET_HEADER = 0x55; | ||||
|  | ||||
| static const uint8_t BL0942_WRITE_COMMAND = 0xA8; | ||||
| static const uint8_t BL0942_REG_I_FAST_RMS_CTRL = 0x10; | ||||
| static const uint8_t BL0942_REG_MODE = 0x18; | ||||
| static const uint8_t BL0942_REG_SOFT_RESET = 0x19; | ||||
| static const uint8_t BL0942_REG_USR_WRPROT = 0x1A; | ||||
| static const uint8_t BL0942_REG_TPS_CTRL = 0x1B; | ||||
|  | ||||
| // TODO: Confirm insialisation works as intended | ||||
| const uint8_t BL0942_INIT[5][6] = { | ||||
|     // Reset to default | ||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38}, | ||||
|     // Enable User Operation Write | ||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0}, | ||||
|     // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS | ||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_MODE, 0x00, 0x10, 0x00, 0x37}, | ||||
|     // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS | ||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE}, | ||||
|     // 0x181C = Half cycle, Fast RMS threshold 6172 | ||||
|     {BL0942_WRITE_COMMAND, BL0942_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}}; | ||||
|  | ||||
| void BL0942::loop() { | ||||
|   DataPacket buffer; | ||||
|   if (!this->available()) { | ||||
|     return; | ||||
|   } | ||||
|   if (read_array((uint8_t *) &buffer, sizeof(buffer))) { | ||||
|     if (validate_checksum(&buffer)) { | ||||
|       received_package_(&buffer); | ||||
|     } | ||||
|   } else { | ||||
|     ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); | ||||
|     while (read() >= 0) | ||||
|       ; | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool BL0942::validate_checksum(DataPacket *data) { | ||||
|   uint8_t checksum = BL0942_READ_COMMAND; | ||||
|   // Whole package but checksum | ||||
|   uint8_t *raw = (uint8_t *) data; | ||||
|   for (uint32_t i = 0; i < sizeof(*data) - 1; i++) { | ||||
|     checksum += raw[i]; | ||||
|   } | ||||
|   checksum ^= 0xFF; | ||||
|   if (checksum != data->checksum) { | ||||
|     ESP_LOGW(TAG, "BL0942 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum); | ||||
|   } | ||||
|   return checksum == data->checksum; | ||||
| } | ||||
|  | ||||
| void BL0942::update() { | ||||
|   this->flush(); | ||||
|   this->write_byte(BL0942_READ_COMMAND); | ||||
|   this->write_byte(BL0942_FULL_PACKET); | ||||
| } | ||||
|  | ||||
| void BL0942::setup() { | ||||
|   for (auto *i : BL0942_INIT) { | ||||
|     this->write_array(i, 6); | ||||
|     delay(1); | ||||
|   } | ||||
|   this->flush(); | ||||
| } | ||||
|  | ||||
| void BL0942::received_package_(DataPacket *data) { | ||||
|   // Bad header | ||||
|   if (data->frame_header != BL0942_PACKET_HEADER) { | ||||
|     ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   float v_rms = (uint24_t) data->v_rms / voltage_reference_; | ||||
|   float i_rms = (uint24_t) data->i_rms / current_reference_; | ||||
|   float watt = (int24_t) data->watt / power_reference_; | ||||
|   uint32_t cf_cnt = (uint24_t) data->cf_cnt; | ||||
|   float total_energy_consumption = cf_cnt / energy_reference_; | ||||
|   float frequency = 1000000.0f / data->frequency; | ||||
|  | ||||
|   if (voltage_sensor_ != nullptr) { | ||||
|     voltage_sensor_->publish_state(v_rms); | ||||
|   } | ||||
|   if (current_sensor_ != nullptr) { | ||||
|     current_sensor_->publish_state(i_rms); | ||||
|   } | ||||
|   if (power_sensor_ != nullptr) { | ||||
|     power_sensor_->publish_state(watt); | ||||
|   } | ||||
|   if (energy_sensor_ != nullptr) { | ||||
|     energy_sensor_->publish_state(total_energy_consumption); | ||||
|   } | ||||
|   if (frequency_sensor_ != nullptr) { | ||||
|     frequency_sensor_->publish_state(frequency); | ||||
|   } | ||||
|  | ||||
|   ESP_LOGV(TAG, "BL0942: U %fV, I %fA, P %fW, Cnt %d, ∫P %fkWh, frequency %f°Hz, status 0x%08X", v_rms, i_rms, watt, | ||||
|            cf_cnt, total_energy_consumption, frequency, data->status); | ||||
| } | ||||
|  | ||||
| void BL0942::dump_config() {  // NOLINT(readability-function-cognitive-complexity) | ||||
|   ESP_LOGCONFIG(TAG, "BL0942:"); | ||||
|   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_); | ||||
| } | ||||
|  | ||||
| }  // namespace bl0942 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										68
									
								
								esphome/components/bl0942/bl0942.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								esphome/components/bl0942/bl0942.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/datatypes.h" | ||||
| #include "esphome/components/uart/uart.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0942 { | ||||
|  | ||||
| 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 | ||||
| static const float BL0942_EREF = 3304.61127328;    // Measured | ||||
|  | ||||
| struct DataPacket { | ||||
|   uint8_t frame_header; | ||||
|   uint24_le_t i_rms; | ||||
|   uint24_le_t v_rms; | ||||
|   uint24_le_t i_fast_rms; | ||||
|   int24_le_t watt; | ||||
|   uint24_le_t cf_cnt; | ||||
|   uint16_le_t frequency; | ||||
|   uint8_t reserved1; | ||||
|   uint8_t status; | ||||
|   uint8_t reserved2; | ||||
|   uint8_t reserved3; | ||||
|   uint8_t checksum; | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| class BL0942 : public PollingComponent, public uart::UARTDevice { | ||||
|  public: | ||||
|   void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } | ||||
|   void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } | ||||
|   void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } | ||||
|   void set_energy_sensor(sensor::Sensor *energy_sensor) { energy_sensor_ = energy_sensor; } | ||||
|   void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; } | ||||
|  | ||||
|   void loop() override; | ||||
|  | ||||
|   void update() override; | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|  | ||||
|  protected: | ||||
|   sensor::Sensor *voltage_sensor_; | ||||
|   sensor::Sensor *current_sensor_; | ||||
|   // NB This may be negative as the circuits is seemingly able to measure | ||||
|   // power in both directions | ||||
|   sensor::Sensor *power_sensor_; | ||||
|   sensor::Sensor *energy_sensor_; | ||||
|   sensor::Sensor *frequency_sensor_; | ||||
|  | ||||
|   // Divide by this to turn into Watt | ||||
|   float power_reference_ = BL0942_PREF; | ||||
|   // Divide by this to turn into Volt | ||||
|   float voltage_reference_ = BL0942_UREF; | ||||
|   // Divide by this to turn into Ampere | ||||
|   float current_reference_ = BL0942_IREF; | ||||
|   // Divide by this to turn into kWh | ||||
|   float energy_reference_ = BL0942_EREF; | ||||
|  | ||||
|   static bool validate_checksum(DataPacket *data); | ||||
|  | ||||
|   void received_package_(DataPacket *data); | ||||
| }; | ||||
| }  // namespace bl0942 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										93
									
								
								esphome/components/bl0942/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								esphome/components/bl0942/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, uart | ||||
| from esphome.const import ( | ||||
|     CONF_CURRENT, | ||||
|     CONF_ENERGY, | ||||
|     CONF_ID, | ||||
|     CONF_POWER, | ||||
|     CONF_VOLTAGE, | ||||
|     CONF_FREQUENCY, | ||||
|     DEVICE_CLASS_CURRENT, | ||||
|     DEVICE_CLASS_ENERGY, | ||||
|     DEVICE_CLASS_POWER, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     DEVICE_CLASS_FREQUENCY, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_AMPERE, | ||||
|     UNIT_KILOWATT_HOURS, | ||||
|     UNIT_VOLT, | ||||
|     UNIT_WATT, | ||||
|     UNIT_HERTZ, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ["uart"] | ||||
|  | ||||
| bl0942_ns = cg.esphome_ns.namespace("bl0942") | ||||
| BL0942 = bl0942_ns.class_("BL0942", cg.PollingComponent, uart.UARTDevice) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BL0942), | ||||
|             cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_VOLT, | ||||
|                 accuracy_decimals=1, | ||||
|                 device_class=DEVICE_CLASS_VOLTAGE, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_CURRENT): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_AMPERE, | ||||
|                 accuracy_decimals=2, | ||||
|                 device_class=DEVICE_CLASS_CURRENT, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_POWER): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_WATT, | ||||
|                 accuracy_decimals=0, | ||||
|                 device_class=DEVICE_CLASS_POWER, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_ENERGY): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||
|                 accuracy_decimals=0, | ||||
|                 device_class=DEVICE_CLASS_ENERGY, | ||||
|             ), | ||||
|             cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_HERTZ, | ||||
|                 accuracy_decimals=0, | ||||
|                 device_class=DEVICE_CLASS_FREQUENCY, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(uart.UART_DEVICE_SCHEMA) | ||||
| ) | ||||
|  | ||||
|  | ||||
| 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 CONF_VOLTAGE in config: | ||||
|         conf = config[CONF_VOLTAGE] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_voltage_sensor(sens)) | ||||
|     if CONF_CURRENT in config: | ||||
|         conf = config[CONF_CURRENT] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_current_sensor(sens)) | ||||
|     if CONF_POWER in config: | ||||
|         conf = config[CONF_POWER] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_power_sensor(sens)) | ||||
|     if CONF_ENERGY in config: | ||||
|         conf = config[CONF_ENERGY] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_energy_sensor(sens)) | ||||
|     if CONF_FREQUENCY in config: | ||||
|         conf = config[CONF_FREQUENCY] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_frequency_sensor(sens)) | ||||
| @@ -530,6 +530,18 @@ sensor: | ||||
|       name: BL0940 Internal temperature | ||||
|     external_temperature: | ||||
|       name: BL0940 External temperature | ||||
|   - platform: bl0942 | ||||
|     uart_id: uart3 | ||||
|     voltage: | ||||
|       name: 'BL0942 Voltage' | ||||
|     current: | ||||
|       name: 'BL0942 Current' | ||||
|     power: | ||||
|       name: 'BL0942 Power' | ||||
|     energy: | ||||
|       name: 'BL0942 Energy' | ||||
|     frequency: | ||||
|       name: "BL0942 Frequency" | ||||
|   - platform: pzem004t | ||||
|     uart_id: uart3 | ||||
|     voltage: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user