{"id":1238,"date":"2024-06-02T13:10:39","date_gmt":"2024-06-02T12:10:39","guid":{"rendered":"https:\/\/www.vatland.no\/?p=1238"},"modified":"2024-06-02T13:10:44","modified_gmt":"2024-06-02T12:10:44","slug":"my-simple-keg-scale","status":"publish","type":"post","link":"https:\/\/www.vatland.no\/index.php\/my-simple-keg-scale\/","title":{"rendered":"My &#8216;simple&#8217; keg scale"},"content":{"rendered":"\n<figure class=\"wp-block-image size-large is-resized\"><a href=\"https:\/\/www.vatland.no\/wp-content\/uploads\/2024\/01\/w3-rotated.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"768\" height=\"1024\" data-attachment-id=\"1215\" data-permalink=\"https:\/\/www.vatland.no\/w3\/\" data-orig-file=\"https:\/\/www.vatland.no\/wp-content\/uploads\/2024\/01\/w3-rotated.jpg\" data-orig-size=\"1024,1365\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;1703512121&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;1&quot;}\" data-image-title=\"Keg scale\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.vatland.no\/wp-content\/uploads\/2024\/01\/w3-768x1024.jpg\" src=\"https:\/\/www.vatland.no\/wp-content\/uploads\/2024\/01\/w3-768x1024.jpg\" alt=\"\" class=\"wp-image-1215\" style=\"width:179px;height:auto\" srcset=\"https:\/\/www.vatland.no\/wp-content\/uploads\/2024\/01\/w3-768x1024.jpg 768w, https:\/\/www.vatland.no\/wp-content\/uploads\/2024\/01\/w3-225x300.jpg 225w, https:\/\/www.vatland.no\/wp-content\/uploads\/2024\/01\/w3-rotated.jpg 1024w\" sizes=\"auto, (max-width: 768px) 100vw, 768px\" \/><\/a><\/figure>\n\n\n\n<p>Since Plaato is retiring their Keg scale in 2024\/25 I had to find a new solution. Searching the web lead me to <a href=\"https:\/\/github.com\/Callwater\/Beerkeg-load-cell\">GitHub &#8211; Callwater\/Beerkeg-load-cell<\/a>. Since I had some ESP and loadcells at my hands it was very quick to setup. Had some wood and a router in my &#8216;workshop&#8217; so made the base in a short time. Soldering all parts together and gluing them to the base. <\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><a href=\"https:\/\/www.vatland.no\/wp-content\/uploads\/2024\/01\/w2-rotated.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"768\" height=\"1024\" data-attachment-id=\"1214\" data-permalink=\"https:\/\/www.vatland.no\/w2\/\" data-orig-file=\"https:\/\/www.vatland.no\/wp-content\/uploads\/2024\/01\/w2-rotated.jpg\" data-orig-size=\"1024,1365\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;1703512065&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;1&quot;}\" data-image-title=\"Base\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.vatland.no\/wp-content\/uploads\/2024\/01\/w2-768x1024.jpg\" src=\"https:\/\/www.vatland.no\/wp-content\/uploads\/2024\/01\/w2-768x1024.jpg\" alt=\"\" class=\"wp-image-1214\" style=\"width:243px;height:auto\" srcset=\"https:\/\/www.vatland.no\/wp-content\/uploads\/2024\/01\/w2-768x1024.jpg 768w, https:\/\/www.vatland.no\/wp-content\/uploads\/2024\/01\/w2-225x300.jpg 225w, https:\/\/www.vatland.no\/wp-content\/uploads\/2024\/01\/w2-rotated.jpg 1024w\" sizes=\"auto, (max-width: 768px) 100vw, 768px\" \/><\/a><\/figure>\n\n\n\n<p>The basic hardware and software was quickly up and running. <\/p>\n\n\n\n<p>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 &#8211; esp8266 it was nearly a walk in the park.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/www.vatland.no\/wp-content\/uploads\/2023\/12\/Screenshot-2023-12-25-134658.png\"><img loading=\"lazy\" decoding=\"async\" width=\"657\" height=\"290\" data-attachment-id=\"1211\" data-permalink=\"https:\/\/www.vatland.no\/screenshot-2023-12-25-134658\/\" data-orig-file=\"https:\/\/www.vatland.no\/wp-content\/uploads\/2023\/12\/Screenshot-2023-12-25-134658.png\" data-orig-size=\"657,290\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"HA\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/www.vatland.no\/wp-content\/uploads\/2023\/12\/Screenshot-2023-12-25-134658.png\" src=\"https:\/\/www.vatland.no\/wp-content\/uploads\/2023\/12\/Screenshot-2023-12-25-134658.png\" alt=\"\" class=\"wp-image-1211\" srcset=\"https:\/\/www.vatland.no\/wp-content\/uploads\/2023\/12\/Screenshot-2023-12-25-134658.png 657w, https:\/\/www.vatland.no\/wp-content\/uploads\/2023\/12\/Screenshot-2023-12-25-134658-300x132.png 300w\" sizes=\"auto, (max-width: 657px) 100vw, 657px\" \/><\/a><\/figure>\n\n\n\n<p>Code was added using Visual Studio Code.<\/p>\n\n\n\n<p>File : main.cpp<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n#include &lt;Arduino.h&gt;\n#include &lt;DHT.h&gt;\n#include &lt;DHT_U.h&gt;\n#include &lt;HX711.h&gt;\n#include &lt;ESP8266WiFi.h&gt;\n#include &lt;PubSubClient.h&gt;\n#include &lt;SPI.h&gt;\n#include &lt;Wire.h&gt;\n#include &lt;Adafruit_GFX.h&gt;\n#include &lt;Adafruit_SSD1306.h&gt;\n\n#define SCREEN_WIDTH 128 \/\/ OLED display width, in pixels\n#define SCREEN_HEIGHT 64 \/\/ OLED display height, in pixels\n\n#include &quot;config.h&quot;\n#define OLED_RESET -1       \/\/ Reset pin # (or -1 if sharing Arduino reset pin)\n#define SCREEN_ADDRESS 0x3c \/\/\/&lt; See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32\nAdafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &amp;Wire, OLED_RESET);\n\nDHT dht{DHT_PIN, DHT_TYPE};      \/\/ Initiate DHT library\nHX711 scale;                     \/\/ Initiate HX711 library\nWiFiClient wifiClient;           \/\/ Initiate WiFi library\nPubSubClient client(wifiClient); \/\/ Initiate PubSubClient library\nint publishcount = 10;           \/\/ Do 10 mqtt publishes - then force reconnect\nbool displayconnected = true;\nfloat prevmeter = 1000.0;\nint screentimeout = 10;\nvoid reconnect();\nvoid callback(char *topic, byte *payload, unsigned int length);\nvoid drawText(float temperature, float volume, float humidity, String Message);\nvoid drawMessage(String message);\nvoid screenoff();\nvoid setup()\n{\n  Serial.begin(74880);\n  Serial.println();\n  WiFi.mode(WIFI_STA);\n  WiFi.begin(SSID, PASSWORD);\n  Serial.print(&quot;Connecting...&quot;);\n  while (WiFi.status() != WL_CONNECTED)\n  { \/\/ Wait till Wifi connected\n    delay(500);\n    Serial.print(&quot;.&quot;);\n  }\n  Serial.println();\n\n  Serial.print(&quot;Connected.IP address:&quot;);\n  Serial.println(WiFi.localIP()); \/\/ Print IP address\n  delay(2000);\n  \/\/ SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally\n  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS))\n  {\n    Serial.println(F(&quot;SSD1306 allocation failed&quot;));\n    \/\/ for(;;); \/\/ Don&#039;t proceed, loop forever\n    displayconnected = false;\n  }\n  else\n  {\n    Serial.println(F(&quot;SSD1306 allocation success&quot;));\n    displayconnected = true;\n    display.clearDisplay();\n    display.display();\n  };\n\n  if (displayconnected)\n  {\n    drawMessage(&quot;Connected IP address:&quot;);\n    delay(2000);\n    drawMessage(WiFi.localIP().toString());\n    delay(2000);\n  }\n  client.setServer(MQTT_SERVER, 1883);              \/\/ Set MQTT server and port number\n  client.setCallback(callback);                     \/\/ Set callback address, this is used for remote tare\n  scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); \/\/ Start scale on specified pins\n  scale.wait_ready();                               \/\/ Ensure scale is ready, this is a blocking function\n  scale.set_scale();\n  Serial.println(&quot;Scale Set&quot;);\n  scale.wait_ready();\n  scale.tare(); \/\/ Tare scale on startup\n  scale.wait_ready();\n  Serial.println(&quot;Scale Zeroed&quot;);\n\n  Serial.println(&quot;Start DHT library&quot;);\n  dht.begin();\n}\n\nvoid loop()\n{\n  float reading;                       \/\/ Float for reading\n  float raw;                           \/\/ Float for raw value which can be useful\n  scale.wait_ready();                  \/\/ Wait till scale is ready, this is blocking if your hardware is not connected properly.\n  scale.set_scale(calibration_factor); \/\/ Sets the calibration factor.\n  scale.set_offset(offset_factor);     \/\/ Sets offset for empty keg\n  if (publishcount &lt; 1)\n  {\n    \/\/ reconnect();\n    client.disconnect();\n    publishcount = 10;\n    \/\/ Serial.println(&quot;Scheduled reconnect.&quot;);\n  }\n  publishcount--;\n\n  \/\/ Ensure we are still connected to MQTT Topics\n  if (!client.connected())\n  {\n    reconnect();\n  }\n\n  Serial.print(&quot;Reading: &quot;); \/\/ Prints weight readings in .2 decimal kg units.\n  scale.wait_ready();\n  reading = scale.get_units(10); \/\/ Read scale in g\/Kg\n  raw = scale.read_average(5);   \/\/ Read raw value from scale too\n  Serial.print(reading, 1);\n  Serial.println(&quot; L&quot;);\n  Serial.print(&quot;Raw: &quot;);\n  Serial.println(raw);\n  Serial.print(&quot;Calibration factor: &quot;); \/\/ Prints calibration factor.\n  Serial.println(calibration_factor);\n\n  if (reading &lt; 0)\n  {\n    reading = 0.00; \/\/ Sets reading to 0 if it is a negative value, sometimes loadcells will drift into slightly negative values\n  }\n\n  String value_str = String(reading);\n  String value_raw_str = String(raw);\n  client.publish(STATE_TOPIC, value_str.c_str());         \/\/ Publish weight to the STATE topic\n  client.publish(STATE_RAW_TOPIC, value_raw_str.c_str()); \/\/ Publish raw value to the RAW topic\n\n  client.loop();      \/\/ MQTT task loop\n  scale.power_down(); \/\/ Puts the scale to sleep mode for 3 seconds. I had issues getting readings if I did not do this\n  delay(3000);\n  scale.power_up();\n\n  \/\/ Reading values from the DHT sensor\n  float humidity = dht.readHumidity();       \/\/ Read humidity\n  float temperature = dht.readTemperature(); \/\/ Read temperature\n\n  \/\/ Check if we recieved a number from dht libraray\n  if (isnan(humidity) || isnan(temperature))\n  {\n    Serial.println(&quot;Error while parse values to numbers from dht&quot;);\n    drawMessage(&quot;Errir reading DHT&quot;);\n    delay(2000);\n  }\n\n  client.publish(TEMPERATURE_TOPIC, String(temperature).c_str()); \/\/ Publish temperature to the temperature value topic\n  client.publish(HUMIDITY_TOPIC, String(humidity).c_str());       \/\/ Publish humidity to the humidity value topic\n  Serial.print(&quot;Temp:&quot;);\n  Serial.println(temperature);\n  Serial.print(&quot;Displayconnected:&quot;);\n  Serial.println(displayconnected);\n  Serial.print(&quot;Prev:&quot;);\n  Serial.println(prevmeter);\n  Serial.print(&quot;Reading2:&quot;);\n  Serial.println(reading);\n    Serial.print(&quot;Screentimeout:&quot;);\n    Serial.println(screentimeout);\n\n  if (displayconnected)\n  {\n    if ((reading &lt; (prevmeter - 0.1)) || (reading &gt; (prevmeter + 0.1)) ||(prevmeter == 1000))\n    {\n      prevmeter = reading;\n      screentimeout = 10;\n    }\n          screentimeout--;\n      if (screentimeout &lt; 1)\n      {\n        screentimeout = 1;\n        screenoff();\n      }\n    Serial.print(&quot;Minvalue:&quot;);\n    Serial.println((prevmeter - 0.1));\n   \n    if (screentimeout &gt; 1)\n    {\n      Serial.println(&quot;Print to display&quot;);\n      drawText(temperature, reading, humidity, &quot;beer remaining !!!&quot;);\n    }\n  }\n}\n\nvoid reconnect()\n{\n  while (!client.connected())\n  { \/\/ Loop until connected to MQTT server\n    Serial.print(&quot;Attempting MQTT connection...&quot;);\n    if (client.connect(HOSTNAME, mqtt_username, mqtt_password))\n    { \/\/ Connect to MQTT server\n      Serial.println(&quot;connected&quot;);\n      client.publish(AVAILABILITY_TOPIC, &quot;online&quot;); \/\/ Once connected, publish online to the availability topic\n      client.subscribe(TARE_TOPIC);                 \/\/ Subscribe to tare topic for remote tare\n      client.subscribe(OFFSET_TOPIC);\n    }\n    else\n    {\n      Serial.print(&quot;failed, rc=&quot;);\n      Serial.print(client.state());\n      Serial.println(&quot; try again in 5 seconds&quot;);\n      delay(5000); \/\/ Will attempt connection again in 5 seconds\n    }\n  }\n}\n\nvoid callback(char *topic, byte *payload, unsigned int length)\n{\n  if (strcmp(topic, TARE_TOPIC) == 0)\n  {\n    Serial.println(&quot;starting tare...&quot;);\n    scale.wait_ready();\n    scale.set_scale();\n    scale.tare(); \/\/ Reset scale to zero\n    Serial.println(&quot;Scale reset to zero&quot;);\n  }\n  if (strcmp(topic, OFFSET_TOPIC) == 0)\n  {\n    Serial.println(&quot;Setting offset...&quot;);\n    scale.wait_ready();\n    scale.set_scale();\n    scale.set_offset(offset_factor);\n    Serial.print(&quot;Scale set to offset &quot;);\n    Serial.println(*payload);\n  }\n}\nvoid drawText(float temperature, float volume, float humidity, String message)\n{\n  display.clearDisplay();\n  display.setCursor(0, 0); \/\/ Start at top-left corner\n  display.setTextSize(1);  \/\/ Draw 2X-scale text\n  display.setTextColor(SSD1306_WHITE);\n  display.print(&quot;T: &quot;);\n  display.print(temperature);\n  display.print((char)247);\n  display.println(F(&quot;C&quot;));\n  display.print(&quot;H: &quot;);\n  display.print(humidity);\n  display.println(F(&quot;%&quot;));\n  display.setTextSize(3); \/\/ Draw 2X-scale text\n  display.print(volume);\n  display.println(F(&quot;L&quot;));\n  display.setTextSize(1); \/\/ Draw 2X-scale text\n  display.println(message);\n\n  display.display();\n  \/\/ delay(2000);\n}\nvoid drawMessage(String message)\n{\n  display.clearDisplay();\n  display.setCursor(0, 0); \/\/ Start at top-left corner\n  display.setTextSize(1);  \/\/ Draw 2X-scale text\n  display.setTextColor(SSD1306_WHITE);\n  display.println(message);\n  display.display();\n  \/\/ delay(2000);\n}\n\nvoid screenoff()\n{\n  display.clearDisplay();\n  display.display();\n}\n\n<\/pre><\/div>\n\n\n<p>File : config.h<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\n\/\/ Wifi Settings\n#define SSID                          &quot;SSID&quot;\n#define PASSWORD                      &quot;WIFIPWD&quot;\n\n\/\/ MQTT Settings\n#define HOSTNAME                      &quot;HA hostname&quot; \/\/&quot;beer_1&quot;\n#define MQTT_SERVER                   &quot;192.168.10.132&quot;\n#define STATE_TOPIC                   &quot;beer_1&quot;\n#define STATE_RAW_TOPIC               &quot;beer_1\/raw&quot;\n#define AVAILABILITY_TOPIC            &quot;beer_1\/available&quot;\n#define TARE_TOPIC                    &quot;beer_1\/tare&quot;\n#define OFFSET_TOPIC                  &quot;beer_1\/offset&quot;\n#define TEMPERATURE_TOPIC             &quot;beer_1\/temperature&quot;\n#define HUMIDITY_TOPIC                &quot;beer_1\/humidity&quot;\n#define mqtt_username                 &quot;mqttusername&quot;\n#define mqtt_password                 &quot;mqttpassword&quot;\n\n\/\/ HX711 Pins\nconst int LOADCELL_DOUT_PIN = 12;  \/\/ Remember these are ESP GPIO pins, they are not the physical pins on the board.\nconst int LOADCELL_SCK_PIN = 13;\nint calibration_factor = -22500;  \/\/ Defines calibration factor we&#039;ll use for calibrating.\nint offset_factor = -285746;       \/\/ Defines offset factor ; -285746 = Empty Cornelius 19L keg. \n\n\/\/ DHT Settings\nconstexpr auto DHT_PIN = 14;            \/\/ Remember these are ESP GPIO pins, they are not the physical pins on the board. \n#define DHT_TYPE DHT11                 \/\/ DHT11 or DHT22\nconstexpr auto sendDHTDataDelay = 500ul; \/\/ Delay between sending data over MQTT\n\n<\/pre><\/div>","protected":false},"excerpt":{"rendered":"<p>Since Plaato is retiring their Keg scale in 2024\/25 I had to find a new solution. Searching the web lead me to GitHub &#8211; 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 &#8216;workshop&#8217; so made the base in &hellip; <a href=\"https:\/\/www.vatland.no\/index.php\/my-simple-keg-scale\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">My &#8216;simple&#8217; keg scale<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[1],"tags":[],"class_list":["post-1238","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"aioseo_notices":[],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_likes_enabled":false,"jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/www.vatland.no\/index.php\/wp-json\/wp\/v2\/posts\/1238","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.vatland.no\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.vatland.no\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.vatland.no\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.vatland.no\/index.php\/wp-json\/wp\/v2\/comments?post=1238"}],"version-history":[{"count":4,"href":"https:\/\/www.vatland.no\/index.php\/wp-json\/wp\/v2\/posts\/1238\/revisions"}],"predecessor-version":[{"id":1242,"href":"https:\/\/www.vatland.no\/index.php\/wp-json\/wp\/v2\/posts\/1238\/revisions\/1242"}],"wp:attachment":[{"href":"https:\/\/www.vatland.no\/index.php\/wp-json\/wp\/v2\/media?parent=1238"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.vatland.no\/index.php\/wp-json\/wp\/v2\/categories?post=1238"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.vatland.no\/index.php\/wp-json\/wp\/v2\/tags?post=1238"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}