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.
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