Gas bottle weight monitor - using Hx711 and load sensors

Note - this is a work in progress, so is far from perfect but so far seems to be ok. I thought that it might help someone if I document my journey, as all of the posts I’ve found so far don’t seem to provide the whole picture

So, I wanted to monitor the weight of the BBQ gas bottle to get alerted in advance when it might be getting low. I saw a few mentions of potentially using load cells to do this (or for other reasons, such as monitoring salt usage for water softener, amount of HCL for a pool chlorinator, etc etc), so I put in my order from Aliexpress for a “New 50kg Load Cell 50kg Weighing Sensor Half-bridge Strain Gauge Human Body Scale Weight Sensor + mounting bracket for Arduino”, total cost US$4.96 delivered.

Once it arrived, I found an old chopping board, and hot glued the four sensors to each corner of the board, and wired them up to an esp32 as follows:


Thanks to fibossensor!

Note that I didn’t use a wemos D1 mini as per the diagram (which of course would work fine), but rather a Lolin D32 for, well, reasons.

I then put the esp32, hx711, cables et al into a small hobby box (as it was all going to live inside a barbecue trolley - protected from rain etc, but not from bugs) and hot glued it to the board and taped up the wiring under the board to provide a small amount of protection.

I put the thing on top of an old stone pizza tray (to provide some stability compared to sitting directly on the bottom of the BBQ trolley), fired up the ESP32 and took a measurement with nothing on it, then another measurement with a known weight on it (a bottle of water that weighed exactly 3kg), so I could calibrate the system.

Then put the gas bottle on top and voila - got the weight of the thing.

As I checked it over the course of the next day, I found that it got lighter at night time, and heavier during the day. So, that’s a thing. Did some more research and found that it was a known issue that the sensors drift based on the temperature. Dang. So not medical grade for under $5. Who’d a thought. :wink:

Removed the hobby box, opened it up, hooked up a ds18b20, and initially put that all back into the box - mistake. Well, second mistake. The first was in my haste accidentally reversing the power and ground wires to the ds18b20, and burning my thumb on the sensor as it turned into a room heater. Anyhow, the second mistake/issue was I found that the heat from the esp32 was causing the temp reading (of the replacement ds18b20 after being a muppet and toasting the first one) to be high and not really moving that much - although I had placed it as far away from the processor as possible, the hobby box was sealed so retained heat. [Note that I used a ds18b20 on a breakout board - you could also use a ds18b20 that is in a waterproof stainless steel probe.

That probably would be better for something that is living outside as it’s more resistant to moisture etc, and maybe in the future when what I have hacked together fails, I might move to that. Maybe. I could also use a separate sensor, such as the outside temp sensor from my weather station, but it wouldn’t be quite as accurate as ideally want something close to the lode sensors]

Anyhow, opened the hobby box yet again, and moved the sensor to under the chopping board - by rights could probably just use my weather station outdoor sensor, but knew it would be more accurate having something taking readings closer to the load sensors. Put everything back together, and it now looks pretty messy, but it’s out of sight so will probably stay that way until I can be bothered making it neater.

Added some code to track the temp reading from the ds18b20 along with the weight plus just for the giggles the outside temp sensor from my weather station, changed the code to also update more frequently and not do the median stuff, put my 3kg weight back on and waited impatiently for the temperature to change.

I roughly worked out what the change was based on the temperature, and put in some code to allow for that. Put back the median settings and monitored the adjusted temp. Roughly speaking it is now between 2 to 3% accuracy which for my purposes is probably good enough, although I need to monitor it for longer over wider temperature ranges and might tweak the adjustment if required.

Finally, here’s my current code. I’ve made some comments in it to make it a bit easier to understand. Also note that I use this for ble presense tracking, as well as have a battery connected to provide a small amount of protection from power outages as well as alert me to power outages on specific circuits - obviously remove those bits if you aren’t using that as well as change to suit the esp device you want to use!

# Compiled and tested on esphome 2025.3.3 and HA 2025.4.1

# Notes:
#  * need to jumper between USB pin and a GPIO - when USB power is connected, power goes to the USB pin and sets pin high
#  * Using a Lolin D32 as cheap and has built in battery socket
#  * Note that need to check polarity on battery correct - in my case frequently they are reversed
#  * The Hx711 does not adjust readings based on ambient temperature, so reads high/low as it is warmer/colder
#  * Optionally, can do some adjustment/compensation to make it less awful
# mdi icon info here: https://pictogrammers.com/library/mdi/icon/weight/

