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/binary_sensor/* @esphome/core | ||||||
| esphome/components/bl0939/* @ziceva | esphome/components/bl0939/* @ziceva | ||||||
| esphome/components/bl0940/* @tobias- | esphome/components/bl0940/* @tobias- | ||||||
|  | esphome/components/bl0942/* @dbuezas | ||||||
| esphome/components/ble_client/* @buxtronix | esphome/components/ble_client/* @buxtronix | ||||||
| esphome/components/bluetooth_proxy/* @jesserockz | esphome/components/bluetooth_proxy/* @jesserockz | ||||||
| esphome/components/bme680_bsec/* @trvrnrth | 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 |       name: BL0940 Internal temperature | ||||||
|     external_temperature: |     external_temperature: | ||||||
|       name: BL0940 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 |   - platform: pzem004t | ||||||
|     uart_id: uart3 |     uart_id: uart3 | ||||||
|     voltage: |     voltage: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user