WiFi Scale (ESP-8266 MQTT Scale)

The picture above is of the finished product that has been functioning reliably for over a year!

This is a tutorial on the scale I made for my Raspberry Pi PLC for Membrane Filtration.

The idea of this scale came after my OpenCV flow meter began to stick due to the characteristics of the liquid we were filtering.

So in order to measure flow I decided to use a scale that would calculate the change in mass over time and using density I would then convert this to a volumetric flow rate. Since the range of flow I needed to measure was 0-50 mL/min a packaged flow sensor was out of the question.

All the flow sensors that were affordable were pulse based flow meters that had very high minimum flow rates and all the low flow rate flow meters were very pricey. Below I decided to include a comparison of a industrial low flow meter and my flow meter.

  ESP8266 HX711 MQTT Scale
Cole-Palmer Flowmeter   ESP8266+HX711+Scale
Measurement PrincipleLaminar Flow ElementDifferential Mass
Price (USD)$1470 (sensor)$21.99 (Scale)
$10.99 for 2 (ESP8266)
$8.99 for 5 (HX711)
Power7-30V DC4.5V to 5.5V
Output0-5V, 4-20mAI2C, SPI, UART, Serial (USB), MQTT
Customizable with Additional Hardware
Range0 – 50 mL/min0.83 uL/min (as programmed)  
Variably set by changing rate change duration and load cell.
Resolution0.01 mL/min2 min (programmed)
Response Time20 msecVariable dependent on resolution needed and load cell.
Measurement UnitVolumetricMass
Temperature CompensationY (only for water)No need, mass flowrate measured
Process Sensor
Type
InlineEnd of Line
Max Particulate
Size
20 umNo limit
EnclosureElectronic : Plastic IP40
Wetted: 302, 303, 316L, Viton
Modified Kitchen Scale
Process ConnectionsThreaded: 1/8″ FNPTBucket
Customer ServiceYYou’re on your own!
Looks ProfessionalYN
Likelihood to Get
Stolen
HighVery Low
Warranty1 YearNone

After looking at that table its pretty clear that each sensor has its own applications. For measuring the flow rate of water coming out of a filtration unit I didn’t need a inline meter which makes things a lot easier. Also we run various types of liquids through our unit with various viscosity, and particulate sizes, most measurement techniques, including laminar flow element, simply cannot be accurate in various process scenarios without an extensive amount of calibrating. Except the coriolis mass flowmeter, man is that thing a feat of engineering, I was going to try to build one with consumer electronics for this project but I’ll save that for another project.

What you will need.

Instead of building my own scale with a load cell and a platform mounted to a base, I decided to purchase a perfectly functioning scale within the weight range I needed.

I wanted the freedom to position the scale where ever I needed and decided to go with a ESP8266 brain with MQTT communication to my Pi.

For those that do not know what MQTT is, it stands for Message Queuing Telemetry Transport. It is a messaging protocol that works on top the TCP/IP protocol, so if your device is connected on the same LAN or to a MQTT internet broker you can receive and publish light weight messages. This is extremely useful for IoT communications.

Pre-requisites

Install a MQTT Broker

Install and run your MQTT Broker, this will be the medium in which your scale sends its data and how your receiver receives its data.

There are many MQTT brokers out there and below is a link to the one I used.

Download Mosquitto

After you download and run your broker, a MQTT server is now running on your host computer. The MQTT IP address will be the local IP address of the host computer and the port is defaulted to 1883

Install a MQTT Viewer

A MQTT viewer is the best way to debug and test the functionality of your MQTT messages to and from your device. I decided to use MQTT Spy. Below I have provided the git for MQTT-Spy.

MQTT-Spy Download

Installing the EPS8266 Arduino add on

We will be programming the ESP8266 with Arduino IDE. In order to program and ESP8266 you must first install the add on into IDE. I have included below a link to a tutorial on how to install the add on.

ESP8266 Arduino Installation Tutorial

Install the HX711 Library

Thankfully there are people out there to write libraries to communicate with the HX711. The library allows you to use simple commands to control and communicate with the HX711. Below is the library I used for this particular project.

HX711 Library Git

Wiring

HX711ESP8266_bb

Code

For this code I modified and merged a MQTT and HX711 example code. I opted to include some custom calibration functions and kept the origionl tare button’s functionality. Also when the scale is bring tared, the built in LED turns on so you have visual confirmation that the scale is being tared.

In the code the topic the ESP is subscibed to is “HX711cal”, you can change this to the topic of your choosing.

Here are some of the commands you can send to the ESP through the topic “HX711cal”

  • “tare”-turns on the built in LED, tares 5 times, and turns off the built in LED
  • “+0.1” adds 0.1 to the calibration factor
  • “-0.1” subtracts 0.1 from the calibration factor
  • “+1” adds 1 to the calibration factor
  • “-1” subtracts 1 from the calibration factor
  • “+10” adds 10 to the calibration factor
  • “-10” subtracts 10 from the calibration factor
  • “+100” adds 100 to the calibration factor
  • “-100” subtracts 100 from the calibration factor