substitutions:
  name: gasbottle
  friendly_name: gasbottle
  devicename: gasbottle
  location: alfresco
  dapin: GPIO32 # for temp sensor
  adcpin: GPIO35 # for battery monitoring
  usbpin: GPIO16 # connected to USB pin to detect when there is power
  doutpin: GPIO27 # for hx711
  clkpin: GPIO14 # for hx711
  # adjustment to hx711 for temperature. Use a known weight at a set temp, then monitor weight change
  # over time as temp changes. Will find that it is not linear or consistent, but is not a medical grade
  # instrument! Personally mine seemed to range from 0.02 to 0.1 but was mainly between 0.06 and 0.08
  basetemp: '18.5'
  baseadj: '0.08'
#      tweak values in mapping for calibration
#      0.08 = adjustment per degree C
#      18.5 is default baseline temp 

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  min_version: 2024.6.0
  name_add_mac_suffix: false
  project:
    name: ninkasi.gas
    version: '1.1'
  comment: Gasbottle Weight Sensor LOLIN D32 $location
  platformio_options:
    build_flags:
      - "-D CONFIG_ADC_SUPPRESS_DEPRECATE_WARN=1" # Putting this in temporarily to remove warning “legacy adc calibration driver is deprecated" message during compilation - https://github.com/esphome/issues/issues/5153#issuecomment-1847547482

esp32:
  board: esp32dev
  framework:
    type: esp-idf
    version: recommended
    # Custom sdkconfig options
    sdkconfig_options:
      COMPILER_OPTIMIZATION_SIZE: y
    # Advanced tweaking options
    advanced:
      ignore_efuse_mac_crc: false

# Enable logging
# Change to avoid "Components should block for at most 20-30ms" warning messages in the log - an issue since 2023.7.0
# Not really a breaking change - it's an issue I suspect due to the device being slow and this error previously
# simply not being reported
logger:
  baud_rate: 0  # disable serial uart logging to maybe save a little ram
  logs:
    component: ERROR

api:
  encryption:
    key: !secret esphome_encryption_key

ota:
  password: !secret ota_password
  platform: esphome

wifi:
  networks:
  - ssid: !secret wifIoT_ssid
    password: !secret wifIoT_password
    priority: 2
# Backup SSID just in case
  - ssid: !secret wifi_ssid
    password: !secret wifi_password
    priority: 1
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "$devicename Fallback Hotspot"
    password: !secret ota_password

one_wire:
  - platform: gpio
    pin:
      number: $dapin
#      ignore_strapping_warning: true

esp32_ble_tracker:
  scan_parameters:
#    continuous: True
    active: True
    interval: 211ms # default 320ms
    window: 120ms # default 30ms

bluetooth_proxy:
  active: true

font:
    - file: "fonts/materialdesignicons-webfont.ttf"
      id: battery_icons_20
      size: 20
      bpp: 4
      glyphs: [
       "\U000F007A", # mdi-battery-10
       "\U000F007B", # mdi-battery-20
       "\U000F007C", # mdi-battery-30
       "\U000F007D", # mdi-battery-40
       "\U000F007E", # mdi-battery-50
       "\U000F007F", # mdi-battery-60
       "\U000F0080", # mdi-battery-70
       "\U000F0081", # mdi-battery-80
       "\U000F0082", # mdi-battery-90
       "\U000F0079", # mdi-battery (full)
       "\U000F008E", # mdi-battery-outline
       "\U000F0091", # mdi-battery-unknown
       ]

sensor:
  - platform: wifi_signal
    name: "WiFi Signal Sensor"
    id: wifisignal
    update_interval: 60s
    unit_of_measurement: dBm
    accuracy_decimals: 0
    device_class: signal_strength
    state_class: measurement
    entity_category: diagnostic
  - platform: copy # Reports the WiFi signal strength in %
    source_id: wifisignal
    id: wifipercent
    name: "WiFi Signal Percent"
    filters:
      - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
    unit_of_measurement: "Signal %"
    entity_category: "diagnostic"
  - platform: uptime
    id: uptime_s
    name: "$devicename Uptime"
    update_interval: 60s          
  - platform: template
    name: $devicename free memory
    lambda: return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
    icon: "mdi:memory"
    entity_category: diagnostic
    state_class: measurement
    unit_of_measurement: "b"
    update_interval: 60s
