diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d126da1..22a1cc5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,4 +25,4 @@ jobs: - name: Build ESPHome firmware to verify configuration uses: esphome/build-action@v4.0.1 with: - yaml_file: ${{ matrix.file }} + yaml-file: ${{ matrix.file }} diff --git a/athom-energy-monitor-x2.yaml b/athom-energy-monitor-x2.yaml index 67b8967..60517c8 100644 --- a/athom-energy-monitor-x2.yaml +++ b/athom-energy-monitor-x2.yaml @@ -1,11 +1,11 @@ substitutions: - name: "athom-energy-monitor-x2" - friendly_name: "Athom Energy Monitor" + name: "athom-em-2" + friendly_name: "Athom Energy Meter" # Allows ESP device to be automatically lined to an 'Area' in Home Assistant. Typically used for areas such as 'Lounge Room', 'Kitchen' etc room: "" - device_description: "athom bl0906 energy monitor (2 channels)" - project_name: "Athom Technology.Athom Energy Monitor(2 Channels)" - project_version: "2.0.2" + device_description: "athom bl0906 energy meter (2 channels)" + project_name: "Athom Technology.Athom Energy Meter(2 Channels)" + project_version: "2.0.3" update_interval: 5s # Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs) dns_domain: "" @@ -42,7 +42,7 @@ esphome: friendly_name: "${friendly_name}" area: "${room}" name_add_mac_suffix: true - min_version: 2024.6.0 + min_version: 2024.7.0 project: name: "${project_name}" version: "${project_version}" @@ -180,6 +180,7 @@ sensor: device_class: "" - platform: bl0906 + id: id_bl0906 update_interval: "${update_interval}" frequency: name: 'Frequency' @@ -301,6 +302,22 @@ button: internal: false entity_category: config + - platform: template + name: Reset Energy + entity_category: config + on_press: + then: + - globals.set: + id: id_Energy_1_persist + value: '0.0' + - globals.set: + id: id_Energy_2_persist + value: '0.0' + - globals.set: + id: id_Energy_sum_persist + value: '0.0' + - bl0906.reset_energy: id_bl0906 + text_sensor: - platform: wifi_info ip_address: diff --git a/athom-energy-monitor-x6.yaml b/athom-energy-monitor-x6.yaml index 9a1a2e7..a57957c 100644 --- a/athom-energy-monitor-x6.yaml +++ b/athom-energy-monitor-x6.yaml @@ -1,11 +1,11 @@ substitutions: - name: "athom-energy-monitor-x6" - friendly_name: "Athom Energy Monitor" + name: "athom-em-6" + friendly_name: "Athom Energy Meter" # Allows ESP device to be automatically lined to an 'Area' in Home Assistant. Typically used for areas such as 'Lounge Room', 'Kitchen' etc room: "" - device_description: "athom bl0906 energy monitor (6 channels)" - project_name: "Athom Technology.Athom Energy Monitor(6 Channels)" - project_version: "2.0.2" + device_description: "athom bl0906 energy meter (6 channels)" + project_name: "Athom Technology.Athom Energy Meter(6 Channels)" + project_version: "2.0.3" update_interval: 5s # Define a domain for this device to use. i.e. iot.home.lan (so device will appear as athom-smart-plug-v2.iot.home.lan in DNS/DHCP logs) dns_domain: "" @@ -54,7 +54,7 @@ esphome: friendly_name: "${friendly_name}" area: "${room}" name_add_mac_suffix: true - min_version: 2024.6.0 + min_version: 2024.7.0 project: name: "${project_name}" version: "${project_version}" @@ -236,6 +236,7 @@ sensor: device_class: "" - platform: bl0906 + id: id_bl0906 update_interval: "${update_interval}" frequency: name: 'Frequency' @@ -489,6 +490,34 @@ button: internal: false entity_category: config + - platform: template + name: Reset Energy + entity_category: config + on_press: + then: + - globals.set: + id: id_Energy_1_persist + value: '0.0' + - globals.set: + id: id_Energy_2_persist + value: '0.0' + - globals.set: + id: id_Energy_3_persist + value: '0.0' + - globals.set: + id: id_Energy_4_persist + value: '0.0' + - globals.set: + id: id_Energy_5_persist + value: '0.0' + - globals.set: + id: id_Energy_6_persist + value: '0.0' + - globals.set: + id: id_Energy_sum_persist + value: '0.0' + - bl0906.reset_energy: id_bl0906 + text_sensor: - platform: wifi_info ip_address: diff --git a/components/bl0906/bl0906.cpp b/components/bl0906/bl0906.cpp index c09db3e..167c974 100644 --- a/components/bl0906/bl0906.cpp +++ b/components/bl0906/bl0906.cpp @@ -76,6 +76,19 @@ static const uint8_t BL0906_WATTGN_4 = 0xBA; static const uint8_t BL0906_WATTGN_5 = 0xBD; static const uint8_t BL0906_WATTGN_6 = 0xBE; // Channel_6 +// User write protection setting register, +// You must first write 0x5555 to the write protection setting register before writing to other registers. +static const uint8_t BL0906_USR_WRPROT = 0x9E; + +// Reset Register +static const uint8_t BL0906_SOFT_RESET = 0x9F; + +const uint8_t BL0906_INIT[2][6] = { + // Reset to default + {BL0906_WRITE_COMMAND, BL0906_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x52}, + // Enable User Operation Write + {BL0906_WRITE_COMMAND, BL0906_USR_WRPROT, 0x55, 0x55, 0x00, 0xB7}}; + void BL0906::loop() { if (this->current_channel_ == UINT8_MAX) { return; @@ -126,6 +139,7 @@ void BL0906::loop() { return; } this->current_channel_++; + handleActionCallback(); } void BL0906::setup() { @@ -163,6 +177,44 @@ uint8_t bl0906_checksum(const uint8_t address, const DataPacket *data) { return (address + data->l + data->m + data->h) ^ 0xFF; } +int BL0906::addActionCallBack(ActionCallbackFuncPtr ptrFunc) { + m_vecActionCallback.push_back(ptrFunc); + return m_vecActionCallback.size(); +} + +void BL0906::handleActionCallback() { + if (m_vecActionCallback.size() == 0) { + return; + } + ActionCallbackFuncPtr ptrFunc = nullptr; + for (int i = 0; i < m_vecActionCallback.size(); i++) { + ptrFunc = m_vecActionCallback[i]; + if (ptrFunc) { + ESP_LOGI(TAG, "HandleActionCallback[%d]...", i); + (this->*ptrFunc)(); + } + } + + while (this->available()) { + this->read(); + } + + m_vecActionCallback.clear(); + if (m_process_state != PROCESS_DONE) { + m_process_state = PROCESS_DONE; + } +} + +// Reset energy +void BL0906::reset_energy() { + this->write_array(BL0906_INIT[0], 6); + delay(1); + this->flush(); + + ESP_LOGW(TAG, "RMSOS:%02X%02X%02X%02X%02X%02X", BL0906_INIT[0][0], BL0906_INIT[0][1], BL0906_INIT[0][2], + BL0906_INIT[0][3], BL0906_INIT[0][4], BL0906_INIT[0][5]); +} + // Read data void BL0906::read_data(const uint8_t address, const float reference, sensor::Sensor *sensor) { if (sensor == nullptr) { diff --git a/components/bl0906/bl0906.h b/components/bl0906/bl0906.h index d10ef0e..1172be4 100644 --- a/components/bl0906/bl0906.h +++ b/components/bl0906/bl0906.h @@ -3,6 +3,8 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/uart/uart.h" #include "esphome/core/component.h" +#include "esphome/core/datatypes.h" +#include "esphome/core/automation.h" // https://www.belling.com.cn/media/file_object/bel_product/BL0906/datasheet/BL0906_V1.02_cn.pdf // https://www.belling.com.cn/media/file_object/bel_product/BL0906/guide/BL0906%20APP%20Note_V1.02.pdf @@ -60,6 +62,11 @@ struct sbe24_t { // NOLINT(readability-identifier-naming,altera-struct-pack-ali int8_t h; } __attribute__((packed)); +typedef enum process_state_ { PROCESS_DONE = 0 } process_state; +template class ResetEnergyAction; +class BL0906; +typedef void (BL0906::*ActionCallbackFuncPtr)(void); + class BL0906 : public PollingComponent, public uart::UARTDevice { public: void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } @@ -92,6 +99,8 @@ class BL0906 : public PollingComponent, public uart::UARTDevice { void dump_config() override; protected: + process_state m_process_state{PROCESS_DONE}; + template friend class ResetEnergyAction; sensor::Sensor *voltage_sensor_{nullptr}; sensor::Sensor *current_sensor_1_{nullptr}; sensor::Sensor *current_sensor_2_{nullptr}; @@ -136,6 +145,8 @@ class BL0906 : public PollingComponent, public uart::UARTDevice { static int32_t to_int32_t(sbe24_t input); + void reset_energy(); + void read_data(const uint8_t address, const float reference, sensor::Sensor *sensor_); void Bias_correction(const uint8_t address, const float measurements, const float Correction); @@ -144,6 +155,22 @@ class BL0906 : public PollingComponent, public uart::UARTDevice { const float coefficient); uint8_t current_channel_ = 0; + int addActionCallBack(ActionCallbackFuncPtr ptrFunc); + void handleActionCallback(); + bool isNeedHandleActionCallback() { return (m_vecActionCallback.size() > 0); } + + private: + std::vector m_vecActionCallback{}; +}; + +template class ResetEnergyAction : public Action { + public: + ResetEnergyAction(BL0906 *bl0906) : bl0906_(bl0906) {} + + void play(Ts... x) override { this->bl0906_->addActionCallBack(&BL0906::reset_energy);} + + protected: + BL0906 *bl0906_; }; } // namespace bl0906 } // namespace esphome diff --git a/components/bl0906/sensor.py b/components/bl0906/sensor.py index 629fa6d..3365296 100644 --- a/components/bl0906/sensor.py +++ b/components/bl0906/sensor.py @@ -1,5 +1,7 @@ import esphome.codegen as cg import esphome.config_validation as cv +from esphome import automation +from esphome.automation import maybe_simple_id from esphome.components import sensor, uart from esphome.const import ( CONF_CHANNEL, @@ -28,11 +30,12 @@ ) DEPENDENCIES = ["uart"] - +AUTO_LOAD = ["bl0906"] CONF_TOTAL_ENERGY = "total_energy" bl0906_ns = cg.esphome_ns.namespace("bl0906") BL0906 = bl0906_ns.class_("BL0906", cg.PollingComponent, uart.UARTDevice) +ResetEnergyAction = bl0906_ns.class_("ResetEnergyAction", automation.Action) CONFIG_SCHEMA = ( cv.Schema( @@ -105,6 +108,18 @@ .extend(cv.polling_component_schema("60s")) ) +@automation.register_action( + "bl0906.reset_energy", + ResetEnergyAction, + maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(BL0906), + } + ), +) +async def reset_energy_to_code(config, action_id, template_arg, args): + paren = await cg.get_variable(config[CONF_ID]) + return cg.new_Pvariable(action_id, template_arg, paren) async def to_code(config): var = cg.new_Pvariable(config[CONF_ID])