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