# Define the battery pin (GPIO35) as an ADC sensor
  - platform: adc
    pin: $adcpin
    name: "Battery Capacity"
    id: battery_capacity
    icon: mdi:battery-medium
    unit_of_measurement: "%"
    accuracy_decimals: 2
    attenuation: 12db
    update_interval: 60s  # Update every 60 seconds (adjust as needed)
    filters:
      - multiply: 2.0
      - median:
          window_size: 7
          send_every: 7
          send_first_at: 7
      - throttle: 15min
      - calibrate_polynomial:
         degree: 3
         datapoints:
          - 0.00 -> 0.0
          - 3.30 -> 0.0
          - 3.35 -> 5.0
          - 3.39 -> 10.0
          - 3.44 -> 15.0
          - 3.48 -> 20.0
          - 3.53 -> 25.0
          - 3.57 -> 30.0
          - 3.62 -> 35.0
          - 3.66 -> 40.0
          - 3.71 -> 45.0
          - 3.75 -> 50.0
          - 3.80 -> 55.0
          - 3.84 -> 60.0
          - 3.88 -> 65.0
          - 3.92 -> 70.0
          - 3.96 -> 75.0
          - 4.00 -> 80.0
          - 4.05 -> 85.0
          - 4.09 -> 90.0
          - 4.14 -> 95.0
          - 4.20 -> 100.0
      - lambda: |-
          if (x < 96) {
            return x;
          } else {
            return 100;
          }

    on_value_range:
      # Trigger an action if the battery voltage goes below a threshold (e.g., 3.3V)
      - above: 30
        then:
          - logger.log: "Battery voltage is above threshold"
      - below: 30
        then:
          - logger.log: "Battery power detected (below threshold)"
# refer to https://esphome.io/components/sensor/hx711.html
  - platform: dallas_temp
    address: 0x2c000000855a4128
    name: "$devicename Temp"
    id: tempv
    filters:
      - median:
          window_size: 7
          send_every: 7
          send_first_at: 7
# Note "-" is ground, "+" is power
    update_interval: 10s
    on_value: # This is to gather data used to create compensation logic with a known weight on scale
      then:
        - lambda: |-
            ESP_LOGD("temp", "%.2f kg, %.2f °C, %.2f °C, %.2f kg", id(hx711v).state, id(tempv).state, id(outdoor_temperature).state, id(hx711a).state);
  - platform: homeassistant
    id: outdoor_temperature
    entity_id: sensor.gw1000_v1_7_6_outdoor_temperature
  - platform: hx711
    name: "HX711 Value"
    id: hx711v
    dout_pin: $doutpin
    clk_pin: $clkpin
    gain: 128
    update_interval: 10s
    filters:
      - calibrate_linear:
          - 149003 -> 0
          - 197351 -> 3
      - median:
          window_size: 7
          send_every: 4
          send_first_at: 3
    unit_of_measurement: kg
    accuracy_decimals: 2
# Can use something like this to adjust readings
# I like to have both, so create a separate sensor with the adjusted amount
#      - lambda: |- 
#          const float reference_temp = 18.5;
#          const float temp_coefficient = 0.08;  // Adjust this based on your tests
#          float temperature = id(temp_sensor).state;
#          float compensation = (temperature - reference_temp) * temp_coefficient;
#          return x - compensation;
  - platform: template 
    name: hx711-adjusted
    id: hx711a
    lambda: return (id(hx711v).state - (id(tempv).state-$basetemp)*$baseadj) ;
    accuracy_decimals: 2
    unit_of_measurement: Kg
    device_class: power
    update_interval: 10s
    icon: "mdi:weight-kilogram"    

# Optional: Set a custom threshold to trigger actions, e.g., battery level below 3.3V
binary_sensor:
  - platform: template
    name: "Low Battery"
    lambda: |-
      if (id(battery_capacity).state < 30) {
        return true;
      } else {
        return false;
      }
    on_press:
      - logger.log: "Battery is low"
    on_release:
      - logger.log: "Battery is back to normal"