The ESP publishes data from the HX711 to 3 different topics.

  • “Scale1” – the rounded weight
  • “Debug” – the full float value of the weight
  • “HX711calfactor” – the calibration factor for the HX711

You can also connect via serial to the ESP and in the serial monitor there should be a list of instructions on how to modify calibration constants and tare the scale. The baud rate of the serial communication is 115200.

Remember in order to connect the ESP8266 to the computer via USB you must first hold the FLASH button and then connect it to the computer.

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Update.h>
#include <HX711_ADC.h>
#include <PubSubClient.h>
char msg_buff[50];
const char* host = "esp32";
const char* ssid = "yourssid";
const char* password = "yourpassword";
const char* mqtt_server = "192.168.1.117";
float calibration_factor = 115.50; //115.50 worked for my scale ;
int switchValue;
//MQTT Setup
WiFiClient esp32Client;
PubSubClient client(esp32Client);
//HX711 Constructor (dout, sck):
HX711_ADC LoadCell(19, 18);
const int eepromAdress = 0;
long t;
WebServer server(80);
/*
Login page
*/
const char* loginIndex =
"<form name='loginForm'>"
"<table width='20%' bgcolor='A09F9F' align='center'>"
"<tr>"
"<td colspan=2>"
"<center><font size=4><b>ESP32 Login Page</b></font></center>"
"<br>"
"</td>"
"<br>"
"<br>"
"</tr>"
"<td>Username:</td>"
"<td><input type='text' size=25 name='userid'><br></td>"
"</tr>"
"<br>"
"<br>"
"<tr>"
"<td>Password:</td>"
"<td><input type='Password' size=25 name='pwd'><br></td>"
"<br>"
"<br>"
"</tr>"
"<tr>"
"<td><input type='submit' onclick='check(this.form)' value='Login'></td>"
"</tr>"
"</table>"
"</form>"
"<script>"
"function check(form)"
"{"
"if(form.userid.value=='admin' && form.pwd.value=='admin')"
"{"
"window.open('/serverIndex')"
"}"
"else"
"{"
" alert('Error Password or Username')/*displays error message*/"
"}"
"}"
"</script>";
/*
Server Index Page
*/
const char* serverIndex =
"<script src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>"
"<form method='POST' action='#' enctype='multipart/form-data' id='upload_form'>"
"<input type='file' name='update'>"
"<input type='submit' value='Update'>"
"</form>"
"<div id='prg'>progress: 0%</div>"
"<script>"
"$('form').submit(function(e){"
"e.preventDefault();"
"var form = $('#upload_form')[0];"
"var data = new FormData(form);"
" $.ajax({"
"url: '/update',"
"type: 'POST',"
"data: data,"
"contentType: false,"
"processData:false,"
"xhr: function() {"
"var xhr = new window.XMLHttpRequest();"
"xhr.upload.addEventListener('progress', function(evt) {"
"if (evt.lengthComputable) {"
"var per = evt.loaded / evt.total;"
"$('#prg').html('progress: ' + Math.round(per*100) + '%');"
"}"
"}, false);"
"return xhr;"
"},"
"success:function(d, s) {"
"console.log('success!')"
"},"
"error: function (a, b, c) {"
"}"
"});"
"});"
"</script>";
/*
setup function
*/
void setup() {
delay(100);
pinMode(BUILTIN_LED, OUTPUT);
pinMode(23, INPUT_PULLUP);
Serial.begin(115200);
Serial.println("HX711 Calibration");
setup_wifi();
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
scale_setup();
}
void setup_wifi() {
// Connect to WiFi network
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
/*use mdns for host name resolution*/
if (!MDNS.begin(host)) { //http://esp32.local
Serial.println("Error setting up MDNS responder!");
while (1) {
delay(1000);
}
}
Serial.println("mDNS responder started");
/*return index page which is stored in serverIndex */
server.on("/", HTTP_GET, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", loginIndex);
});
server.on("/serverIndex", HTTP_GET, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/html", serverIndex);
});
/*handling uploading firmware file */
server.on("/update", HTTP_POST, []() {
server.sendHeader("Connection", "close");
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
ESP.restart();
}, []() {
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
Serial.printf("Update: %s\n", upload.filename.c_str());
if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
/* flashing firmware to ESP*/
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_END) {
if (Update.end(true)) { //true to set the size to the current progress
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
} else {
Update.printError(Serial);
}
}
});
server.begin();
}
void scale_setup() {
client.subscribe("HX711cal1");
Serial.println("HX711 Calibration");
Serial.println("Remove all weight from scale");
Serial.println("After readings begin, place known weight on scale");
Serial.println("Press a,s,d,f to increase calibration factor by 10,100,1000,10000 respectively");
Serial.println("Press z,x,c,v to decrease calibration factor by 10,100,1000,10000 respectively");
Serial.println("Press t for tare");
LoadCell.begin();
long stabilisingtime = 2000; // tare preciscion can be improved by adding a few seconds of stabilising time
LoadCell.start(stabilisingtime);
if (LoadCell.getTareTimeoutFlag()) {
Serial.println("Tare timeout, check MCU>HX711 wiring and pin designations");
}
else {
LoadCell.setCalFactor(calibration_factor); // set calibration value (float)
Serial.println("Startup + tare is complete");
};
}
void callback(char* topic, byte* payload, unsigned int length) {
int i = 0;
Serial.println("Message arrived: topic: " + String(topic));
Serial.println("Length: " + String(length, DEC));
for (i = 0; i < length; i++) {
msg_buff[i] = payload[i];
}
msg_buff[i] = '\0';
String msgString = String(msg_buff);
Serial.println("Payload: " + msgString);
if (msgString == "tare") { // if there is a "1" published to any topic (#) on the broker then:
digitalWrite(BUILTIN_LED, LOW); // set pin to the opposite state
Serial.println("Switching LED");
LoadCell.tareNoDelay();
digitalWrite(BUILTIN_LED, HIGH);
}
else if (msgString == "+0.1")
calibration_factor += 0.1;
else if (msgString == "-0.1")
calibration_factor -= 0.1;
else if (msgString == "+1")
calibration_factor += 1;
else if (msgString == "-1")
calibration_factor -= 1;
else if (msgString == "+10")
calibration_factor += 10;
else if (msgString == "-10")
calibration_factor -= 10;
else if (msgString == "+100")
calibration_factor += 100;
else if (msgString == "-100")
calibration_factor -= 100;
}
void reconnect() {
while (!client.connected()) {
Serial.print("AtinByteting MQTT connection...");
if (client.connect("ESP8266Client")) {
Serial.println("connected");
client.subscribe("HX711cal1");
client.publish("IP", WiFi.localIP().toString().c_str(), true);
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void loop(void) {
if (!client.connected()) {
reconnect();
}
server.handleClient();
delay(1);
client.loop();
LoadCell.update();
//get smoothed value from data set
if (millis() > t + 250) {
float i = LoadCell.getData();
Serial.print("Load_cell output val: ");
Serial.println(i);
t = millis();
LoadCell.setCalFactor(calibration_factor);
client.publish("HX711calfactor1", String(calibration_factor).c_str(), true);
client.publish("Scale2",String(i).c_str(),true);
Serial.print(" calibration_factor: ");
Serial.print(calibration_factor);
Serial.println();
}
//receive from serial terminal
if (Serial.available() > 0) {
float i;
char inByte = Serial.read();
if (inByte == 't') LoadCell.tareNoDelay();
else if (inByte == '+' || inByte == 'a')
calibration_factor += 0.1;
else if (inByte == '-' || inByte == 'z')
calibration_factor -= 0.1;
else if (inByte == 's')
calibration_factor += 1;
else if (inByte == 'x')
calibration_factor -= 1;
else if (inByte == 'd')
calibration_factor += 10;
else if (inByte == 'c')
calibration_factor -= 10;
else if (inByte == 'f')
calibration_factor += 100;
else if (inByte == 'v')
calibration_factor -= 100;
}
//check if last tare operation is complete
if (LoadCell.getTareStatus() == true) {
Serial.println("Tare complete");
}
}

Update 12/19, code modified to include OTA update capabilities. More information can be found here.

Receiving the Messages and Calculating Flow

To do this I have a Raspberry Pi running NodeRed, see my other post, DIY Raspberry Pi PLC and Sensors for Process Data Collection and Control, for more information. The key nodes you will need are MQTT node, rate node, and UI nodes.

Node Red Scale

The key to calculating a rate is the rate node. As you can see above that the weight from my ESP8266 MQTT topic is limited to 1 message ever 2 minutes. This message then goes into a function node.

msg = {
payload: msg.payload,
timestamp: Date.now()
};
return msg;

In order for the rate node to work you must give it a reference time. Below are the settings for my rate node.

msg.payload is the instantaneous value I want to find the rate of change of. In this case its the scale weight.

global.flowrate is the outputted rate that is set as a global variable so all nodes can use it as a variable.

msg.timestamp is the time stamp I have added to the message with the above function node, this is so the node knows how much time has elapsed.

Rate period is set to 60000 miliseconds which will out put my rate with the units per minute.

ratenodesettings

Above you might have noticed the some UI nodes basically those are to send a certain messages to the MQTT topic HX711Cal.

Below is what the calibration UI looks like, it shows the current weight of the scale to 2 decimal points, the scale constant, and has buttons to tare the scale as well as add or decrease the calibration constant.

scale calibration UI

I will add a tutorial for how to configure nodered and UI later.

Related Posts

Ultrasonic Level Sensor for Water Level Measurement (Arduino Nano + HC-SRF05)

This is a level sensor I built for my Raspberry Pi PLC for Membrane Filtration. Here is a comparison of a SEIMENS SITRANS Ultrasonic level sensor and my…

Saving Files to Network Drive via NodeRED (Raspberry Pi)

Recently I needed to save a csv file to a network location in NodeRED. After searching various places on how to mount my drive I finally found…

Raspberry Pi Laboratory R&D Automation

How I built a custom PLC system and sensors for process data collection and control.

This Post Has 3 Comments

Leave a Reply