mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Add support for BL0939 (Sonoff Dual R3 V2 powermeter) (#3300)
This commit is contained in:
		| @@ -31,6 +31,7 @@ esphome/components/bang_bang/* @OttoWinter | ||||
| esphome/components/bedjet/* @jhansche | ||||
| esphome/components/bh1750/* @OttoWinter | ||||
| esphome/components/binary_sensor/* @esphome/core | ||||
| esphome/components/bl0939/* @ziceva | ||||
| esphome/components/bl0940/* @tobias- | ||||
| esphome/components/ble_client/* @buxtronix | ||||
| esphome/components/bme680_bsec/* @trvrnrth | ||||
|   | ||||
							
								
								
									
										1
									
								
								esphome/components/bl0939/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/bl0939/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| CODEOWNERS = ["@ziceva"] | ||||
							
								
								
									
										144
									
								
								esphome/components/bl0939/bl0939.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								esphome/components/bl0939/bl0939.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | ||||
| #include "bl0939.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0939 { | ||||
|  | ||||
| static const char *const TAG = "bl0939"; | ||||
|  | ||||
| // https://www.belling.com.cn/media/file_object/bel_product/BL0939/datasheet/BL0939_V1.2_cn.pdf | ||||
| // (unfortunatelly chinese, but the protocol can be understood with some translation tool) | ||||
| static const uint8_t BL0939_READ_COMMAND = 0x55;  // 0x5{A4,A3,A2,A1} | ||||
| static const uint8_t BL0939_FULL_PACKET = 0xAA; | ||||
| static const uint8_t BL0939_PACKET_HEADER = 0x55; | ||||
|  | ||||
| static const uint8_t BL0939_WRITE_COMMAND = 0xA5;  // 0xA{A4,A3,A2,A1} | ||||
| static const uint8_t BL0939_REG_IA_FAST_RMS_CTRL = 0x10; | ||||
| static const uint8_t BL0939_REG_IB_FAST_RMS_CTRL = 0x1E; | ||||
| static const uint8_t BL0939_REG_MODE = 0x18; | ||||
| static const uint8_t BL0939_REG_SOFT_RESET = 0x19; | ||||
| static const uint8_t BL0939_REG_USR_WRPROT = 0x1A; | ||||
| static const uint8_t BL0939_REG_TPS_CTRL = 0x1B; | ||||
|  | ||||
| const uint8_t BL0939_INIT[6][6] = { | ||||
|     // Reset to default | ||||
|     {BL0939_WRITE_COMMAND, BL0939_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x33}, | ||||
|     // Enable User Operation Write | ||||
|     {BL0939_WRITE_COMMAND, BL0939_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xEB}, | ||||
|     // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS | ||||
|     {BL0939_WRITE_COMMAND, BL0939_REG_MODE, 0x00, 0x10, 0x00, 0x32}, | ||||
|     // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS | ||||
|     {BL0939_WRITE_COMMAND, BL0939_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xF9}, | ||||
|     // 0x181C = Half cycle, Fast RMS threshold 6172 | ||||
|     {BL0939_WRITE_COMMAND, BL0939_REG_IA_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x16}, | ||||
|     // 0x181C = Half cycle, Fast RMS threshold 6172 | ||||
|     {BL0939_WRITE_COMMAND, BL0939_REG_IB_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x08}}; | ||||
|  | ||||
| void BL0939::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 BL0939::validate_checksum(const DataPacket *data) { | ||||
|   uint8_t checksum = BL0939_READ_COMMAND; | ||||
|   // Whole package but checksum | ||||
|   for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) { | ||||
|     checksum += data->raw[i]; | ||||
|   } | ||||
|   checksum ^= 0xFF; | ||||
|   if (checksum != data->checksum) { | ||||
|     ESP_LOGW(TAG, "BL0939 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum); | ||||
|   } | ||||
|   return checksum == data->checksum; | ||||
| } | ||||
|  | ||||
| void BL0939::update() { | ||||
|   this->flush(); | ||||
|   this->write_byte(BL0939_READ_COMMAND); | ||||
|   this->write_byte(BL0939_FULL_PACKET); | ||||
| } | ||||
|  | ||||
| void BL0939::setup() { | ||||
|   for (auto *i : BL0939_INIT) { | ||||
|     this->write_array(i, 6); | ||||
|     delay(1); | ||||
|   } | ||||
|   this->flush(); | ||||
| } | ||||
|  | ||||
| void BL0939::received_package_(const DataPacket *data) const { | ||||
|   // Bad header | ||||
|   if (data->frame_header != BL0939_PACKET_HEADER) { | ||||
|     ESP_LOGI("bl0939", "Invalid data. Header mismatch: %d", data->frame_header); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_; | ||||
|   float ia_rms = (float) to_uint32_t(data->ia_rms) / current_reference_; | ||||
|   float ib_rms = (float) to_uint32_t(data->ib_rms) / current_reference_; | ||||
|   float a_watt = (float) to_int32_t(data->a_watt) / power_reference_; | ||||
|   float b_watt = (float) to_int32_t(data->b_watt) / power_reference_; | ||||
|   int32_t cfa_cnt = to_int32_t(data->cfa_cnt); | ||||
|   int32_t cfb_cnt = to_int32_t(data->cfb_cnt); | ||||
|   float a_energy_consumption = (float) cfa_cnt / energy_reference_; | ||||
|   float b_energy_consumption = (float) cfb_cnt / energy_reference_; | ||||
|   float total_energy_consumption = a_energy_consumption + b_energy_consumption; | ||||
|  | ||||
|   if (voltage_sensor_ != nullptr) { | ||||
|     voltage_sensor_->publish_state(v_rms); | ||||
|   } | ||||
|   if (current_sensor_1_ != nullptr) { | ||||
|     current_sensor_1_->publish_state(ia_rms); | ||||
|   } | ||||
|   if (current_sensor_2_ != nullptr) { | ||||
|     current_sensor_2_->publish_state(ib_rms); | ||||
|   } | ||||
|   if (power_sensor_1_ != nullptr) { | ||||
|     power_sensor_1_->publish_state(a_watt); | ||||
|   } | ||||
|   if (power_sensor_2_ != nullptr) { | ||||
|     power_sensor_2_->publish_state(b_watt); | ||||
|   } | ||||
|   if (energy_sensor_1_ != nullptr) { | ||||
|     energy_sensor_1_->publish_state(a_energy_consumption); | ||||
|   } | ||||
|   if (energy_sensor_2_ != nullptr) { | ||||
|     energy_sensor_2_->publish_state(b_energy_consumption); | ||||
|   } | ||||
|   if (energy_sensor_sum_ != nullptr) { | ||||
|     energy_sensor_sum_->publish_state(total_energy_consumption); | ||||
|   } | ||||
|  | ||||
|   ESP_LOGV("bl0939", "BL0939: U %fV, I1 %fA, I2 %fA, P1 %fW, P2 %fW, CntA %d, CntB %d, ∫P1 %fkWh, ∫P2 %fkWh", v_rms, | ||||
|            ia_rms, ib_rms, a_watt, b_watt, cfa_cnt, cfb_cnt, a_energy_consumption, b_energy_consumption); | ||||
| } | ||||
|  | ||||
| void BL0939::dump_config() {  // NOLINT(readability-function-cognitive-complexity) | ||||
|   ESP_LOGCONFIG(TAG, "BL0939:"); | ||||
|   LOG_SENSOR("", "Voltage", this->voltage_sensor_); | ||||
|   LOG_SENSOR("", "Current 1", this->current_sensor_1_); | ||||
|   LOG_SENSOR("", "Current 2", this->current_sensor_2_); | ||||
|   LOG_SENSOR("", "Power 1", this->power_sensor_1_); | ||||
|   LOG_SENSOR("", "Power 2", this->power_sensor_2_); | ||||
|   LOG_SENSOR("", "Energy 1", this->energy_sensor_1_); | ||||
|   LOG_SENSOR("", "Energy 2", this->energy_sensor_2_); | ||||
|   LOG_SENSOR("", "Energy sum", this->energy_sensor_sum_); | ||||
| } | ||||
|  | ||||
| uint32_t BL0939::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; } | ||||
|  | ||||
| int32_t BL0939::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; } | ||||
|  | ||||
| }  // namespace bl0939 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										107
									
								
								esphome/components/bl0939/bl0939.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								esphome/components/bl0939/bl0939.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/uart/uart.h" | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0939 { | ||||
|  | ||||
| // https://datasheet.lcsc.com/lcsc/2108071830_BL-Shanghai-Belling-BL0939_C2841044.pdf | ||||
| // (unfortunatelly chinese, but the formulas can be easily understood) | ||||
| // Sonoff Dual R3 V2 has the exact same resistor values for the current shunts (RL=1miliOhm) | ||||
| // and for the voltage divider (R1=0.51kOhm, R2=5*390kOhm) | ||||
| // as in the manufacturer's reference circuit, so the same formulas were used here (Vref=1.218V) | ||||
| static const float BL0939_IREF = 324004 * 1 / 1.218; | ||||
| static const float BL0939_UREF = 79931 * 0.51 * 1000 / (1.218 * (5 * 390 + 0.51)); | ||||
| static const float BL0939_PREF = 4046 * 1 * 0.51 * 1000 / (1.218 * 1.218 * (5 * 390 + 0.51)); | ||||
| static const float BL0939_EREF = 3.6e6 * 4046 * 1 * 0.51 * 1000 / (1638.4 * 256 * 1.218 * 1.218 * (5 * 390 + 0.51)); | ||||
|  | ||||
| struct ube24_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align) | ||||
|   uint8_t l; | ||||
|   uint8_t m; | ||||
|   uint8_t h; | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| struct ube16_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align) | ||||
|   uint8_t l; | ||||
|   uint8_t h; | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| struct sbe24_t {  // NOLINT(readability-identifier-naming,altera-struct-pack-align) | ||||
|   uint8_t l; | ||||
|   uint8_t m; | ||||
|   int8_t h; | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| // Caveat: All these values are big endian (low - middle - high) | ||||
|  | ||||
| union DataPacket {  // NOLINT(altera-struct-pack-align) | ||||
|   uint8_t raw[35]; | ||||
|   struct { | ||||
|     uint8_t frame_header;  // 0x55 according to docs | ||||
|     ube24_t ia_fast_rms; | ||||
|     ube24_t ia_rms; | ||||
|     ube24_t ib_rms; | ||||
|     ube24_t v_rms; | ||||
|     ube24_t ib_fast_rms; | ||||
|     sbe24_t a_watt; | ||||
|     sbe24_t b_watt; | ||||
|     sbe24_t cfa_cnt; | ||||
|     sbe24_t cfb_cnt; | ||||
|     ube16_t tps1; | ||||
|     uint8_t RESERVED1;  // value of 0x00 | ||||
|     ube16_t tps2; | ||||
|     uint8_t RESERVED2;  // value of 0x00 | ||||
|     uint8_t checksum;   // checksum | ||||
|   }; | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| class BL0939 : public PollingComponent, public uart::UARTDevice { | ||||
|  public: | ||||
|   void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } | ||||
|   void set_current_sensor_1(sensor::Sensor *current_sensor_1) { current_sensor_1_ = current_sensor_1; } | ||||
|   void set_current_sensor_2(sensor::Sensor *current_sensor_2) { current_sensor_2_ = current_sensor_2; } | ||||
|   void set_power_sensor_1(sensor::Sensor *power_sensor_1) { power_sensor_1_ = power_sensor_1; } | ||||
|   void set_power_sensor_2(sensor::Sensor *power_sensor_2) { power_sensor_2_ = power_sensor_2; } | ||||
|   void set_energy_sensor_1(sensor::Sensor *energy_sensor_1) { energy_sensor_1_ = energy_sensor_1; } | ||||
|   void set_energy_sensor_2(sensor::Sensor *energy_sensor_2) { energy_sensor_2_ = energy_sensor_2; } | ||||
|   void set_energy_sensor_sum(sensor::Sensor *energy_sensor_sum) { energy_sensor_sum_ = energy_sensor_sum; } | ||||
|  | ||||
|   void loop() override; | ||||
|  | ||||
|   void update() override; | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|  | ||||
|  protected: | ||||
|   sensor::Sensor *voltage_sensor_; | ||||
|   sensor::Sensor *current_sensor_1_; | ||||
|   sensor::Sensor *current_sensor_2_; | ||||
|   // NB This may be negative as the circuits is seemingly able to measure | ||||
|   // power in both directions | ||||
|   sensor::Sensor *power_sensor_1_; | ||||
|   sensor::Sensor *power_sensor_2_; | ||||
|   sensor::Sensor *energy_sensor_1_; | ||||
|   sensor::Sensor *energy_sensor_2_; | ||||
|   sensor::Sensor *energy_sensor_sum_; | ||||
|  | ||||
|   // Divide by this to turn into Watt | ||||
|   float power_reference_ = BL0939_PREF; | ||||
|   // Divide by this to turn into Volt | ||||
|   float voltage_reference_ = BL0939_UREF; | ||||
|   // Divide by this to turn into Ampere | ||||
|   float current_reference_ = BL0939_IREF; | ||||
|   // Divide by this to turn into kWh | ||||
|   float energy_reference_ = BL0939_EREF; | ||||
|  | ||||
|   static uint32_t to_uint32_t(ube24_t input); | ||||
|  | ||||
|   static int32_t to_int32_t(sbe24_t input); | ||||
|  | ||||
|   static bool validate_checksum(const DataPacket *data); | ||||
|  | ||||
|   void received_package_(const DataPacket *data) const; | ||||
| }; | ||||
| }  // namespace bl0939 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										123
									
								
								esphome/components/bl0939/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								esphome/components/bl0939/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import sensor, uart | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_VOLTAGE, | ||||
|     DEVICE_CLASS_CURRENT, | ||||
|     DEVICE_CLASS_ENERGY, | ||||
|     DEVICE_CLASS_POWER, | ||||
|     DEVICE_CLASS_VOLTAGE, | ||||
|     STATE_CLASS_MEASUREMENT, | ||||
|     UNIT_AMPERE, | ||||
|     UNIT_KILOWATT_HOURS, | ||||
|     UNIT_VOLT, | ||||
|     UNIT_WATT, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ["uart"] | ||||
|  | ||||
| CONF_CURRENT_1 = "current_1" | ||||
| CONF_CURRENT_2 = "current_2" | ||||
| CONF_ACTIVE_POWER_1 = "active_power_1" | ||||
| CONF_ACTIVE_POWER_2 = "active_power_2" | ||||
| CONF_ENERGY_1 = "energy_1" | ||||
| CONF_ENERGY_2 = "energy_2" | ||||
| CONF_ENERGY_TOTAL = "energy_total" | ||||
|  | ||||
| bl0939_ns = cg.esphome_ns.namespace("bl0939") | ||||
| BL0939 = bl0939_ns.class_("BL0939", cg.PollingComponent, uart.UARTDevice) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(BL0939), | ||||
|             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_1): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_AMPERE, | ||||
|                 accuracy_decimals=2, | ||||
|                 device_class=DEVICE_CLASS_CURRENT, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_CURRENT_2): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_AMPERE, | ||||
|                 accuracy_decimals=2, | ||||
|                 device_class=DEVICE_CLASS_CURRENT, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_ACTIVE_POWER_1): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_WATT, | ||||
|                 accuracy_decimals=0, | ||||
|                 device_class=DEVICE_CLASS_POWER, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_ACTIVE_POWER_2): 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_1): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||
|                 accuracy_decimals=3, | ||||
|                 device_class=DEVICE_CLASS_ENERGY, | ||||
|             ), | ||||
|             cv.Optional(CONF_ENERGY_2): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||
|                 accuracy_decimals=3, | ||||
|                 device_class=DEVICE_CLASS_ENERGY, | ||||
|             ), | ||||
|             cv.Optional(CONF_ENERGY_TOTAL): sensor.sensor_schema( | ||||
|                 unit_of_measurement=UNIT_KILOWATT_HOURS, | ||||
|                 accuracy_decimals=3, | ||||
|                 device_class=DEVICE_CLASS_ENERGY, | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .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_1 in config: | ||||
|         conf = config[CONF_CURRENT_1] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_current_sensor_1(sens)) | ||||
|     if CONF_CURRENT_2 in config: | ||||
|         conf = config[CONF_CURRENT_2] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_current_sensor_2(sens)) | ||||
|     if CONF_ACTIVE_POWER_1 in config: | ||||
|         conf = config[CONF_ACTIVE_POWER_1] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_power_sensor_1(sens)) | ||||
|     if CONF_ACTIVE_POWER_2 in config: | ||||
|         conf = config[CONF_ACTIVE_POWER_2] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_power_sensor_2(sens)) | ||||
|     if CONF_ENERGY_1 in config: | ||||
|         conf = config[CONF_ENERGY_1] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_energy_sensor_1(sens)) | ||||
|     if CONF_ENERGY_2 in config: | ||||
|         conf = config[CONF_ENERGY_2] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_energy_sensor_2(sens)) | ||||
|     if CONF_ENERGY_TOTAL in config: | ||||
|         conf = config[CONF_ENERGY_TOTAL] | ||||
|         sens = await sensor.new_sensor(conf) | ||||
|         cg.add(var.set_energy_sensor_sum(sens)) | ||||
| @@ -256,6 +256,12 @@ uart: | ||||
|     tx_pin: GPIO4 | ||||
|     rx_pin: GPIO5 | ||||
|     baud_rate: 38400 | ||||
|   - id: uart8 | ||||
|     tx_pin: GPIO4 | ||||
|     rx_pin: GPIO5 | ||||
|     baud_rate: 4800 | ||||
|     parity: NONE | ||||
|     stop_bits: 2 | ||||
|     # Specifically added for testing debug with no options at all. | ||||
|     debug: | ||||
|  | ||||
| @@ -477,6 +483,24 @@ sensor: | ||||
|     active_power_b: | ||||
|       name: ADE7953 Active Power B | ||||
|       id: ade7953_active_power_b | ||||
|   - platform: bl0939 | ||||
|     uart_id: uart8 | ||||
|     voltage: | ||||
|       name: 'BL0939 Voltage' | ||||
|     current_1: | ||||
|       name: 'BL0939 Current 1' | ||||
|     current_2: | ||||
|       name: 'BL0939 Current 2' | ||||
|     active_power_1: | ||||
|       name: 'BL0939 Active Power 1' | ||||
|     active_power_2: | ||||
|       name: 'BL0939 Active Power 2' | ||||
|     energy_1: | ||||
|       name: 'BL0939 Energy 1' | ||||
|     energy_2: | ||||
|       name: 'BL0939 Energy 2' | ||||
|     energy_total: | ||||
|       name: 'BL0939 Total energy' | ||||
|   - platform: bl0940 | ||||
|     uart_id: uart3 | ||||
|     voltage: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user