# Define the GPIO pin connected to USB power detection
  - platform: gpio
    pin: $usbpin  # GPIO pin connected to USB power (this varies by board)
    name: "USB Power Status"
    device_class: power
    filters:
      - delayed_on: 100ms
      - delayed_off: 100ms
    on_press:
      # Action to take when USB power is disconnected (i.e., running on battery)
      then:
        - logger.log: "Running on battery power"
    on_release:
      then:
        - logger.log: "Running on USB power"
        # Optionally, trigger actions for when USB power is present

Ah. Just realised that the tare weight (T.W.) of the gas bottle is stamped on the side of each bottle. My backup (unused) bottle has a T.W. of 8.24 and weighs 17.2kg, so it has 8.96kg of gas - so that seems to line up for a 9kg (i.e. they should start with 9kg of gas when full) bottle.

The partially used bottle that is in the BBQ now has a T.W. of 7.75kg

When I get the chance, I’ll update my code so I can set the T.W. of the currently installed bottle, so I get a more accurate reading of how much gas is actually available. Will also have to allow for the weight of the connector and gas hose etc.

1 Like

Yeah, I tried to do the same thing using the HX711 sensors and the temperature drift was too much for me. And I couldn’t get a good compensation worked out for it so I gave up on that.

Now I use Mopeka Tank sensors. They’re Bluetooth so you just need a BT Proxy to read them and they are reliable and accurate.

1 Like

Agreed, Mopeka Tank sensors aren’t 100% perfect, but I 've found they work the best. I added the Mopeka Tank WiFi Router Bridge additional features

How much they drift? Is it in the range of grams, kilograms…? (or even better: how many percent)? Is there any datashet for these chinese load cells?
I use these cells for dog water bowl. Temperature in the hallway is pretty constant now, but in the summer it will rise, so it would be nice to know how much to compensate. my average load is 2-4 kg. I made my system so that it automatically resets to zero each time i remove the bowl (when i’m cleaning it, so at least once a day), and so far it works perfectly.

That’s pretty cool. I would think on that scale, the tolerances have to be small.

Good job.
I really don’t care about temperature for my grill gas tank, just need to know when I have to replace it. So here is my code and template for card and automation.

  {{ [0, [((states('sensor.propane_tank_tank_weight') | float - 17) / 21 * 100),
  100] | min] | max | round(0) }} %

type: custom:mushroom-template-card
primary: >-
  {{ [0, [((states('sensor.propane_tank_tank_weight') | float - 17) / 21 * 100),
  100] | min] | max | round(0) }} %
secondary: ""
icon: mdi:propane-tank
entity: sensor.propane_tank_tank_weight
icon_color: |-
  {% set bl = states('sensor.propane_tank_tank_weight') | int %}
   
  {% if bl < 20 %} orange
  {% elif bl < 27 %} blue
  {% elif bl < 38 %} green
  {% elif bl == 38 %} green
  {% endif %}
card_mod:
  style:
    mushroom-shape-icon$: |
      .shape {
        --shape-color: none !important;
      }
    .: |
      ha-state-icon {
        animation: pulse 2s ease-in-out infinite;
        stroke: white;
            stroke-width: .4px;
      }
      ha-card {
      background: none !important;
      border: none !important;
      }  
visibility:
  - condition: numeric_state
    entity: sensor.propane_tank_tank_weight
    below: 20

Automation

alias: "Grill Propane Tank Replacement Time "
description: ""
triggers:
  - trigger: numeric_state
    entity_id:
      - sensor.propane_tank_tank_weight
    below: 19
conditions: []
actions:
  - action: notify.mobile_app_sm_s918u
    metadata: {}
    data:
      message: >-
        Time to replace Grill Gas Tank. {{ [0,
        [((states('sensor.propane_tank_tank_weight') | float - 17) / 21 * 100),
        100] | min] | max | round(0) }} %
mode: single
2 Likes

I guess i’ll have to spend another few € for another set and make some measurements. It’s a good idea to have a spare set anyway… it’s cheap chinese stuff, it can die suddenly, i guess…

Nice - wasn’t really aware of those. Probably over the top for my purposes though, plus looks like you need to stick them to the tank to get accurate readings - and in my case the empty tanks are exchanged for full ones (in the good old days you could get them refilled, but is very hard to find places that do that now) so I’d have to remember to remove the sensor and refit each time which would be slightly annoying… will keep in mind though!

I suspect that the amount of drift will vary depending on the actual devices themselves. In my case it appears to be around the 5% mark per degree C but the variance is not linear.

