From b47d6db80f6a39817521385e5c2fe8cb7e9ce049 Mon Sep 17 00:00:00 2001 From: eigger Date: Mon, 6 Jul 2026 22:32:43 +0900 Subject: [PATCH 1/2] feat(m5unit_scales): add absolute weight support and default icons --- components/m5unit_scales/m5unit_scales.cpp | 92 +++++++++++++++++++++- components/m5unit_scales/m5unit_scales.h | 5 ++ components/m5unit_scales/sensor.py | 13 +++ tests/components/m5unit_scales/common.yaml | 2 + 4 files changed, 109 insertions(+), 3 deletions(-) diff --git a/components/m5unit_scales/m5unit_scales.cpp b/components/m5unit_scales/m5unit_scales.cpp index d82dc4a5..044aea9e 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 { @@ -48,6 +49,7 @@ void M5UnitScalesComponent::dump_config() { ESP_LOGCONFIG(TAG, " Connection failed!"); } LOG_SENSOR(" ", "Weight", this->weight_sensor_); + LOG_SENSOR(" ", "Absolute Weight", this->absolute_weight_sensor_); LOG_SENSOR(" ", "Raw ADC", this->raw_adc_sensor_); LOG_BINARY_SENSOR(" ", "Button", this->button_sensor_); } @@ -112,13 +114,19 @@ void M5UnitScalesComponent::update() { } // 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 +138,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."); } @@ -176,6 +190,48 @@ 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); } } @@ -183,6 +239,36 @@ void M5UnitScalesComponent::loop() { 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..9b546066 100644 --- a/components/m5unit_scales/m5unit_scales.h +++ b/components/m5unit_scales/m5unit_scales.h @@ -32,6 +32,7 @@ class M5UnitScalesComponent : public PollingComponent, public i2c::I2CDevice { // Setters for platform items 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; } void set_button_sensor(binary_sensor::BinarySensor *button_sensor) { button_sensor_ = button_sensor; } // Custom platform wrappers @@ -55,6 +56,10 @@ class M5UnitScalesComponent : public PollingComponent, public i2c::I2CDevice { sensor::Sensor *weight_sensor_{nullptr}; sensor::Sensor *raw_adc_sensor_{nullptr}; + sensor::Sensor *absolute_weight_sensor_{nullptr}; + + float tare_offset_{0.0f}; + float last_read_weight_{0.0f}; binary_sensor::BinarySensor *button_sensor_{nullptr}; button::Button *tare_button_{nullptr}; switch_::Switch *lp_filter_switch_{nullptr}; 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" From 9cc874f8774417975c27e535841e1a9f73953a59 Mon Sep 17 00:00:00 2001 From: eigger Date: Mon, 6 Jul 2026 22:39:20 +0900 Subject: [PATCH 2/2] fix(m5unit_scales): compile error when using partial configurations --- components/m5unit_scales/m5unit_scales.cpp | 16 +++++++ components/m5unit_scales/m5unit_scales.h | 49 +++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/components/m5unit_scales/m5unit_scales.cpp b/components/m5unit_scales/m5unit_scales.cpp index 044aea9e..6d889a1c 100644 --- a/components/m5unit_scales/m5unit_scales.cpp +++ b/components/m5unit_scales/m5unit_scales.cpp @@ -48,10 +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 { @@ -67,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)) { @@ -74,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)) { @@ -100,19 +107,25 @@ 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 || this->absolute_weight_sensor_ != nullptr) { uint8_t data[4]; @@ -174,9 +187,11 @@ void M5UnitScalesComponent::update() { } } } +#endif } void M5UnitScalesComponent::loop() { +#ifdef USE_BINARY_SENSOR if (this->is_failed() || this->button_sensor_ == nullptr) { return; } @@ -235,6 +250,7 @@ void M5UnitScalesComponent::loop() { this->button_sensor_->publish_state(pressed); } } +#endif } void M5UnitScalesComponent::tare() { diff --git a/components/m5unit_scales/m5unit_scales.h b/components/m5unit_scales/m5unit_scales.h index 9b546066..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,18 +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(); @@ -54,19 +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}; @@ -74,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; } @@ -81,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; } @@ -92,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; } @@ -125,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; } @@ -145,6 +191,7 @@ class M5UnitScalesLED : public light::LightOutput { protected: M5UnitScalesComponent *parent_; }; +#endif } // namespace m5unit_scales } // namespace esphome