My ‘simple’ keg scale

Since Plaato is retiring their Keg scale in 2024/25 I had to find a new solution. Searching the web lead me to GitHub – Callwater/Beerkeg-load-cell. Since I had some ESP and loadcells at my hands it was very quick to setup. Had some wood and a router in my ‘workshop’ so made the base in a short time. Soldering all parts together and gluing them to the base.

The basic hardware and software was quickly up and running.

Added a MQTT server to my HomeAssistant instance to prepare for some data presentation. Since there already was a mqtt client library ready for node-mcu – esp8266 it was nearly a walk in the park.

Code was added using Visual Studio Code.

File : main.cpp

#include <Arduino.h>
#include <DHT.h>
#include <DHT_U.h>
#include <HX711.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

#include "config.h"
#define OLED_RESET -1       // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3c ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

DHT dht{DHT_PIN, DHT_TYPE};      // Initiate DHT library
HX711 scale;                     // Initiate HX711 library
WiFiClient wifiClient;           // Initiate WiFi library
PubSubClient client(wifiClient); // Initiate PubSubClient library
int publishcount = 10;           // Do 10 mqtt publishes - then force reconnect
bool displayconnected = true;
float prevmeter = 1000.0;
int screentimeout = 10;
void reconnect();
void callback(char *topic, byte *payload, unsigned int length);
void drawText(float temperature, float volume, float humidity, String Message);
void drawMessage(String message);
void screenoff();
void setup()
{
  Serial.begin(74880);
  Serial.println();
  WiFi.mode(WIFI_STA);
  WiFi.begin(SSID, PASSWORD);
  Serial.print("Connecting...");
  while (WiFi.status() != WL_CONNECTED)
  { // Wait till Wifi connected
    delay(500);
    Serial.print(".");
  }
  Serial.println();

  Serial.print("Connected.IP address:");
  Serial.println(WiFi.localIP()); // Print IP address
  delay(2000);
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS))
  {
    Serial.println(F("SSD1306 allocation failed"));
    // for(;;); // Don't proceed, loop forever
    displayconnected = false;
  }
  else
  {
    Serial.println(F("SSD1306 allocation success"));
    displayconnected = true;
    display.clearDisplay();
    display.display();
  };

  if (displayconnected)
  {
    drawMessage("Connected IP address:");
    delay(2000);
    drawMessage(WiFi.localIP().toString());
    delay(2000);
  }
  client.setServer(MQTT_SERVER, 1883);              // Set MQTT server and port number
  client.setCallback(callback);                     // Set callback address, this is used for remote tare
  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); // Start scale on specified pins
  scale.wait_ready();                               // Ensure scale is ready, this is a blocking function
  scale.set_scale();
  Serial.println("Scale Set");
  scale.wait_ready();
  scale.tare(); // Tare scale on startup
  scale.wait_ready();
  Serial.println("Scale Zeroed");

  Serial.println("Start DHT library");
  dht.begin();
}

void loop()
{
  float reading;                       // Float for reading
  float raw;                           // Float for raw value which can be useful
  scale.wait_ready();                  // Wait till scale is ready, this is blocking if your hardware is not connected properly.
  scale.set_scale(calibration_factor); // Sets the calibration factor.
  scale.set_offset(offset_factor);     // Sets offset for empty keg
  if (publishcount < 1)
  {
    // reconnect();
    client.disconnect();
    publishcount = 10;
    // Serial.println("Scheduled reconnect.");
  }
  publishcount--;

  // Ensure we are still connected to MQTT Topics
  if (!client.connected())
  {
    reconnect();
  }

  Serial.print("Reading: "); // Prints weight readings in .2 decimal kg units.
  scale.wait_ready();
  reading = scale.get_units(10); // Read scale in g/Kg
  raw = scale.read_average(5);   // Read raw value from scale too
  Serial.print(reading, 1);
  Serial.println(" L");
  Serial.print("Raw: ");
  Serial.println(raw);
  Serial.print("Calibration factor: "); // Prints calibration factor.
  Serial.println(calibration_factor);

  if (reading < 0)
  {
    reading = 0.00; // Sets reading to 0 if it is a negative value, sometimes loadcells will drift into slightly negative values
  }

  String value_str = String(reading);
  String value_raw_str = String(raw);
  client.publish(STATE_TOPIC, value_str.c_str());         // Publish weight to the STATE topic
  client.publish(STATE_RAW_TOPIC, value_raw_str.c_str()); // Publish raw value to the RAW topic

  client.loop();      // MQTT task loop
  scale.power_down(); // Puts the scale to sleep mode for 3 seconds. I had issues getting readings if I did not do this
  delay(3000);
  scale.power_up();

  // Reading values from the DHT sensor
  float humidity = dht.readHumidity();       // Read humidity
  float temperature = dht.readTemperature(); // Read temperature

  // Check if we recieved a number from dht libraray
  if (isnan(humidity) || isnan(temperature))
  {
    Serial.println("Error while parse values to numbers from dht");
    drawMessage("Errir reading DHT");
    delay(2000);
  }

  client.publish(TEMPERATURE_TOPIC, String(temperature).c_str()); // Publish temperature to the temperature value topic
  client.publish(HUMIDITY_TOPIC, String(humidity).c_str());       // Publish humidity to the humidity value topic
  Serial.print("Temp:");
  Serial.println(temperature);
  Serial.print("Displayconnected:");
  Serial.println(displayconnected);
  Serial.print("Prev:");
  Serial.println(prevmeter);
  Serial.print("Reading2:");
  Serial.println(reading);
    Serial.print("Screentimeout:");
    Serial.println(screentimeout);

  if (displayconnected)
  {
    if ((reading < (prevmeter - 0.1)) || (reading > (prevmeter + 0.1)) ||(prevmeter == 1000))
    {
      prevmeter = reading;
      screentimeout = 10;
    }
          screentimeout--;
      if (screentimeout < 1)
      {
        screentimeout = 1;
        screenoff();
      }
    Serial.print("Minvalue:");
    Serial.println((prevmeter - 0.1));
   
    if (screentimeout > 1)
    {
      Serial.println("Print to display");
      drawText(temperature, reading, humidity, "beer remaining !!!");
    }
  }
}