I had manually captured some readings, and what I have now is probably good enough but because I can’t help myself I’ve now modified my code to send the readings to google sheets every 5 minutes so I can then get a decent amount of data points over time/temps.

1 Like

They are magnetic so removing and refitting is pretty easy.

Will definitely consider if I can’t get the cheaper load sensors to work, but ease of fitting is only one of my problems. The other is that I am not the only one who might change the tanks - so is quite possible that the tank will be changed and then exchanged with the sensor still attached, bringing me great sadness. :wink:

…and as promised, a minor update to my code to allow for the tare weight of the bottle itself, plus a few other tweaks.

# Compiled and tested on esphome 2025.3.3 and HA 2025.4.1

# Notes:
#  * need to jumper between USB pin and a GPIO - when USB power is connected, power goes to the USB pin and sets pin high
#  * Using a Lolin D32 as cheap and has built in battery socket
#  * Note that need to check polarity on battery correct - in my case frequently they are reversed
#  * The Hx711 does not adjust readings based on ambient temperature, so reads high/low as it is warmer/colder
#  * Optionally, can do some adjustment/compensation to make it less awful

# The 9kg gas bottle weight full (8.5 kg gas bottle), for SWAP’n’GO gas bottles, includes the weight 
# of the 8.5kg gas bottle plus the 8.5kg of LPG, which is approximately 17.5kg.
# https://www.elgas.com.au/elgas-knowledge-hub/residential-lpg/8-5kg-gas-bottle-weight-full-45kg-weigh-full/
# Just weighed two: one that had been used for one cook was at 17.2, and a never used bottle was also at 17.2kg.
# There will be variance as not all bottles are exactly the same.
# The tare weight of steel SWAP’n’GO 8.5kg gas bottle ranges from 7.8 to 10.4 kg

# Currently using google sheets to log the data
# Enable the google sheets integration
# https://www.home-assistant.io/integrations/google_sheets/
# Then setup automation to capture logs - remember to disable this once all ok


substitutions:
  name: gasbottle
  friendly_name: gasbottle
  devicename: gasbottle
  location: alfresco
  dapin: GPIO32 # for temp sensor
  adcpin: GPIO35 # for battery monitoring
  usbpin: GPIO16 # connected to USB pin to detect when there is power
  doutpin: GPIO27 # for hx711
  clkpin: GPIO14 # for hx711
  # adjustment to hx711 for temperature. Use a known weight at a set temp, then monitor weight change
  # over time as temp changes. Will find that it is not linear or consistent, but is not a medical grade
  # instrument! Personally mine seemed to range from 0.02 to 0.1 but was mainly between 0.06 and 0.08
  basetemp: '20.2'
  baseadj: '0.08'
  connector: '0.5' # weight in kilos of hose etc connected to bottle

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  min_version: 2024.6.0
  name_add_mac_suffix: false
  project:
    name: ninkasi.gas
    version: '1.1'
  comment: Gasbottle Weight Sensor LOLIN D32 $location
  platformio_options:
    build_flags:
      - "-D CONFIG_ADC_SUPPRESS_DEPRECATE_WARN=1" # Putting this in temporarily to remove warning “legacy adc calibration driver is deprecated" message during compilation - https://github.com/esphome/issues/issues/5153#issuecomment-1847547482

esp32:
  board: esp32dev
  framework:
    type: esp-idf
    version: recommended
    # Custom sdkconfig options
    sdkconfig_options:
      COMPILER_OPTIMIZATION_SIZE: y
    # Advanced tweaking options
    advanced:
      ignore_efuse_mac_crc: false

# Enable logging
# Change to avoid "Components should block for at most 20-30ms" warning messages in the log - an issue since 2023.7.0
# Not really a breaking change - it's an issue I suspect due to the device being slow and this error previously
# simply not being reported
logger:
  baud_rate: 0  # disable serial uart logging to maybe save a little ram
  logs:
    component: ERROR

api:
  encryption:
    key: !secret esphome_encryption_key

ota:
  password: !secret ota_password
  platform: esphome

wifi:
  networks:
  - ssid: !secret wifIoT_ssid
    password: !secret wifIoT_password
    priority: 2
# Backup SSID just in case
  - ssid: !secret wifi_ssid
    password: !secret wifi_password
    priority: 1
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "$devicename Fallback Hotspot"
    password: !secret ota_password

one_wire:
  - platform: gpio
    pin:
      number: $dapin
