mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	[bl0940] extend configuration options of bl0940 device (#8158)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
		| @@ -66,7 +66,7 @@ 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/bl0940/* @dan-s-github @tobias- | ||||
| esphome/components/bl0942/* @dbuezas @dwmw2 | ||||
| esphome/components/ble_client/* @buxtronix @clydebarrow | ||||
| esphome/components/bluetooth_proxy/* @bdraco @jesserockz | ||||
|   | ||||
| @@ -1 +1,6 @@ | ||||
| CODEOWNERS = ["@tobias-"] | ||||
| import esphome.codegen as cg | ||||
|  | ||||
| CODEOWNERS = ["@tobias-", "@dan-s-github"] | ||||
|  | ||||
| CONF_BL0940_ID = "bl0940_id" | ||||
| bl0940_ns = cg.esphome_ns.namespace("bl0940") | ||||
|   | ||||
| @@ -7,28 +7,26 @@ namespace bl0940 { | ||||
|  | ||||
| static const char *const TAG = "bl0940"; | ||||
|  | ||||
| static const uint8_t BL0940_READ_COMMAND = 0x50;  // 0x58 according to documentation | ||||
| static const uint8_t BL0940_FULL_PACKET = 0xAA; | ||||
| static const uint8_t BL0940_PACKET_HEADER = 0x55;  // 0x58 according to documentation | ||||
| static const uint8_t BL0940_PACKET_HEADER = 0x55;  // 0x58 according to en doc but 0x55 in cn doc | ||||
|  | ||||
| static const uint8_t BL0940_WRITE_COMMAND = 0xA0;  // 0xA8 according to documentation | ||||
| static const uint8_t BL0940_REG_I_FAST_RMS_CTRL = 0x10; | ||||
| static const uint8_t BL0940_REG_MODE = 0x18; | ||||
| static const uint8_t BL0940_REG_SOFT_RESET = 0x19; | ||||
| static const uint8_t BL0940_REG_USR_WRPROT = 0x1A; | ||||
| static const uint8_t BL0940_REG_TPS_CTRL = 0x1B; | ||||
|  | ||||
| const uint8_t BL0940_INIT[5][6] = { | ||||
| static const uint8_t BL0940_INIT[5][5] = { | ||||
|     // Reset to default | ||||
|     {BL0940_WRITE_COMMAND, BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38}, | ||||
|     {BL0940_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x38}, | ||||
|     // Enable User Operation Write | ||||
|     {BL0940_WRITE_COMMAND, BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0}, | ||||
|     {BL0940_REG_USR_WRPROT, 0x55, 0x00, 0x00, 0xF0}, | ||||
|     // 0x0100 = CF_UNABLE energy pulse, AC_FREQ_SEL 50Hz, RMS_UPDATE_SEL 800mS | ||||
|     {BL0940_WRITE_COMMAND, BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37}, | ||||
|     {BL0940_REG_MODE, 0x00, 0x10, 0x00, 0x37}, | ||||
|     // 0x47FF = Over-current and leakage alarm on, Automatic temperature measurement, Interval 100mS | ||||
|     {BL0940_WRITE_COMMAND, BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE}, | ||||
|     {BL0940_REG_TPS_CTRL, 0xFF, 0x47, 0x00, 0xFE}, | ||||
|     // 0x181C = Half cycle, Fast RMS threshold 6172 | ||||
|     {BL0940_WRITE_COMMAND, BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}}; | ||||
|     {BL0940_REG_I_FAST_RMS_CTRL, 0x1C, 0x18, 0x00, 0x1B}}; | ||||
|  | ||||
| void BL0940::loop() { | ||||
|   DataPacket buffer; | ||||
| @@ -36,8 +34,8 @@ void BL0940::loop() { | ||||
|     return; | ||||
|   } | ||||
|   if (read_array((uint8_t *) &buffer, sizeof(buffer))) { | ||||
|     if (validate_checksum(&buffer)) { | ||||
|       received_package_(&buffer); | ||||
|     if (this->validate_checksum_(&buffer)) { | ||||
|       this->received_package_(&buffer); | ||||
|     } | ||||
|   } else { | ||||
|     ESP_LOGW(TAG, "Junk on wire. Throwing away partial message"); | ||||
| @@ -46,35 +44,151 @@ void BL0940::loop() { | ||||
|   } | ||||
| } | ||||
|  | ||||
| bool BL0940::validate_checksum(const DataPacket *data) { | ||||
|   uint8_t checksum = BL0940_READ_COMMAND; | ||||
| bool BL0940::validate_checksum_(DataPacket *data) { | ||||
|   uint8_t checksum = this->read_command_; | ||||
|   // Whole package but checksum | ||||
|   for (uint32_t i = 0; i < sizeof(data->raw) - 1; i++) { | ||||
|     checksum += data->raw[i]; | ||||
|   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, "BL0940 invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum); | ||||
|     ESP_LOGW(TAG, "Invalid checksum! 0x%02X != 0x%02X", checksum, data->checksum); | ||||
|   } | ||||
|   return checksum == data->checksum; | ||||
| } | ||||
|  | ||||
| void BL0940::update() { | ||||
|   this->flush(); | ||||
|   this->write_byte(BL0940_READ_COMMAND); | ||||
|   this->write_byte(this->read_command_); | ||||
|   this->write_byte(BL0940_FULL_PACKET); | ||||
| } | ||||
|  | ||||
| void BL0940::setup() { | ||||
| #ifdef USE_NUMBER | ||||
|   // add calibration callbacks | ||||
|   if (this->voltage_calibration_number_ != nullptr) { | ||||
|     this->voltage_calibration_number_->add_on_state_callback( | ||||
|         [this](float state) { this->voltage_calibration_callback_(state); }); | ||||
|     if (this->voltage_calibration_number_->has_state()) { | ||||
|       this->voltage_calibration_callback_(this->voltage_calibration_number_->state); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (this->current_calibration_number_ != nullptr) { | ||||
|     this->current_calibration_number_->add_on_state_callback( | ||||
|         [this](float state) { this->current_calibration_callback_(state); }); | ||||
|     if (this->current_calibration_number_->has_state()) { | ||||
|       this->current_calibration_callback_(this->current_calibration_number_->state); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (this->power_calibration_number_ != nullptr) { | ||||
|     this->power_calibration_number_->add_on_state_callback( | ||||
|         [this](float state) { this->power_calibration_callback_(state); }); | ||||
|     if (this->power_calibration_number_->has_state()) { | ||||
|       this->power_calibration_callback_(this->power_calibration_number_->state); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (this->energy_calibration_number_ != nullptr) { | ||||
|     this->energy_calibration_number_->add_on_state_callback( | ||||
|         [this](float state) { this->energy_calibration_callback_(state); }); | ||||
|     if (this->energy_calibration_number_->has_state()) { | ||||
|       this->energy_calibration_callback_(this->energy_calibration_number_->state); | ||||
|     } | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   // calculate calibrated reference values | ||||
|   this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_; | ||||
|   this->current_reference_cal_ = this->current_reference_ / this->current_cal_; | ||||
|   this->power_reference_cal_ = this->power_reference_ / this->power_cal_; | ||||
|   this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_; | ||||
|  | ||||
|   for (auto *i : BL0940_INIT) { | ||||
|     this->write_array(i, 6); | ||||
|     this->write_byte(this->write_command_), this->write_array(i, 5); | ||||
|     delay(1); | ||||
|   } | ||||
|   this->flush(); | ||||
| } | ||||
|  | ||||
| float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const { | ||||
|   auto tb = (float) (temperature.h << 8 | temperature.l); | ||||
| float BL0940::calculate_power_reference_() { | ||||
|   // calculate power reference based on voltage and current reference | ||||
|   return this->voltage_reference_cal_ * this->current_reference_cal_ * 4046 / 324004 / 79931; | ||||
| } | ||||
|  | ||||
| float BL0940::calculate_energy_reference_() { | ||||
|   // formula: 3600000 * 4046 * RL * R1 * 1000 / (1638.4 * 256) / Vref² / (R1 + R2) | ||||
|   // or:  power_reference_ * 3600000 / (1638.4 * 256) | ||||
|   return this->power_reference_cal_ * 3600000 / (1638.4 * 256); | ||||
| } | ||||
|  | ||||
| float BL0940::calculate_calibration_value_(float state) { return (100 + state) / 100; } | ||||
|  | ||||
| void BL0940::reset_calibration() { | ||||
| #ifdef USE_NUMBER | ||||
|   if (this->current_calibration_number_ != nullptr && this->current_cal_ != 1) { | ||||
|     this->current_calibration_number_->make_call().set_value(0).perform(); | ||||
|   } | ||||
|   if (this->voltage_calibration_number_ != nullptr && this->voltage_cal_ != 1) { | ||||
|     this->voltage_calibration_number_->make_call().set_value(0).perform(); | ||||
|   } | ||||
|   if (this->power_calibration_number_ != nullptr && this->power_cal_ != 1) { | ||||
|     this->power_calibration_number_->make_call().set_value(0).perform(); | ||||
|   } | ||||
|   if (this->energy_calibration_number_ != nullptr && this->energy_cal_ != 1) { | ||||
|     this->energy_calibration_number_->make_call().set_value(0).perform(); | ||||
|   } | ||||
| #endif | ||||
|   ESP_LOGD(TAG, "external calibration values restored to initial state"); | ||||
| } | ||||
|  | ||||
| void BL0940::current_calibration_callback_(float state) { | ||||
|   this->current_cal_ = this->calculate_calibration_value_(state); | ||||
|   ESP_LOGV(TAG, "update current calibration state: %f", this->current_cal_); | ||||
|   this->recalibrate_(); | ||||
| } | ||||
| void BL0940::voltage_calibration_callback_(float state) { | ||||
|   this->voltage_cal_ = this->calculate_calibration_value_(state); | ||||
|   ESP_LOGV(TAG, "update voltage calibration state: %f", this->voltage_cal_); | ||||
|   this->recalibrate_(); | ||||
| } | ||||
| void BL0940::power_calibration_callback_(float state) { | ||||
|   this->power_cal_ = this->calculate_calibration_value_(state); | ||||
|   ESP_LOGV(TAG, "update power calibration state: %f", this->power_cal_); | ||||
|   this->recalibrate_(); | ||||
| } | ||||
| void BL0940::energy_calibration_callback_(float state) { | ||||
|   this->energy_cal_ = this->calculate_calibration_value_(state); | ||||
|   ESP_LOGV(TAG, "update energy calibration state: %f", this->energy_cal_); | ||||
|   this->recalibrate_(); | ||||
| } | ||||
|  | ||||
| void BL0940::recalibrate_() { | ||||
|   ESP_LOGV(TAG, "Recalibrating reference values"); | ||||
|   this->voltage_reference_cal_ = this->voltage_reference_ / this->voltage_cal_; | ||||
|   this->current_reference_cal_ = this->current_reference_ / this->current_cal_; | ||||
|  | ||||
|   if (this->voltage_cal_ != 1 || this->current_cal_ != 1) { | ||||
|     this->power_reference_ = this->calculate_power_reference_(); | ||||
|   } | ||||
|   this->power_reference_cal_ = this->power_reference_ / this->power_cal_; | ||||
|  | ||||
|   if (this->voltage_cal_ != 1 || this->current_cal_ != 1 || this->power_cal_ != 1) { | ||||
|     this->energy_reference_ = this->calculate_energy_reference_(); | ||||
|   } | ||||
|   this->energy_reference_cal_ = this->energy_reference_ / this->energy_cal_; | ||||
|  | ||||
|   ESP_LOGD(TAG, | ||||
|            "Recalibrated reference values:\n" | ||||
|            "Voltage: %f\n, Current: %f\n, Power: %f\n, Energy: %f\n", | ||||
|            this->voltage_reference_cal_, this->current_reference_cal_, this->power_reference_cal_, | ||||
|            this->energy_reference_cal_); | ||||
| } | ||||
|  | ||||
| float BL0940::update_temp_(sensor::Sensor *sensor, uint16_le_t temperature) const { | ||||
|   auto tb = (float) temperature; | ||||
|   float converted_temp = ((float) 170 / 448) * (tb / 2 - 32) - 45; | ||||
|   if (sensor != nullptr) { | ||||
|     if (sensor->has_state() && std::abs(converted_temp - sensor->get_state()) > max_temperature_diff_) { | ||||
| @@ -87,33 +201,40 @@ float BL0940::update_temp_(sensor::Sensor *sensor, ube16_t temperature) const { | ||||
|   return converted_temp; | ||||
| } | ||||
|  | ||||
| void BL0940::received_package_(const DataPacket *data) const { | ||||
| void BL0940::received_package_(DataPacket *data) { | ||||
|   // Bad header | ||||
|   if (data->frame_header != BL0940_PACKET_HEADER) { | ||||
|     ESP_LOGI(TAG, "Invalid data. Header mismatch: %d", data->frame_header); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   float v_rms = (float) to_uint32_t(data->v_rms) / voltage_reference_; | ||||
|   float i_rms = (float) to_uint32_t(data->i_rms) / current_reference_; | ||||
|   float watt = (float) to_int32_t(data->watt) / power_reference_; | ||||
|   uint32_t cf_cnt = to_uint32_t(data->cf_cnt); | ||||
|   float total_energy_consumption = (float) cf_cnt / energy_reference_; | ||||
|   // cf_cnt is only 24 bits, so track overflows | ||||
|   uint32_t cf_cnt = (uint24_t) data->cf_cnt; | ||||
|   cf_cnt |= this->prev_cf_cnt_ & 0xff000000; | ||||
|   if (cf_cnt < this->prev_cf_cnt_) { | ||||
|     cf_cnt += 0x1000000; | ||||
|   } | ||||
|   this->prev_cf_cnt_ = cf_cnt; | ||||
|  | ||||
|   float tps1 = update_temp_(internal_temperature_sensor_, data->tps1); | ||||
|   float tps2 = update_temp_(external_temperature_sensor_, data->tps2); | ||||
|   float v_rms = (uint24_t) data->v_rms / this->voltage_reference_cal_; | ||||
|   float i_rms = (uint24_t) data->i_rms / this->current_reference_cal_; | ||||
|   float watt = (int24_t) data->watt / this->power_reference_cal_; | ||||
|   float total_energy_consumption = cf_cnt / this->energy_reference_cal_; | ||||
|  | ||||
|   if (voltage_sensor_ != nullptr) { | ||||
|     voltage_sensor_->publish_state(v_rms); | ||||
|   float tps1 = update_temp_(this->internal_temperature_sensor_, data->tps1); | ||||
|   float tps2 = update_temp_(this->external_temperature_sensor_, data->tps2); | ||||
|  | ||||
|   if (this->voltage_sensor_ != nullptr) { | ||||
|     this->voltage_sensor_->publish_state(v_rms); | ||||
|   } | ||||
|   if (current_sensor_ != nullptr) { | ||||
|     current_sensor_->publish_state(i_rms); | ||||
|   if (this->current_sensor_ != nullptr) { | ||||
|     this->current_sensor_->publish_state(i_rms); | ||||
|   } | ||||
|   if (power_sensor_ != nullptr) { | ||||
|     power_sensor_->publish_state(watt); | ||||
|   if (this->power_sensor_ != nullptr) { | ||||
|     this->power_sensor_->publish_state(watt); | ||||
|   } | ||||
|   if (energy_sensor_ != nullptr) { | ||||
|     energy_sensor_->publish_state(total_energy_consumption); | ||||
|   if (this->energy_sensor_ != nullptr) { | ||||
|     this->energy_sensor_->publish_state(total_energy_consumption); | ||||
|   } | ||||
|  | ||||
|   ESP_LOGV(TAG, "BL0940: U %fV, I %fA, P %fW, Cnt %" PRId32 ", ∫P %fkWh, T1 %f°C, T2 %f°C", v_rms, i_rms, watt, cf_cnt, | ||||
| @@ -121,7 +242,27 @@ void BL0940::received_package_(const DataPacket *data) const { | ||||
| } | ||||
|  | ||||
| void BL0940::dump_config() {  // NOLINT(readability-function-cognitive-complexity) | ||||
|   ESP_LOGCONFIG(TAG, "BL0940:"); | ||||
|   ESP_LOGCONFIG(TAG, | ||||
|                 "BL0940:\n" | ||||
|                 "  LEGACY MODE: %s\n" | ||||
|                 "  READ  CMD: 0x%02X\n" | ||||
|                 "  WRITE CMD: 0x%02X\n" | ||||
|                 "  ------------------\n" | ||||
|                 "  Current reference: %f\n" | ||||
|                 "  Energy reference: %f\n" | ||||
|                 "  Power reference: %f\n" | ||||
|                 "  Voltage reference: %f\n", | ||||
|                 TRUEFALSE(this->legacy_mode_enabled_), this->read_command_, this->write_command_, | ||||
|                 this->current_reference_, this->energy_reference_, this->power_reference_, this->voltage_reference_); | ||||
| #ifdef USE_NUMBER | ||||
|   ESP_LOGCONFIG(TAG, | ||||
|                 "BL0940:\n" | ||||
|                 "  Current calibration: %f\n" | ||||
|                 "  Energy calibration: %f\n" | ||||
|                 "  Power calibration: %f\n" | ||||
|                 "  Voltage calibration: %f\n", | ||||
|                 this->current_cal_, this->energy_cal_, this->power_cal_, this->voltage_cal_); | ||||
| #endif | ||||
|   LOG_SENSOR("", "Voltage", this->voltage_sensor_); | ||||
|   LOG_SENSOR("", "Current", this->current_sensor_); | ||||
|   LOG_SENSOR("", "Power", this->power_sensor_); | ||||
| @@ -130,9 +271,5 @@ void BL0940::dump_config() {  // NOLINT(readability-function-cognitive-complexit | ||||
|   LOG_SENSOR("", "External temperature", this->external_temperature_sensor_); | ||||
| } | ||||
|  | ||||
| uint32_t BL0940::to_uint32_t(ube24_t input) { return input.h << 16 | input.m << 8 | input.l; } | ||||
|  | ||||
| int32_t BL0940::to_int32_t(sbe24_t input) { return input.h << 16 | input.m << 8 | input.l; } | ||||
|  | ||||
| }  // namespace bl0940 | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -1,66 +1,48 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/uart/uart.h" | ||||
| #include "esphome/core/datatypes.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #ifdef USE_BUTTON | ||||
| #include "esphome/components/button/button.h" | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
| #include "esphome/components/number/number.h" | ||||
| #endif | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #include "esphome/components/uart/uart.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0940 { | ||||
|  | ||||
| static const float BL0940_PREF = 1430; | ||||
| static const float BL0940_UREF = 33000; | ||||
| static const float BL0940_IREF = 275000;  // 2750 from tasmota. Seems to generate values 100 times too high | ||||
|  | ||||
| // Measured to 297J  per click according to power consumption of 5 minutes | ||||
| // Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4 | ||||
| static const float BL0940_EREF = 3.6e6 / 297; | ||||
|  | ||||
| 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;  // value of 0x58 according to docs. 0x55 according to Tasmota real world tests. Reality wins. | ||||
|     ube24_t i_fast_rms;    // 0x00 | ||||
|     ube24_t i_rms;         // 0x04 | ||||
|     ube24_t RESERVED0;     // reserved | ||||
|     ube24_t v_rms;         // 0x06 | ||||
|     ube24_t RESERVED1;     // reserved | ||||
|     sbe24_t watt;          // 0x08 | ||||
|     ube24_t RESERVED2;     // reserved | ||||
|     ube24_t cf_cnt;        // 0x0A | ||||
|     ube24_t RESERVED3;     // reserved | ||||
|     ube16_t tps1;          // 0x0c | ||||
|     uint8_t RESERVED4;     // value of 0x00 | ||||
|     ube16_t tps2;          // 0x0c | ||||
|     uint8_t RESERVED5;     // value of 0x00 | ||||
|     uint8_t checksum;      // checksum | ||||
|   }; | ||||
| struct DataPacket { | ||||
|   uint8_t frame_header;    // Packet header (0x58 in EN docs, 0x55 in CN docs and Tasmota tests) | ||||
|   uint24_le_t i_fast_rms;  // Fast RMS current | ||||
|   uint24_le_t i_rms;       // RMS current | ||||
|   uint24_t RESERVED0;      // Reserved | ||||
|   uint24_le_t v_rms;       // RMS voltage | ||||
|   uint24_t RESERVED1;      // Reserved | ||||
|   int24_le_t watt;         // Active power (can be negative for bidirectional measurement) | ||||
|   uint24_t RESERVED2;      // Reserved | ||||
|   uint24_le_t cf_cnt;      // Energy pulse count | ||||
|   uint24_t RESERVED3;      // Reserved | ||||
|   uint16_le_t tps1;        // Internal temperature sensor 1 | ||||
|   uint8_t RESERVED4;       // Reserved (should be 0x00) | ||||
|   uint16_le_t tps2;        // Internal temperature sensor 2 | ||||
|   uint8_t RESERVED5;       // Reserved (should be 0x00) | ||||
|   uint8_t checksum;        // Packet checksum | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| class BL0940 : public PollingComponent, public uart::UARTDevice { | ||||
|  public: | ||||
|   // Sensor setters | ||||
|   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; } | ||||
|  | ||||
|   // Temperature sensor setters | ||||
|   void set_internal_temperature_sensor(sensor::Sensor *internal_temperature_sensor) { | ||||
|     internal_temperature_sensor_ = internal_temperature_sensor; | ||||
|   } | ||||
| @@ -68,42 +50,105 @@ class BL0940 : public PollingComponent, public uart::UARTDevice { | ||||
|     external_temperature_sensor_ = external_temperature_sensor; | ||||
|   } | ||||
|  | ||||
|   void loop() override; | ||||
|   // Configuration setters | ||||
|   void set_legacy_mode(bool enable) { this->legacy_mode_enabled_ = enable; } | ||||
|   void set_read_command(uint8_t read_command) { this->read_command_ = read_command; } | ||||
|   void set_write_command(uint8_t write_command) { this->write_command_ = write_command; } | ||||
|  | ||||
|   // Reference value setters (used for calibration and conversion) | ||||
|   void set_current_reference(float current_ref) { this->current_reference_ = current_ref; } | ||||
|   void set_energy_reference(float energy_ref) { this->energy_reference_ = energy_ref; } | ||||
|   void set_power_reference(float power_ref) { this->power_reference_ = power_ref; } | ||||
|   void set_voltage_reference(float voltage_ref) { this->voltage_reference_ = voltage_ref; } | ||||
|  | ||||
| #ifdef USE_NUMBER | ||||
|   // Calibration number setters (for Home Assistant number entities) | ||||
|   void set_current_calibration_number(number::Number *num) { this->current_calibration_number_ = num; } | ||||
|   void set_voltage_calibration_number(number::Number *num) { this->voltage_calibration_number_ = num; } | ||||
|   void set_power_calibration_number(number::Number *num) { this->power_calibration_number_ = num; } | ||||
|   void set_energy_calibration_number(number::Number *num) { this->energy_calibration_number_ = num; } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_BUTTON | ||||
|   // Resets all calibration values to defaults (can be triggered by a button) | ||||
|   void reset_calibration(); | ||||
| #endif | ||||
|  | ||||
|   // Core component methods | ||||
|   void loop() override; | ||||
|   void update() override; | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|  | ||||
|  protected: | ||||
|   sensor::Sensor *voltage_sensor_{nullptr}; | ||||
|   sensor::Sensor *current_sensor_{nullptr}; | ||||
|   // NB This may be negative as the circuits is seemingly able to measure | ||||
|   // power in both directions | ||||
|   sensor::Sensor *power_sensor_{nullptr}; | ||||
|   sensor::Sensor *energy_sensor_{nullptr}; | ||||
|   sensor::Sensor *internal_temperature_sensor_{nullptr}; | ||||
|   sensor::Sensor *external_temperature_sensor_{nullptr}; | ||||
|   // --- Sensor pointers --- | ||||
|   sensor::Sensor *voltage_sensor_{nullptr};               // Voltage sensor | ||||
|   sensor::Sensor *current_sensor_{nullptr};               // Current sensor | ||||
|   sensor::Sensor *power_sensor_{nullptr};                 // Power sensor (can be negative for bidirectional) | ||||
|   sensor::Sensor *energy_sensor_{nullptr};                // Energy sensor | ||||
|   sensor::Sensor *internal_temperature_sensor_{nullptr};  // Internal temperature sensor | ||||
|   sensor::Sensor *external_temperature_sensor_{nullptr};  // External temperature sensor | ||||
|  | ||||
|   // Max difference between two measurements of the temperature. Used to avoid noise. | ||||
|   float max_temperature_diff_{0}; | ||||
|   // Divide by this to turn into Watt | ||||
|   float power_reference_ = BL0940_PREF; | ||||
|   // Divide by this to turn into Volt | ||||
|   float voltage_reference_ = BL0940_UREF; | ||||
|   // Divide by this to turn into Ampere | ||||
|   float current_reference_ = BL0940_IREF; | ||||
|   // Divide by this to turn into kWh | ||||
|   float energy_reference_ = BL0940_EREF; | ||||
| #ifdef USE_NUMBER | ||||
|   // --- Calibration number entities (for dynamic calibration via HA UI) --- | ||||
|   number::Number *voltage_calibration_number_{nullptr}; | ||||
|   number::Number *current_calibration_number_{nullptr}; | ||||
|   number::Number *power_calibration_number_{nullptr}; | ||||
|   number::Number *energy_calibration_number_{nullptr}; | ||||
| #endif | ||||
|  | ||||
|   float update_temp_(sensor::Sensor *sensor, ube16_t packed_temperature) const; | ||||
|   // --- Internal state --- | ||||
|   uint32_t prev_cf_cnt_ = 0;       // Previous energy pulse count (for energy calculation) | ||||
|   float max_temperature_diff_{0};  // Max allowed temperature difference between two measurements (noise filter) | ||||
|  | ||||
|   static uint32_t to_uint32_t(ube24_t input); | ||||
|   // --- Reference values for conversion --- | ||||
|   float power_reference_;        // Divider for raw power to get Watts | ||||
|   float power_reference_cal_;    // Calibrated power reference | ||||
|   float voltage_reference_;      // Divider for raw voltage to get Volts | ||||
|   float voltage_reference_cal_;  // Calibrated voltage reference | ||||
|   float current_reference_;      // Divider for raw current to get Amperes | ||||
|   float current_reference_cal_;  // Calibrated current reference | ||||
|   float energy_reference_;       // Divider for raw energy to get kWh | ||||
|   float energy_reference_cal_;   // Calibrated energy reference | ||||
|  | ||||
|   static int32_t to_int32_t(sbe24_t input); | ||||
|   // --- Home Assistant calibration values (multipliers, default 1) --- | ||||
|   float current_cal_{1}; | ||||
|   float voltage_cal_{1}; | ||||
|   float power_cal_{1}; | ||||
|   float energy_cal_{1}; | ||||
|  | ||||
|   static bool validate_checksum(const DataPacket *data); | ||||
|   // --- Protocol commands --- | ||||
|   uint8_t read_command_; | ||||
|   uint8_t write_command_; | ||||
|  | ||||
|   void received_package_(const DataPacket *data) const; | ||||
|   // --- Mode flags --- | ||||
|   bool legacy_mode_enabled_ = true; | ||||
|  | ||||
|   // --- Methods --- | ||||
|   // Converts packed temperature value to float and updates the sensor | ||||
|   float update_temp_(sensor::Sensor *sensor, uint16_le_t packed_temperature) const; | ||||
|  | ||||
|   // Validates the checksum of a received data packet | ||||
|   bool validate_checksum_(DataPacket *data); | ||||
|  | ||||
|   // Handles a received data packet | ||||
|   void received_package_(DataPacket *data); | ||||
|  | ||||
|   // Calculates reference values for calibration and conversion | ||||
|   float calculate_energy_reference_(); | ||||
|   float calculate_power_reference_(); | ||||
|   float calculate_calibration_value_(float state); | ||||
|  | ||||
|   // Calibration update callbacks (used with number entities) | ||||
|   void current_calibration_callback_(float state); | ||||
|   void voltage_calibration_callback_(float state); | ||||
|   void power_calibration_callback_(float state); | ||||
|   void energy_calibration_callback_(float state); | ||||
|   void reset_calibration_callback_(); | ||||
|  | ||||
|   // Recalculates all reference values after calibration changes | ||||
|   void recalibrate_(); | ||||
| }; | ||||
|  | ||||
| }  // namespace bl0940 | ||||
| }  // namespace esphome | ||||
|   | ||||
							
								
								
									
										27
									
								
								esphome/components/bl0940/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								esphome/components/bl0940/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import button | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ENTITY_CATEGORY_CONFIG, ICON_RESTART | ||||
|  | ||||
| from .. import CONF_BL0940_ID, bl0940_ns | ||||
| from ..sensor import BL0940 | ||||
|  | ||||
| CalibrationResetButton = bl0940_ns.class_( | ||||
|     "CalibrationResetButton", button.Button, cg.Component | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     button.button_schema( | ||||
|         CalibrationResetButton, | ||||
|         entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|         icon=ICON_RESTART, | ||||
|     ) | ||||
|     .extend({cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940)}) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = await button.new_button(config) | ||||
|     await cg.register_component(var, config) | ||||
|     await cg.register_parented(var, config[CONF_BL0940_ID]) | ||||
| @@ -0,0 +1,20 @@ | ||||
| #include "calibration_reset_button.h" | ||||
| #include "../bl0940.h" | ||||
| #include "esphome/core/hal.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/application.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0940 { | ||||
|  | ||||
| static const char *const TAG = "bl0940.button.calibration_reset"; | ||||
|  | ||||
| void CalibrationResetButton::dump_config() { LOG_BUTTON("", "Calibration Reset Button", this); } | ||||
|  | ||||
| void CalibrationResetButton::press_action() { | ||||
|   ESP_LOGI(TAG, "Resetting calibration defaults..."); | ||||
|   this->parent_->reset_calibration(); | ||||
| } | ||||
|  | ||||
| }  // namespace bl0940 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										19
									
								
								esphome/components/bl0940/button/calibration_reset_button.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								esphome/components/bl0940/button/calibration_reset_button.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/button/button.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0940 { | ||||
|  | ||||
| class BL0940;  // Forward declaration of BL0940 class | ||||
|  | ||||
| class CalibrationResetButton : public button::Button, public Component, public Parented<BL0940> { | ||||
|  public: | ||||
|   void dump_config() override; | ||||
|  | ||||
|   void press_action() override; | ||||
| }; | ||||
|  | ||||
| }  // namespace bl0940 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										94
									
								
								esphome/components/bl0940/number/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								esphome/components/bl0940/number/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import number | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_MAX_VALUE, | ||||
|     CONF_MIN_VALUE, | ||||
|     CONF_MODE, | ||||
|     CONF_RESTORE_VALUE, | ||||
|     CONF_STEP, | ||||
|     ENTITY_CATEGORY_CONFIG, | ||||
|     UNIT_PERCENT, | ||||
| ) | ||||
|  | ||||
| from .. import CONF_BL0940_ID, bl0940_ns | ||||
| from ..sensor import BL0940 | ||||
|  | ||||
| # Define calibration types | ||||
| CONF_CURRENT_CALIBRATION = "current_calibration" | ||||
| CONF_VOLTAGE_CALIBRATION = "voltage_calibration" | ||||
| CONF_POWER_CALIBRATION = "power_calibration" | ||||
| CONF_ENERGY_CALIBRATION = "energy_calibration" | ||||
|  | ||||
| BL0940Number = bl0940_ns.class_("BL0940Number") | ||||
|  | ||||
| CalibrationNumber = bl0940_ns.class_( | ||||
|     "CalibrationNumber", number.Number, cg.PollingComponent | ||||
| ) | ||||
|  | ||||
|  | ||||
| def validate_min_max(config): | ||||
|     if config[CONF_MAX_VALUE] <= config[CONF_MIN_VALUE]: | ||||
|         raise cv.Invalid("max_value must be greater than min_value") | ||||
|     return config | ||||
|  | ||||
|  | ||||
| CALIBRATION_SCHEMA = cv.All( | ||||
|     number.number_schema( | ||||
|         CalibrationNumber, | ||||
|         entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|         unit_of_measurement=UNIT_PERCENT, | ||||
|     ) | ||||
|     .extend( | ||||
|         { | ||||
|             cv.Optional(CONF_MODE, default="BOX"): cv.enum(number.NUMBER_MODES), | ||||
|             cv.Optional(CONF_MAX_VALUE, default=10): cv.All( | ||||
|                 cv.float_, cv.Range(max=50) | ||||
|             ), | ||||
|             cv.Optional(CONF_MIN_VALUE, default=-10): cv.All( | ||||
|                 cv.float_, cv.Range(min=-50) | ||||
|             ), | ||||
|             cv.Optional(CONF_STEP, default=0.1): cv.positive_float, | ||||
|             cv.Optional(CONF_RESTORE_VALUE): cv.boolean, | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA), | ||||
|     validate_min_max, | ||||
| ) | ||||
|  | ||||
| # Configuration schema for BL0940 numbers | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(BL0940Number), | ||||
|         cv.GenerateID(CONF_BL0940_ID): cv.use_id(BL0940), | ||||
|         cv.Optional(CONF_CURRENT_CALIBRATION): CALIBRATION_SCHEMA, | ||||
|         cv.Optional(CONF_VOLTAGE_CALIBRATION): CALIBRATION_SCHEMA, | ||||
|         cv.Optional(CONF_POWER_CALIBRATION): CALIBRATION_SCHEMA, | ||||
|         cv.Optional(CONF_ENERGY_CALIBRATION): CALIBRATION_SCHEMA, | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     # Get the BL0940 component instance | ||||
|     bl0940 = await cg.get_variable(config[CONF_BL0940_ID]) | ||||
|  | ||||
|     # Process all calibration types | ||||
|     for cal_type, setter_method in [ | ||||
|         (CONF_CURRENT_CALIBRATION, "set_current_calibration_number"), | ||||
|         (CONF_VOLTAGE_CALIBRATION, "set_voltage_calibration_number"), | ||||
|         (CONF_POWER_CALIBRATION, "set_power_calibration_number"), | ||||
|         (CONF_ENERGY_CALIBRATION, "set_energy_calibration_number"), | ||||
|     ]: | ||||
|         if conf := config.get(cal_type): | ||||
|             var = await number.new_number( | ||||
|                 conf, | ||||
|                 min_value=conf.get(CONF_MIN_VALUE), | ||||
|                 max_value=conf.get(CONF_MAX_VALUE), | ||||
|                 step=conf.get(CONF_STEP), | ||||
|             ) | ||||
|             await cg.register_component(var, conf) | ||||
|  | ||||
|             if restore_value := config.get(CONF_RESTORE_VALUE): | ||||
|                 cg.add(var.set_restore_value(restore_value)) | ||||
|             cg.add(getattr(bl0940, setter_method)(var)) | ||||
							
								
								
									
										29
									
								
								esphome/components/bl0940/number/calibration_number.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								esphome/components/bl0940/number/calibration_number.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| #include "calibration_number.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0940 { | ||||
|  | ||||
| static const char *const TAG = "bl0940.number"; | ||||
|  | ||||
| void CalibrationNumber::setup() { | ||||
|   float value = 0.0f; | ||||
|   if (this->restore_value_) { | ||||
|     this->pref_ = global_preferences->make_preference<float>(this->get_object_id_hash()); | ||||
|     if (!this->pref_.load(&value)) { | ||||
|       value = 0.0f; | ||||
|     } | ||||
|   } | ||||
|   this->publish_state(value); | ||||
| } | ||||
|  | ||||
| void CalibrationNumber::control(float value) { | ||||
|   this->publish_state(value); | ||||
|   if (this->restore_value_) | ||||
|     this->pref_.save(&value); | ||||
| } | ||||
|  | ||||
| void CalibrationNumber::dump_config() { LOG_NUMBER("", "Calibration Number", this); } | ||||
|  | ||||
| }  // namespace bl0940 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										26
									
								
								esphome/components/bl0940/number/calibration_number.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/bl0940/number/calibration_number.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/number/number.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/preferences.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace bl0940 { | ||||
|  | ||||
| class CalibrationNumber : public number::Number, public Component { | ||||
|  public: | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return setup_priority::HARDWARE; } | ||||
|  | ||||
|   void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } | ||||
|  | ||||
|  protected: | ||||
|   void control(float value) override; | ||||
|   bool restore_value_{true}; | ||||
|  | ||||
|   ESPPreferenceObject pref_; | ||||
| }; | ||||
|  | ||||
| }  // namespace bl0940 | ||||
| }  // namespace esphome | ||||
| @@ -8,6 +8,7 @@ from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_INTERNAL_TEMPERATURE, | ||||
|     CONF_POWER, | ||||
|     CONF_REFERENCE_VOLTAGE, | ||||
|     CONF_VOLTAGE, | ||||
|     DEVICE_CLASS_CURRENT, | ||||
|     DEVICE_CLASS_ENERGY, | ||||
| @@ -23,12 +24,133 @@ from esphome.const import ( | ||||
|     UNIT_WATT, | ||||
| ) | ||||
|  | ||||
| from . import bl0940_ns | ||||
|  | ||||
| DEPENDENCIES = ["uart"] | ||||
|  | ||||
|  | ||||
| bl0940_ns = cg.esphome_ns.namespace("bl0940") | ||||
| BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice) | ||||
|  | ||||
| CONF_LEGACY_MODE = "legacy_mode" | ||||
|  | ||||
| CONF_READ_COMMAND = "read_command" | ||||
| CONF_WRITE_COMMAND = "write_command" | ||||
|  | ||||
| CONF_RESISTOR_SHUNT = "resistor_shunt" | ||||
| CONF_RESISTOR_ONE = "resistor_one" | ||||
| CONF_RESISTOR_TWO = "resistor_two" | ||||
|  | ||||
| CONF_CURRENT_REFERENCE = "current_reference" | ||||
| CONF_ENERGY_REFERENCE = "energy_reference" | ||||
| CONF_POWER_REFERENCE = "power_reference" | ||||
| CONF_VOLTAGE_REFERENCE = "voltage_reference" | ||||
|  | ||||
| DEFAULT_BL0940_READ_COMMAND = 0x58 | ||||
| DEFAULT_BL0940_WRITE_COMMAND = 0xA1 | ||||
|  | ||||
| # Values according to BL0940 application note: | ||||
| # https://www.belling.com.cn/media/file_object/bel_product/BL0940/guide/BL0940_APPNote_TSSOP14_V1.04_EN.pdf | ||||
| DEFAULT_BL0940_VREF = 1.218  # Vref = 1.218 | ||||
| DEFAULT_BL0940_RL = 1  # RL = 1 mΩ | ||||
| DEFAULT_BL0940_R1 = 0.51  # R1 = 0.51 kΩ | ||||
| DEFAULT_BL0940_R2 = 1950  # R2 = 5 x 390 kΩ -> 1950 kΩ | ||||
|  | ||||
| # ---------------------------------------------------- | ||||
| # values from initial implementation | ||||
| DEFAULT_BL0940_LEGACY_READ_COMMAND = 0x50 | ||||
| DEFAULT_BL0940_LEGACY_WRITE_COMMAND = 0xA0 | ||||
|  | ||||
| DEFAULT_BL0940_LEGACY_UREF = 33000 | ||||
| DEFAULT_BL0940_LEGACY_IREF = 275000 | ||||
| DEFAULT_BL0940_LEGACY_PREF = 1430 | ||||
| # Measured to 297J  per click according to power consumption of 5 minutes | ||||
| # Converted to kWh (3.6MJ per kwH). Used to be 256 * 1638.4 | ||||
| DEFAULT_BL0940_LEGACY_EREF = 3.6e6 / 297 | ||||
| # ---------------------------------------------------- | ||||
|  | ||||
|  | ||||
| # methods to calculate voltage and current reference values | ||||
| def calculate_voltage_reference(vref, r_one, r_two): | ||||
|     # formula: 79931 / Vref * (R1 * 1000) / (R1 + R2) | ||||
|     return 79931 / vref * (r_one * 1000) / (r_one + r_two) | ||||
|  | ||||
|  | ||||
| def calculate_current_reference(vref, r_shunt): | ||||
|     # formula: 324004 * RL / Vref | ||||
|     return 324004 * r_shunt / vref | ||||
|  | ||||
|  | ||||
| def calculate_power_reference(voltage_reference, current_reference): | ||||
|     # calculate power reference based on voltage and current reference | ||||
|     return voltage_reference * current_reference * 4046 / 324004 / 79931 | ||||
|  | ||||
|  | ||||
| def calculate_energy_reference(power_reference): | ||||
|     # formula: power_reference * 3600000 / (1638.4 * 256) | ||||
|     return power_reference * 3600000 / (1638.4 * 256) | ||||
|  | ||||
|  | ||||
| def validate_legacy_mode(config): | ||||
|     # Only allow schematic calibration options if legacy_mode is False | ||||
|     if config.get(CONF_LEGACY_MODE, True): | ||||
|         forbidden = [ | ||||
|             CONF_REFERENCE_VOLTAGE, | ||||
|             CONF_RESISTOR_SHUNT, | ||||
|             CONF_RESISTOR_ONE, | ||||
|             CONF_RESISTOR_TWO, | ||||
|         ] | ||||
|         for key in forbidden: | ||||
|             if key in config: | ||||
|                 raise cv.Invalid( | ||||
|                     f"Option '{key}' is only allowed when legacy_mode: false" | ||||
|                 ) | ||||
|     return config | ||||
|  | ||||
|  | ||||
| def set_command_defaults(config): | ||||
|     # Set defaults for read_command and write_command based on legacy_mode | ||||
|     legacy = config.get(CONF_LEGACY_MODE, True) | ||||
|     if legacy: | ||||
|         config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_LEGACY_READ_COMMAND) | ||||
|         config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_LEGACY_WRITE_COMMAND) | ||||
|     else: | ||||
|         config.setdefault(CONF_READ_COMMAND, DEFAULT_BL0940_READ_COMMAND) | ||||
|         config.setdefault(CONF_WRITE_COMMAND, DEFAULT_BL0940_WRITE_COMMAND) | ||||
|     return config | ||||
|  | ||||
|  | ||||
| def set_reference_values(config): | ||||
|     # Set default reference values based on legacy_mode | ||||
|     if config.get(CONF_LEGACY_MODE, True): | ||||
|         config.setdefault(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_LEGACY_UREF) | ||||
|         config.setdefault(CONF_CURRENT_REFERENCE, DEFAULT_BL0940_LEGACY_IREF) | ||||
|         config.setdefault(CONF_POWER_REFERENCE, DEFAULT_BL0940_LEGACY_PREF) | ||||
|         config.setdefault(CONF_ENERGY_REFERENCE, DEFAULT_BL0940_LEGACY_PREF) | ||||
|     else: | ||||
|         vref = config.get(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_VREF) | ||||
|         r_one = config.get(CONF_RESISTOR_ONE, DEFAULT_BL0940_R1) | ||||
|         r_two = config.get(CONF_RESISTOR_TWO, DEFAULT_BL0940_R2) | ||||
|         r_shunt = config.get(CONF_RESISTOR_SHUNT, DEFAULT_BL0940_RL) | ||||
|  | ||||
|         config.setdefault( | ||||
|             CONF_VOLTAGE_REFERENCE, calculate_voltage_reference(vref, r_one, r_two) | ||||
|         ) | ||||
|         config.setdefault( | ||||
|             CONF_CURRENT_REFERENCE, calculate_current_reference(vref, r_shunt) | ||||
|         ) | ||||
|         config.setdefault( | ||||
|             CONF_POWER_REFERENCE, | ||||
|             calculate_power_reference( | ||||
|                 config.get(CONF_VOLTAGE_REFERENCE), config.get(CONF_CURRENT_REFERENCE) | ||||
|             ), | ||||
|         ) | ||||
|         config.setdefault( | ||||
|             CONF_ENERGY_REFERENCE, | ||||
|             calculate_energy_reference(config.get(CONF_POWER_REFERENCE)), | ||||
|         ) | ||||
|  | ||||
|     return config | ||||
|  | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     cv.Schema( | ||||
|         { | ||||
| @@ -69,10 +191,24 @@ CONFIG_SCHEMA = ( | ||||
|                 device_class=DEVICE_CLASS_TEMPERATURE, | ||||
|                 state_class=STATE_CLASS_MEASUREMENT, | ||||
|             ), | ||||
|             cv.Optional(CONF_LEGACY_MODE, default=True): cv.boolean, | ||||
|             cv.Optional(CONF_READ_COMMAND): cv.hex_uint8_t, | ||||
|             cv.Optional(CONF_WRITE_COMMAND): cv.hex_uint8_t, | ||||
|             cv.Optional(CONF_REFERENCE_VOLTAGE): cv.float_, | ||||
|             cv.Optional(CONF_RESISTOR_SHUNT): cv.float_, | ||||
|             cv.Optional(CONF_RESISTOR_ONE): cv.float_, | ||||
|             cv.Optional(CONF_RESISTOR_TWO): cv.float_, | ||||
|             cv.Optional(CONF_CURRENT_REFERENCE): cv.float_, | ||||
|             cv.Optional(CONF_ENERGY_REFERENCE): cv.float_, | ||||
|             cv.Optional(CONF_POWER_REFERENCE): cv.float_, | ||||
|             cv.Optional(CONF_VOLTAGE_REFERENCE): cv.float_, | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.polling_component_schema("60s")) | ||||
|     .extend(uart.UART_DEVICE_SCHEMA) | ||||
|     .add_extra(validate_legacy_mode) | ||||
|     .add_extra(set_command_defaults) | ||||
|     .add_extra(set_reference_values) | ||||
| ) | ||||
|  | ||||
|  | ||||
| @@ -99,3 +235,16 @@ async def to_code(config): | ||||
|     if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE): | ||||
|         sens = await sensor.new_sensor(external_temperature_config) | ||||
|         cg.add(var.set_external_temperature_sensor(sens)) | ||||
|  | ||||
|     # enable legacy mode | ||||
|     cg.add(var.set_legacy_mode(config.get(CONF_LEGACY_MODE))) | ||||
|  | ||||
|     # Set bl0940 commands after validator has determined which defaults to use if not set | ||||
|     cg.add(var.set_read_command(config.get(CONF_READ_COMMAND))) | ||||
|     cg.add(var.set_write_command(config.get(CONF_WRITE_COMMAND))) | ||||
|  | ||||
|     # Set reference values after validator has set the values either from defaults or calculated | ||||
|     cg.add(var.set_current_reference(config.get(CONF_CURRENT_REFERENCE))) | ||||
|     cg.add(var.set_voltage_reference(config.get(CONF_VOLTAGE_REFERENCE))) | ||||
|     cg.add(var.set_power_reference(config.get(CONF_POWER_REFERENCE))) | ||||
|     cg.add(var.set_energy_reference(config.get(CONF_ENERGY_REFERENCE))) | ||||
|   | ||||
| @@ -4,8 +4,14 @@ uart: | ||||
|     rx_pin: ${rx_pin} | ||||
|     baud_rate: 9600 | ||||
|  | ||||
| button: | ||||
|   - platform: bl0940 | ||||
|     bl0940_id: test_id | ||||
|     name: Cal Reset | ||||
|  | ||||
| sensor: | ||||
|   - platform: bl0940 | ||||
|     id: test_id | ||||
|     voltage: | ||||
|       name: BL0940 Voltage | ||||
|     current: | ||||
| @@ -18,3 +24,18 @@ sensor: | ||||
|       name: BL0940 Internal temperature | ||||
|     external_temperature: | ||||
|       name: BL0940 External temperature | ||||
|  | ||||
| number: | ||||
|   - platform: bl0940 | ||||
|     id: bl0940_number_id | ||||
|     bl0940_id: test_id | ||||
|     current_calibration: | ||||
|       name: Cal Current | ||||
|       min_value: -5 | ||||
|       max_value: 5 | ||||
|     voltage_calibration: | ||||
|       name: Cal Voltage | ||||
|       step: 0.01 | ||||
|     power_calibration: | ||||
|       name: Cal Power | ||||
|       disabled_by_default: true | ||||
|   | ||||
		Reference in New Issue
	
	Block a user