ESPhome Scale - Need Help

I’m trying to build a scale with ESPhome.

The Goal:
I have two snakes where a scale will routinely be used to weigh the snakes and their feed. My idea was to create the scale with a button for each snake to log feed size and separate button to log the snake’s weight. The fifth button for running a manual tare on the scale.

The Problem:
The values off the HX711 are varying wildly so I cannot get a baseline reading. I’m not sure if the problem is the amp or something else. For example, the baseline reading 30 minutes ago was ‘1315912’ and it’s now at ‘1259202’'.

The other kick in the pants is that the reading is changing every time I reinstall the firmware. For example: I’m sitting at ‘1184776’ for 0 weight and ‘1935727’ for 4572 g (dumbbell). I set those values and reinstalled the firmware. Now 0 g weight is showing ‘1286100’

I’ve twisted the wire pairings between the load cell and the amp to help reduce EMI based on recommendations from a few forum threads. I am powering the ESP from USB (wall plug) and using the VIN pin off the ESP to power to HX711.

Setup:
10 kg load cell and HX711
ESP32 board
Freenove 16x2 i2c display
Five momentary pushbuttons
3D printed enclosure

YAML:
Pulled from here and modified to add the display and buttons.

## Node configuration ##
substitutions:
  name: esphome-web-7dd534
  friendly_name: ESPHome Web 7dd534
  baseline_0_reading: "1315912"
  baseline_4572_reading: "2078353"

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  name_add_mac_suffix: False

esp32:
  board: esp32dev
  framework:
    type: esp-idf

globals:
  - id: initial_zero
    type: float
    restore_value: yes
    # NOTE: make sure to align this value to the one used in "calibrate_linear" below!
    initial_value: ${baseline_0_reading}
    
  - id: auto_tare_enabled
    type: bool
    restore_value: yes
    initial_value: 'true'

  - id: auto_tare_difference
    type: float
    restore_value: yes
    initial_value: '0'

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

api:

# Enable logging
logger:
  level: INFO

ota:
  platform: esphome

status_led:
  pin:
    number: GPIO2
    inverted: True

i2c:
  sda: GPIO21
  scl: GPIO22

display:
  - platform: lcd_pcf8574
    dimensions: 16x2
    address: 0x27
    lambda: |-
      it.printf("Weight: %6.0f g", id(smart_scale_hx711_value).state);

button:
  ## Button used to initiate a manual tare
  - platform: template
    id: smart_scale_manual_tare_action_switch
    name: "Smart Scale Manual Tare Action"
    icon: mdi:scale-balance
    on_press:
      - lambda: |-
          id(auto_tare_difference) = id(initial_zero) - id(smart_scale_hx711_value_raw).state;
      - sensor.template.publish:
          id: smart_scale_hx711_value_raw_diagnostic
          state: !lambda 'return id(smart_scale_hx711_value_raw).state;'

switch:
  ## Switch to enable/disable the auto tare feature
  - platform: template
    id: smart_scale_continuous_tare_enabled
    name: "Smart Scale Continuous Tare Enabled"
    entity_category: "config"
    lambda: |-
      return id(auto_tare_enabled);
    turn_on_action:
      - lambda: |-
          id(auto_tare_enabled) = true;
    turn_off_action:
      - lambda: |-
          id(auto_tare_enabled) = false;

## Sensor Configuration ##
sensor:
  # template sensors from global variables
  - platform: template
    id: smart_scale_initial_zero
    name: "Smart Scale Initial Zero"
    lambda: |-
      return id(initial_zero);
    update_interval: 1s
    
  - platform: template
    id: smart_scale_auto_tare_difference
    name: "Smart Scale Auto Tare Zero Calibration Offset"
    lambda: |-
      return id(auto_tare_difference);
    update_interval: 1s

  - platform: template
    id: smart_scale_auto_tare_deviance
    entity_category: "diagnostic"
    name: "Smart Scale Calibration Deviance"
    lambda: |-
      return (int((id(smart_scale_hx711_value_raw).state - (id(initial_zero) - id(auto_tare_difference)) ) / 100)) * 100;
    update_interval: 1s
    
  # sensors imported from home assistant
  - platform: homeassistant
    id: homeassistant_initial_zero
    entity_id: input_number.smart_scale_initial_zero
    on_value:
      then:
        - lambda: |-
            id(initial_zero) = x;
  
  # RAW Scale input
  - platform: hx711
    id: smart_scale_hx711_value_raw
    internal: True
    dout_pin: GPIO16
    clk_pin: GPIO4
    gain: 128
    update_interval: 0.2s
    filters:
      - quantile:
          window_size: 10
          send_every: 1
          send_first_at: 1
          quantile: .9
    on_value:
      then:
        - sensor.template.publish:
            id: smart_scale_hx711_value
            state: !lambda 'return id(smart_scale_hx711_value_raw).state;'
        - if:
            condition:
              - lambda: |-
                  auto n = id(smart_scale_hx711_value_raw_diagnostic).state;
                  auto n_str = to_string(n);
                  return str_equals_case_insensitive(n_str, "NaN");
            then:
              - sensor.template.publish:
                  id: smart_scale_hx711_value_raw_diagnostic
                  state: !lambda 'return id(smart_scale_hx711_value_raw).state;'
        - if:
            condition:
              and:
                - lambda: 'return id(auto_tare_enabled);'
                # current smart scale value is below approx. 10KG (raw value -275743) aka nobody is standing on the scale
                # - lambda: 'return id(smart_scale_hx711_value).state < 10.0;'
                # only update if the current deviance is not 0
                - lambda: 'return id(smart_scale_auto_tare_deviance).state != 0.0;'
            then:
              - if:
                  condition:
                    # current raw scale value is below expected zero value
                    - lambda: 'return id(smart_scale_hx711_value_raw).state < (id(initial_zero) - id(auto_tare_difference));'
                  then:
                    # INcrease Auto-Tare offset to slowly align real zero value with expected zero value
                    - lambda: |-
                        id(auto_tare_difference) += 10;
                  else:
                    # DEcrease Auto-Tare offset to slowly align real zero value with expected zero value
                    - lambda: |-
                        id(auto_tare_difference) -= 10;

  - platform: template
    id: smart_scale_hx711_value_raw_diagnostic
    name: "Smart Scale HX711 Raw Value"
    entity_category: "diagnostic"

  # Mapped value to KG
  - platform: template
    id: smart_scale_hx711_value
    name: "Smart Scale HX711 Value"
    internal: False
    filters:
      # apply auto_tare difference
      - lambda: 'return x + id(auto_tare_difference);'
      # apply rough calibration
      - calibrate_linear:
          # retrieve these values by evaluating the raw values with loads of known mass.
          # note that a bigger difference between measurements usually results in higher resolution,
          # so measure 0 Kg and the highest known mass you have (like f.ex. your own weight, measured by a normal scale with good accuracy)
          - ${baseline_0_reading} -> 0
          - ${baseline_4572_reading} -> 4572
      # map values below 0.1 to 0 (to decrease value changes due to random fluctuation)
      - lambda: |-
          if (x <= 0.1) {
            return 0.0;
          } else {
            return x;
          }
          
    unit_of_measurement: g
    # the sensor will automatically update through the 'sensor.template.publish' call
    update_interval: 5s