#      ignore_strapping_warning: true

esp32_ble_tracker:
  scan_parameters:
#    continuous: True
    active: True
    interval: 211ms # default 320ms
    window: 120ms # default 30ms

bluetooth_proxy:
  active: true

font:
    - file: "fonts/materialdesignicons-webfont.ttf"
      id: battery_icons_20
      size: 20
      bpp: 4
      glyphs: [
       "\U000F007A", # mdi-battery-10
       "\U000F007B", # mdi-battery-20
       "\U000F007C", # mdi-battery-30
       "\U000F007D", # mdi-battery-40
       "\U000F007E", # mdi-battery-50
       "\U000F007F", # mdi-battery-60
       "\U000F0080", # mdi-battery-70
       "\U000F0081", # mdi-battery-80
       "\U000F0082", # mdi-battery-90
       "\U000F0079", # mdi-battery (full)
       "\U000F008E", # mdi-battery-outline
       "\U000F0091", # mdi-battery-unknown
       ]

sensor:
  - platform: wifi_signal
    name: "WiFi Signal Sensor"
    id: wifisignal
    update_interval: 60s
    unit_of_measurement: dBm
    accuracy_decimals: 0
    device_class: signal_strength
    state_class: measurement
    entity_category: diagnostic
  - platform: copy # Reports the WiFi signal strength in %
    source_id: wifisignal
    id: wifipercent
    name: "WiFi Signal Percent"
    filters:
      - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0);
    unit_of_measurement: "Signal %"
    entity_category: "diagnostic"
  - platform: uptime
    id: uptime_s
    name: "$devicename Uptime"
    update_interval: 60s          
  - platform: template
    name: $devicename free memory
    lambda: return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
    icon: "mdi:memory"
    entity_category: diagnostic
    state_class: measurement
    unit_of_measurement: "b"
    update_interval: 60s
# Define the battery pin (GPIO35) as an ADC sensor
  - platform: adc
    pin: $adcpin
    name: "Battery Capacity"
    id: battery_capacity
    icon: mdi:battery-medium
    unit_of_measurement: "%"
    accuracy_decimals: 2
    attenuation: 12db
    update_interval: 60s  # Update every 60 seconds (adjust as needed)
    filters:
      - multiply: 2.0
      - median:
          window_size: 7
          send_every: 7
          send_first_at: 7
      - throttle: 15min
      - calibrate_polynomial:
         degree: 3
         datapoints:
          - 0.00 -> 0.0
          - 3.30 -> 0.0
          - 3.35 -> 5.0
          - 3.39 -> 10.0
          - 3.44 -> 15.0
          - 3.48 -> 20.0
          - 3.53 -> 25.0
          - 3.57 -> 30.0
          - 3.62 -> 35.0
          - 3.66 -> 40.0
          - 3.71 -> 45.0
          - 3.75 -> 50.0
          - 3.80 -> 55.0
          - 3.84 -> 60.0
          - 3.88 -> 65.0
          - 3.92 -> 70.0
          - 3.96 -> 75.0
          - 4.00 -> 80.0
          - 4.05 -> 85.0
          - 4.09 -> 90.0
          - 4.14 -> 95.0
          - 4.20 -> 100.0
      - lambda: |-
          if (x < 96) {
            return x;
          } else {
            return 100;
          }

    on_value_range:
      # Trigger an action if the battery voltage goes below a threshold (e.g., 3.3V)
      - above: 30
        then:
          - logger.log: "Battery voltage is above threshold"
      - below: 30
        then:
          - logger.log: "Battery power detected (below threshold)"
# refer to https://esphome.io/components/sensor/hx711.html
  - platform: dallas_temp
    address: 0x2c000000855a4128
    name: "$devicename Temp"
    id: tempv
    filters:
      - median:
          window_size: 7
          send_every: 7
          send_first_at: 7
# Note "-" is ground, "+" is power
    update_interval: 10s
