diff --git a/components/m5unit_scales/m5unit_scales.cpp b/components/m5unit_scales/m5unit_scales.cpp index d82dc4a5..6d889a1c 100644 --- a/components/m5unit_scales/m5unit_scales.cpp +++ b/components/m5unit_scales/m5unit_scales.cpp @@ -1,6 +1,7 @@ #include "m5unit_scales.h" #include "esphome/core/log.h" #include "esphome/core/hal.h" +#include namespace esphome { namespace m5unit_scales { @@ -47,9 +48,14 @@ void M5UnitScalesComponent::dump_config() { if (this->is_failed()) { ESP_LOGCONFIG(TAG, " Connection failed!"); } +#ifdef USE_SENSOR LOG_SENSOR(" ", "Weight", this->weight_sensor_); + LOG_SENSOR(" ", "Absolute Weight", this->absolute_weight_sensor_); LOG_SENSOR(" ", "Raw ADC", this->raw_adc_sensor_); +#endif +#ifdef USE_BINARY_SENSOR LOG_BINARY_SENSOR(" ", "Button", this->button_sensor_); +#endif } float M5UnitScalesComponent::get_setup_priority() const { @@ -65,6 +71,7 @@ void M5UnitScalesComponent::update() { if (this->model_ == M5UNIT_SCALES_MODEL_MINI && !this->initial_sync_done_) { this->initial_sync_done_ = true; +#ifdef USE_SWITCH // LP Filter State uint8_t lp_val = 0; if (this->read_byte(REG_MINI_FILTER_BASE, &lp_val)) { @@ -72,7 +79,9 @@ void M5UnitScalesComponent::update() { this->lp_filter_switch_->publish_state(lp_val != 0); } } +#endif +#ifdef USE_NUMBER // Avg Filter Value uint8_t avg_val = 0; if (this->read_byte(REG_MINI_FILTER_BASE + 1, &avg_val)) { @@ -98,27 +107,39 @@ void M5UnitScalesComponent::update() { this->gap_number_->publish_state(gap); } } +#endif } else if (this->model_ == M5UNIT_SCALES_MODEL_STANDARD && !this->initial_sync_done_) { this->initial_sync_done_ = true; +#ifdef USE_SWITCH if (this->lp_filter_switch_ != nullptr) { ESP_LOGW(TAG, "Low Pass Filter switch is only supported on MINI model."); } +#endif +#ifdef USE_NUMBER if (this->avg_filter_number_ != nullptr || this->ema_filter_number_ != nullptr) { ESP_LOGW(TAG, "Average and EMA filter numbers are only supported on MINI model."); } if (this->gap_number_ != nullptr) { ESP_LOGW(TAG, "Gap calibration number is only supported on MINI model. Use manual tare/zero calibration for STANDARD."); } +#endif } +#ifdef USE_SENSOR // Read Weight - if (this->weight_sensor_ != nullptr) { + if (this->weight_sensor_ != nullptr || this->absolute_weight_sensor_ != nullptr) { uint8_t data[4]; if (this->model_ == M5UNIT_SCALES_MODEL_MINI) { if (this->read_bytes(REG_MINI_CAL_DATA, data, 4)) { float weight = 0.0f; memcpy(&weight, data, 4); - this->weight_sensor_->publish_state(weight); + this->last_read_weight_ = weight; + if (this->weight_sensor_ != nullptr) { + this->weight_sensor_->publish_state(weight); + } + if (this->absolute_weight_sensor_ != nullptr) { + this->absolute_weight_sensor_->publish_state(weight + this->tare_offset_); + } } else { ESP_LOGW(TAG, "Failed to read weight data."); } @@ -130,7 +151,13 @@ void M5UnitScalesComponent::update() { ((int32_t)data[2] << 8) | data[3]; float weight = (float)weight_raw / 100.0f; - this->weight_sensor_->publish_state(weight); + this->last_read_weight_ = weight; + if (this->weight_sensor_ != nullptr) { + this->weight_sensor_->publish_state(weight); + } + if (this->absolute_weight_sensor_ != nullptr) { + this->absolute_weight_sensor_->publish_state(weight + this->tare_offset_); + } } else { ESP_LOGW(TAG, "Failed to read weight data."); } @@ -160,9 +187,11 @@ void M5UnitScalesComponent::update() { } } } +#endif } void M5UnitScalesComponent::loop() { +#ifdef USE_BINARY_SENSOR if (this->is_failed() || this->button_sensor_ == nullptr) { return; } @@ -176,13 +205,86 @@ void M5UnitScalesComponent::loop() { if (this->read_byte(reg, &btn_val)) { // 0 = pressed, 1 = no press bool pressed = (btn_val == 0); + + // Detect rising edge of physical button press + if (pressed && !this->button_sensor_->state) { + // Physical button transition to pressed. + // The hardware will trigger a tare internally. Update tare_offset_ in software. + float current_weight = 0.0f; + bool read_success = false; + uint8_t data[4]; + if (this->model_ == M5UNIT_SCALES_MODEL_MINI) { + if (this->read_bytes(REG_MINI_CAL_DATA, data, 4)) { + memcpy(¤t_weight, data, 4); + read_success = true; + } + } else { + if (this->read_bytes(REG_STD_CAL_DATA, data, 4)) { + int32_t weight_raw = ((int32_t)data[0] << 24) | + ((int32_t)data[1] << 16) | + ((int32_t)data[2] << 8) | + data[3]; + current_weight = (float)weight_raw / 100.0f; + read_success = true; + } + } + + float weight_to_add = 0.0f; + if (read_success) { + // If the hardware hasn't zeroed the value yet, we use the on-demand read value. + // However, if the hardware already tared and returns ~0, but our last_read_weight_ was significant, + // we fall back to last_read_weight_. + if (std::abs(current_weight) < 0.01f && std::abs(this->last_read_weight_) > 0.1f) { + weight_to_add = this->last_read_weight_; + } else { + weight_to_add = current_weight; + } + } else { + weight_to_add = this->last_read_weight_; + } + + this->tare_offset_ += weight_to_add; + ESP_LOGD(TAG, "Physical button pressed, updated tare offset to %.2f (added %.2f)", this->tare_offset_, weight_to_add); + } + this->button_sensor_->publish_state(pressed); } } +#endif } void M5UnitScalesComponent::tare() { ESP_LOGI(TAG, "Executing tare / zero calibration..."); + + // Read current weight before tare command to update tare_offset_ in software + float current_weight = 0.0f; + bool read_success = false; + uint8_t data[4]; + if (this->model_ == M5UNIT_SCALES_MODEL_MINI) { + if (this->read_bytes(REG_MINI_CAL_DATA, data, 4)) { + memcpy(¤t_weight, data, 4); + read_success = true; + } + } else { + if (this->read_bytes(REG_STD_CAL_DATA, data, 4)) { + int32_t weight_raw = ((int32_t)data[0] << 24) | + ((int32_t)data[1] << 16) | + ((int32_t)data[2] << 8) | + data[3]; + current_weight = (float)weight_raw / 100.0f; + read_success = true; + } + } + + if (read_success) { + this->tare_offset_ += current_weight; + ESP_LOGD(TAG, "Software tare triggered, updated tare offset to %.2f (added %.2f)", this->tare_offset_, current_weight); + } else { + // If on-demand read failed, fall back to last_read_weight_ + this->tare_offset_ += this->last_read_weight_; + ESP_LOGW(TAG, "Failed to read current weight synchronously, fell back to last read weight: %.2f", this->last_read_weight_); + } + uint8_t cmd = 1; uint8_t reg = (this->model_ == M5UNIT_SCALES_MODEL_MINI) ? REG_MINI_SET_OFFSET : REG_STD_SET_OFFSET; if (!this->write_byte(reg, cmd)) { diff --git a/components/m5unit_scales/m5unit_scales.h b/components/m5unit_scales/m5unit_scales.h index a3874fb0..c60f0511 100644 --- a/components/m5unit_scales/m5unit_scales.h +++ b/components/m5unit_scales/m5unit_scales.h @@ -1,14 +1,28 @@ #pragma once #include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/components/i2c/i2c.h" + +#ifdef USE_SENSOR #include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_BINARY_SENSOR #include "esphome/components/binary_sensor/binary_sensor.h" +#endif +#ifdef USE_BUTTON #include "esphome/components/button/button.h" +#endif +#ifdef USE_LIGHT #include "esphome/components/light/light_output.h" #include "esphome/components/light/light_state.h" +#endif +#ifdef USE_SWITCH #include "esphome/components/switch/switch.h" +#endif +#ifdef USE_NUMBER #include "esphome/components/number/number.h" -#include "esphome/components/i2c/i2c.h" +#endif namespace esphome { namespace m5unit_scales { @@ -30,17 +44,30 @@ class M5UnitScalesComponent : public PollingComponent, public i2c::I2CDevice { void set_model(M5UnitScalesModel model) { model_ = model; } // Setters for platform items +#ifdef USE_SENSOR void set_weight_sensor(sensor::Sensor *weight_sensor) { weight_sensor_ = weight_sensor; } void set_raw_adc_sensor(sensor::Sensor *raw_adc_sensor) { raw_adc_sensor_ = raw_adc_sensor; } + void set_absolute_weight_sensor(sensor::Sensor *absolute_weight_sensor) { absolute_weight_sensor_ = absolute_weight_sensor; } +#endif +#ifdef USE_BINARY_SENSOR void set_button_sensor(binary_sensor::BinarySensor *button_sensor) { button_sensor_ = button_sensor; } +#endif // Custom platform wrappers +#ifdef USE_BUTTON void set_tare_button(button::Button *tare_button) { tare_button_ = tare_button; } +#endif +#ifdef USE_SWITCH void set_lp_filter_switch(switch_::Switch *lp_filter_switch) { lp_filter_switch_ = lp_filter_switch; } +#endif +#ifdef USE_NUMBER void set_avg_filter_number(number::Number *avg_filter_number) { avg_filter_number_ = avg_filter_number; } void set_ema_filter_number(number::Number *ema_filter_number) { ema_filter_number_ = ema_filter_number; } void set_gap_number(number::Number *gap_number) { gap_number_ = gap_number; } +#endif +#ifdef USE_LIGHT void set_led_light(light::LightOutput *led_light) { led_light_ = led_light; } +#endif // API methods called by wrapper entities void tare(); @@ -53,15 +80,32 @@ class M5UnitScalesComponent : public PollingComponent, public i2c::I2CDevice { protected: M5UnitScalesModel model_{M5UNIT_SCALES_MODEL_MINI}; +#ifdef USE_SENSOR sensor::Sensor *weight_sensor_{nullptr}; sensor::Sensor *raw_adc_sensor_{nullptr}; + sensor::Sensor *absolute_weight_sensor_{nullptr}; +#endif + + float tare_offset_{0.0f}; + float last_read_weight_{0.0f}; + +#ifdef USE_BINARY_SENSOR binary_sensor::BinarySensor *button_sensor_{nullptr}; +#endif +#ifdef USE_BUTTON button::Button *tare_button_{nullptr}; +#endif +#ifdef USE_SWITCH switch_::Switch *lp_filter_switch_{nullptr}; +#endif +#ifdef USE_NUMBER number::Number *avg_filter_number_{nullptr}; number::Number *ema_filter_number_{nullptr}; number::Number *gap_number_{nullptr}; +#endif +#ifdef USE_LIGHT light::LightOutput *led_light_{nullptr}; +#endif uint32_t last_button_poll_{0}; bool initial_sync_done_{false}; @@ -69,6 +113,7 @@ class M5UnitScalesComponent : public PollingComponent, public i2c::I2CDevice { // C++ Platform Wrappers +#ifdef USE_BUTTON class M5UnitScalesTareButton : public button::Button { public: void set_parent(M5UnitScalesComponent *parent) { parent_ = parent; } @@ -76,7 +121,9 @@ class M5UnitScalesTareButton : public button::Button { protected: M5UnitScalesComponent *parent_; }; +#endif +#ifdef USE_SWITCH class M5UnitScalesLPFilterSwitch : public switch_::Switch { public: void set_parent(M5UnitScalesComponent *parent) { parent_ = parent; } @@ -87,7 +134,9 @@ class M5UnitScalesLPFilterSwitch : public switch_::Switch { protected: M5UnitScalesComponent *parent_; }; +#endif +#ifdef USE_NUMBER class M5UnitScalesAvgFilterNumber : public number::Number { public: void set_parent(M5UnitScalesComponent *parent) { parent_ = parent; } @@ -120,7 +169,9 @@ class M5UnitScalesGapNumber : public number::Number { protected: M5UnitScalesComponent *parent_; }; +#endif +#ifdef USE_LIGHT class M5UnitScalesLED : public light::LightOutput { public: void set_parent(M5UnitScalesComponent *parent) { parent_ = parent; } @@ -140,6 +191,7 @@ class M5UnitScalesLED : public light::LightOutput { protected: M5UnitScalesComponent *parent_; }; +#endif } // namespace m5unit_scales } // namespace esphome diff --git a/components/m5unit_scales/sensor.py b/components/m5unit_scales/sensor.py index 68c51b7f..1796ddeb 100644 --- a/components/m5unit_scales/sensor.py +++ b/components/m5unit_scales/sensor.py @@ -8,6 +8,7 @@ CONF_WEIGHT = "weight" CONF_RAW_ADC = "raw_adc" +CONF_ABSOLUTE_WEIGHT = "absolute_weight" CONFIG_SCHEMA = cv.Schema({ cv.GenerateID(CONF_M5UNIT_SCALES_ID): cv.use_id(M5UnitScalesComponent), @@ -16,10 +17,19 @@ accuracy_decimals=2, device_class=DEVICE_CLASS_WEIGHT, state_class=STATE_CLASS_MEASUREMENT, + icon="mdi:scale-balance", ), cv.Optional(CONF_RAW_ADC): sensor.sensor_schema( accuracy_decimals=0, state_class=STATE_CLASS_MEASUREMENT, + icon="mdi:sine-wave", + ), + cv.Optional(CONF_ABSOLUTE_WEIGHT): sensor.sensor_schema( + unit_of_measurement="g", + accuracy_decimals=2, + device_class=DEVICE_CLASS_WEIGHT, + state_class=STATE_CLASS_MEASUREMENT, + icon="mdi:scale-balance", ), }) @@ -31,3 +41,6 @@ async def to_code(config): if CONF_RAW_ADC in config: sens = await sensor.new_sensor(config[CONF_RAW_ADC]) cg.add(parent.set_raw_adc_sensor(sens)) + if CONF_ABSOLUTE_WEIGHT in config: + sens = await sensor.new_sensor(config[CONF_ABSOLUTE_WEIGHT]) + cg.add(parent.set_absolute_weight_sensor(sens)) diff --git a/tests/components/m5unit_scales/common.yaml b/tests/components/m5unit_scales/common.yaml index f47012b3..8ca05d7e 100644 --- a/tests/components/m5unit_scales/common.yaml +++ b/tests/components/m5unit_scales/common.yaml @@ -24,6 +24,8 @@ sensor: - platform: m5unit_scales weight: name: "Scale Weight" + absolute_weight: + name: "Scale Absolute Weight" raw_adc: name: "Scale Raw ADC"