binary_sensor:
  - platform: gpio
    pin: 
      number: GPIO27
      mode: INPUT_PULLUP
      inverted: True
    name: "Button 1"
    on_press:
      button.press: smart_scale_manual_tare_action_switch
  - platform: gpio
    pin: 
      number: GPIO25
      mode: INPUT_PULLUP
      inverted: True
    name: "Button 2"
  - platform: gpio
    pin: 
      number: GPIO26
      mode: INPUT_PULLUP
      inverted: True
    name: "Button 3"
  - platform: gpio
    pin: 
      number: GPIO32
      mode: INPUT_PULLUP
      inverted: True
    name: "Button 4"
  - platform: gpio
    pin: 
      number: GPIO33
      mode: INPUT_PULLUP
      inverted: True
    name: "Button 5"

Decent swings in readings is pretty common; I’ve found using a healthy average over a few minutes helps if it’s a long-term install situation (which it doesn’t sound like yours will be).

But more than that, it looks like you’re using a single load cell? That’s fine for say a kitchen scale that’s getting used for a few minutes and gets tared everytime (which is essentially what you’re describing I think), but the fact that it can’t accurately measure any load that isn’t perfectly centered through it will always cause issues.

I’ve used these style (same as is used on most bathroom scales) for multiple long-term projects with good success:

First for a Keezer to weigh beer kegs. Only problem I ran into in that case was long-term water exposure from condensation pooling caused them to corrode. The same setup that I’m currently using to weigh the CO2 tank outside the Keezer is still working well (been years at this point).

I’m currently using the same load cells for a version 2 prototype for weighing my beehives (currently using some leftover ones of a different type, long story). Haven’t put it into permanent use yet, but it’s been in my garage for a few weeks with a constant load and only drifts 1/10th of a pound (which is more than adequate for my needs).

One big advantage in my cases is I’m weighing fairly heavy things. Not sure how heavy your snake is; while obviously the food will be very light, I imagine you can still get past that by simply taring it before adding it.

But after re-reading your post, it sounds like you already plan to tare it each time anyway. The fact that it drifts isn’t really an issue, as long as the difference between values with a known weight is consistent. That’s how kitchen and bathroom scales work; they re-tare each time.

1 Like

Yeah, tare each use is the plan. I think I just need to add a function (or trouble shoot) to have it auto-tare at bootup and offset my known weight values for the linear calibration. It looks like that’s what is getting me now.

I looked at those type load cells yesterday but thought they’re way overshooting what I need. The max I could ever see being put on here is about 2 kg

Sounds like you have a good plan. A load cell like that is exactly what my old kitchen scale used and worked great for that type of use (taring every time you use it); salvaged it when the main board died.

Another option is to rewire the scale, in most cases it’s easier…
Anyway, keep wires short and consider that temperature has effect also.

Welp…the problem appears to have been either the load cell or the 3D printed setup. After fiddling a little more, my last ditch effort was to steal this idea and try using the load cell in an Amazon scale I already had. Hard to see in the picture, but it’s spot on at 2250 g after just a little tweaking of the calibration values and hasn’t moved. Think I’m going to tweak my 3D print model to incorporate the load cell and top plate from the Amazon scale and reuse those. The load cell in it is mounted to a steel plate so I won’t have to worry about flex in the 3D printed parts throwing the reading off (which might, in part, been the problem with the original setup).

Honestly how do you expect people to help if you don’t post a few pictures of your cool snakes! :wink:



2 Likes

I think this part is the key. When you’re using 4 load cells, this is at least compensated for somewhat by design, but when you have only the one it really needs to be solid so flex doesn’t throw things off.

Cool snakes! Absolutely no interest in having one as a pet, but I still think they’re awesome animals.

Well, the model tweak turned into building it from scratch, but 90% there. Considering the number of articles I found about folks having the same issues with the cheaper load cells, I’m going to stick the blame there.