#    on_value: # This is to gather data used to create compensation logic with a known weight on scale
#      then:
#        - lambda: |-
#            ESP_LOGD("temp", "%.2f kg, %.2f °C, %.2f °C, %.2f kg", id(hx711v).state, id(tempv).state, id(outdoor_temperature).state, id(hx711a).state);
  - platform: homeassistant
    id: outdoor_temperature
    entity_id: sensor.gw1000_v1_7_6_outdoor_temperature
  - platform: hx711 # The raw readings from the HX711
    name: "HX711 Raw"
    id: hx711a
    dout_pin: $doutpin
    clk_pin: $clkpin
    gain: 128
    update_interval: 10s
    accuracy_decimals: 0 
  - platform: template # The raw readings converted to kg
    name: "HX711 kg"
    id: hx711b
    lambda: return id(hx711a).state ;
    update_interval: 10s
    filters:
      - calibrate_linear:
          - 161695 -> 0 # note - reading taken with ambient temp of 21C
          - 397823 -> 16.5 # gas bottle plus connectors and hose etc
      - median:
          window_size: 7
          send_every: 4
          send_first_at: 3
    accuracy_decimals: 2
    unit_of_measurement: Kg
    device_class: weight
  - platform: template # The raw readings converted to kg
    name: "HX711 kg adjusted"
    id: hx711c
    lambda: return id(hx711b).state + ($basetemp-id(tempv).state)*$baseadj;
    update_interval: 10s
    accuracy_decimals: 2
    unit_of_measurement: Kg
    device_class: weight


  - platform: template # Actual weight of gas available - total weight of bottle, less the Tare Weight of bottle and connector
    name: gas
    id: gas
    lambda: return (id(hx711c).state - id(tareweight).state) - $connector;
    accuracy_decimals: 2
    unit_of_measurement: Kg
    device_class: weight
    update_interval: 10s
    icon: "mdi:weight-kilogram"    
  - platform: template 
    name: tarew # just putting this in, so can see it in the dashboard as a reminder to change if required when bottle is changed
    lambda: return id(tareweight).state;
    accuracy_decimals: 2
    unit_of_measurement: Kg
    update_interval: 600s
    icon: "mdi:weight-kilogram"    

# Optional: Set a custom threshold to trigger actions, e.g., battery level below 3.3V
binary_sensor:
  - platform: template
    name: "Low Battery"
    lambda: |-
      if (id(battery_capacity).state < 30) {
        return true;
      } else {
        return false;
      }
    on_press:
      - logger.log: "Battery is low"
    on_release:
      - logger.log: "Battery is back to normal"

# Define the GPIO pin connected to USB power detection
  - platform: gpio
    pin: $usbpin  # GPIO pin connected to USB power (this varies by board)
    name: "USB Power Status"
  #  device_class: power
    filters:
      - delayed_on: 100ms
      - delayed_off: 100ms
    on_press:
      # Action to take when USB power is disconnected (i.e., running on battery)
      then:
        - logger.log: "Running on battery power"
    on_release:
      then:
        - logger.log: "Running on USB power"
        # Optionally, trigger actions for when USB power is present
number:
  - platform: template
    name: "Tare Weight"
    id: tareweight
    optimistic: true
    min_value: 7.5
    max_value: 11.0
    step: 0.05
    restore_value: True #save this to memory, so retains even after a reboot
    update_interval: 600s

I am currently using the google sheets integration to track the weight over time using an automation eg:

alias: gasbottle-log
description: ""
triggers:
  - trigger: time_pattern
    minutes: /5
conditions: []
actions:
  - action: google_sheets.append_sheet
    metadata: {}
    data:
      config_entry: numbers<-change this
      data:
        Date: "{{ now().date() }}"
        Time: "{{ now().strftime('%H:%M')}}"
        Temp: "{{states.sensor.gasbottle_gasbottle_temp.state}}"
        TempW: "{{states.sensor.gw1000_v1_7_6_outdoor_temperature.state}}"
        Raw: "{{states.sensor.gasbottle_hx711_raw.state}}"
        Kg: "{{states.sensor.gasbottle_hx711_kg.state}}"
        Adjusted: "{{states.sensor.gasbottle_hx711_kg_adjusted.state}}"
        Tare: "{{states.sensor.gasbottle_tarew.state}}"
        gas: "{{states.sensor.gasbottle_gas.state}}"
mode: single

Once I have sufficient readings over a range of temperatures, I’ll work out whether it’s better to use the external temperature or the sensor next to the scales, and what I might need to tweak to make the readings a bit more accurate.

Oh, FWIW I have on my dashboard now a gauge showing how much gas is left…

type: gauge
entity: sensor.gasbottle_gas
name: Alfresco Gas Bottle
needle: true
max: 9
severity:
  green: 4
  yellow: 2
  red: 0
min: 1