A basic IoT scale that monitors the weight of a gas bottle (e.g. a CO2 tank for a kegerator or aquarium) and reports the current weight and the estimated amount of gas remaining over MQTT.
An ESP8266 reads a set of load cells through an HX711 amplifier, publishes two values to an MQTT broker, and then drops into deep sleep to conserve power. It wakes on a fixed interval, takes a fresh reading, publishes, and sleeps again.
- Continuously track how much CO2 (or any gas) is left in a bottle without manual weighing.
- Publish readings to a home-automation / dashboard system (Adafruit IO, Mosquitto, Home Assistant, etc.) via MQTT.
- Spend almost all of its time in ESP8266 deep sleep, so it sips power between readings.
The firmware assumes pounds (lbs). Kilograms work fine too — the calibration factor is just linearly related (1 lb = 0.453592 kg), so pick your unit and set the calibration factor to match.
On each wake cycle the device:
- Boots out of deep sleep and initializes the HX711 scale.
- Checks the TARE button:
- If not pressed, it loads the previously saved zero offset from EEPROM.
- If pressed, it re-tares the scale (zeroes it) and saves the new offset to EEPROM.
- Connects to WiFi (10 checks per attempt, up to 5 attempts), then connects to the MQTT broker
(up to
MQTT_CONNECT_ATTEMPTSattempts). - Reads the scale, computes
estimatedRemaining = FULL_BOTTLE_WEIGHT - currentReading, and publishes both values to their MQTT topics. - Enters deep sleep for
SLEEP_TIMEseconds (default 15 minutes) and repeats.
If either WiFi or the MQTT broker can't be reached, the device gives up rather than retrying indefinitely: it logs the failure, skips publishing, and goes straight back to sleep to try again on the next cycle. This bounds how long a wake cycle can draw current for when the network is down.
Installed: the electronics enclosure sits alongside the scale platform, which the CO2 bottle stands on.
| Component | Notes |
|---|---|
| ESP8266 board | e.g. Adafruit Feather HUZZAH with ESP8266. The 3.3 V rail powers the HX711. |
| HX711 load cell amplifier | 24-bit ADC breakout for the load cells. Runs at 2.7 V–5 V, so 3.3 V is fine. |
| 4 × half-bridge load cells | The common 50 kg "bathroom scale" type. All four wire together into a single full Wheatstone bridge feeding the HX711 — see wiring. |
| Tactile push button | Momentary button used to trigger a re-tare. |
| Power source | USB (as built). Deep sleep keeps average current low, so battery should be viable — though it hasn't been tested on one. |
Deep sleep wiring note: For the ESP8266 to wake itself from
ESP.deepSleep(), GPIO16 (D0) must be connected to the RST pin. Without this jumper the device will not wake up after the first sleep.
Four half-bridge cells combine into one full bridge. Each cell has three wires (red, black, white). Wire them so that opposite cells form the two halves of the bridge:
| Bridge signal | HX711 pin |
|---|---|
| Excitation + | E+ |
| Excitation − | E− |
| Signal + | A+ |
| Signal − | A− |
If your readings run backwards (weight increasing reads negative), that's expected and normal —
it just means the cells deflect the other way. Use a negative CALIBRATION_FACTOR to correct
it rather than rewiring. The default here is -9500 for exactly this reason.
The scale body — the disc the load cells mount to — lives in hardware/:
BottleScale.skp— SketchUp source model (editable).BottleScale.stl— exported mesh, ready to slice and print.
The electronics enclosure shown in the photos is a generic third-party ESP8266 case and is not included in this repo.
Install these through the Arduino Library Manager (or manually):
| Library | Source |
|---|---|
ESP8266 board support (ESP8266WiFi, EEPROM) |
esp8266/Arduino board package |
HX711 |
bogde/HX711 |
PubSubClient |
knolleary/pubsubclient |
Defined at the top of BottleScale/BottleScale.ino:
| Signal | ESP8266 GPIO | #define |
|---|---|---|
HX711 DOUT (data) |
GPIO14 | DOUT |
HX711 SCK / CLK (clock) |
GPIO12 | CLK |
| TARE button | GPIO13 | TARE_PIN |
The TARE button uses the internal pull-up (INPUT_PULLUP); wire the button between GPIO13 and
GND. A HIGH reading means "not pressed" (use the saved offset); pulling the pin LOW by
pressing the button triggers a re-tare.
The HX711 also needs power — connect its VCC to the ESP8266 3.3 V pin and GND to ground.
Edit the #define values near the top of BottleScale/BottleScale.ino
before flashing. The committed file ships with <PLACEHOLDER> values:
| Setting | Purpose |
|---|---|
WIFI_SSID / WIFI_PASSWORD |
Your WiFi credentials. |
MQTT_SERVER / MQTT_PORT |
MQTT broker address and port (default 1883). |
MQTT_USER / MQTT_PASSWORD |
MQTT broker credentials. Leave as empty strings for an anonymous local broker. |
MQTT_TOPIC_SCALE_READING |
Topic for the raw weight reading. |
MQTT_TOPIC_EST_REMAINING |
Topic for the estimated gas remaining. |
MQTT_CONNECT_ATTEMPTS |
How many times to try the broker before giving up and sleeping (default 5, 2 s apart). |
CALIBRATION_FACTOR |
Load-cell calibration (default -9500). Adjust for your cells and unit. |
FULL_BOTTLE_WEIGHT |
Weight of gas in a full bottle (default 5 lb), used for the "remaining" estimate. |
SLEEP_TIME |
Deep-sleep interval in seconds (default 900 = 15 min, max ~70 min). |
OFFSET_EEPROM_ADDR |
EEPROM address where the tare offset is stored (default 0). |
The source includes commented-out MQTT settings for Adafruit IO as an alternative to a local Mosquitto broker — uncomment whichever set you use.
This sketch keeps WiFi and MQTT credentials inline as
#defines, which makes it very easy to commit real secrets by accident. Before you push anything:git diff -- BottleScale/BottleScale.inoand confirm the placeholders are still placeholders. To have git ignore your local edits to the file entirely:
git update-index --skip-worktree BottleScale/BottleScale.ino
This project makes no attempt at transport security, which is worth understanding before you deploy it:
- MQTT runs in plaintext on port 1883. Credentials and readings are sent unencrypted. Anyone on your network can read them.
- No TLS, no certificate validation.
WiFiClientis used rather thanWiFiClientSecure. - Credentials are compiled into the firmware. Anyone who can read the flash can recover your WiFi password.
This is fine for a device on a trusted home LAN talking to a local broker. Do not point this at
a public broker over the internet as-is — if you need that, switch to WiFiClientSecure on port
8883 and use a broker-specific token rather than your real credentials.
- With no bottle on the scale, hold the TARE button while resetting the device to zero it.
- Place a known weight on the scale and compare the reported reading.
- Adjust
CALIBRATION_FACTORup or down until the reading matches the known weight, then re-flash.
For an interactive way to find the factor, SparkFun's
HX711 calibration example lets you nudge
the value with +/- over the serial monitor until the reading matches your known weight. Run it
with the same DOUT/CLK pins as above (GPIO14 / GPIO12), then copy the resulting factor into
this sketch.
- Install the ESP8266 board package and the libraries listed above in the Arduino IDE.
- Open BottleScale/BottleScale.ino.
- Fill in your WiFi, MQTT, and calibration settings.
- Select your ESP8266 board and port, then upload.
- Open the Serial Monitor at 9600 baud to watch the boot/connect/publish/sleep cycle.
Contributions welcome — these are known and unfixed:
estimatedRemainingdepends on how you tared. The firmware computesFULL_BOTTLE_WEIGHT - currentReading. That only yields "gas remaining" if the scale was tared with a full bottle in place. If you tared with an empty cylinder on the scale, thencurrentReadingis already the gas remaining and this value is the gas consumed. Check which convention your tare matches before trusting the number.
This repository is dual-licensed:
| Path | License |
|---|---|
Firmware (BottleScale/) and documentation |
MIT |
3D models (hardware/) |
CC BY-SA 4.0 |
The HX711 library by bogde is a separate GPL-licensed dependency, not included here — install it via the Arduino Library Manager. The calibration procedure references SparkFun's beerware-licensed example, linked above rather than vendored.