void reconnect()
{
  while (!client.connected())
  { // Loop until connected to MQTT server
    Serial.print("Attempting MQTT connection...");
    if (client.connect(HOSTNAME, mqtt_username, mqtt_password))
    { // Connect to MQTT server
      Serial.println("connected");
      client.publish(AVAILABILITY_TOPIC, "online"); // Once connected, publish online to the availability topic
      client.subscribe(TARE_TOPIC);                 // Subscribe to tare topic for remote tare
      client.subscribe(OFFSET_TOPIC);
    }
    else
    {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000); // Will attempt connection again in 5 seconds
    }
  }
}

void callback(char *topic, byte *payload, unsigned int length)
{
  if (strcmp(topic, TARE_TOPIC) == 0)
  {
    Serial.println("starting tare...");
    scale.wait_ready();
    scale.set_scale();
    scale.tare(); // Reset scale to zero
    Serial.println("Scale reset to zero");
  }
  if (strcmp(topic, OFFSET_TOPIC) == 0)
  {
    Serial.println("Setting offset...");
    scale.wait_ready();
    scale.set_scale();
    scale.set_offset(offset_factor);
    Serial.print("Scale set to offset ");
    Serial.println(*payload);
  }
}
void drawText(float temperature, float volume, float humidity, String message)
{
  display.clearDisplay();
  display.setCursor(0, 0); // Start at top-left corner
  display.setTextSize(1);  // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.print("T: ");
  display.print(temperature);
  display.print((char)247);
  display.println(F("C"));
  display.print("H: ");
  display.print(humidity);
  display.println(F("%"));
  display.setTextSize(3); // Draw 2X-scale text
  display.print(volume);
  display.println(F("L"));
  display.setTextSize(1); // Draw 2X-scale text
  display.println(message);

  display.display();
  // delay(2000);
}
void drawMessage(String message)
{
  display.clearDisplay();
  display.setCursor(0, 0); // Start at top-left corner
  display.setTextSize(1);  // Draw 2X-scale text
  display.setTextColor(SSD1306_WHITE);
  display.println(message);
  display.display();
  // delay(2000);
}

void screenoff()
{
  display.clearDisplay();
  display.display();
}

File : config.h

// Wifi Settings
#define SSID                          "SSID"
#define PASSWORD                      "WIFIPWD"

// MQTT Settings
#define HOSTNAME                      "HA hostname" //"beer_1"
#define MQTT_SERVER                   "192.168.10.132"
#define STATE_TOPIC                   "beer_1"
#define STATE_RAW_TOPIC               "beer_1/raw"
#define AVAILABILITY_TOPIC            "beer_1/available"
#define TARE_TOPIC                    "beer_1/tare"
#define OFFSET_TOPIC                  "beer_1/offset"
#define TEMPERATURE_TOPIC             "beer_1/temperature"
#define HUMIDITY_TOPIC                "beer_1/humidity"
#define mqtt_username                 "mqttusername"
#define mqtt_password                 "mqttpassword"

// HX711 Pins
const int LOADCELL_DOUT_PIN = 12;  // Remember these are ESP GPIO pins, they are not the physical pins on the board.
const int LOADCELL_SCK_PIN = 13;
int calibration_factor = -22500;  // Defines calibration factor we'll use for calibrating.
int offset_factor = -285746;       // Defines offset factor ; -285746 = Empty Cornelius 19L keg. 

// DHT Settings
constexpr auto DHT_PIN = 14;            // Remember these are ESP GPIO pins, they are not the physical pins on the board. 
#define DHT_TYPE DHT11                 // DHT11 or DHT22
constexpr auto sendDHTDataDelay = 500ul; // Delay between sending data over MQTT

Leave a Reply