As I talked about in my last post about the FilaWeigher, I wanted to add MQTT and temperature/humidity(Using the BME280) to that project. Well, I’ve done that. Refer to the previous post for more details. But now for this one, I’m just going to focus on the updated code and schematics, and the Home Assistant integration via MQTT. I’m going to leave the old post up for the people that don’t want MQTT or the BME280 sensor. This version also has JSON output of all the sensors which allows for OctoPrint integration(I actually just finished up that plugin and I’m going to be posting that up soon).
Anyways, let’s get started.
Here’s what you will need:
- A spool holder for the load cell. Here’s the design I made. It should work with the Creality Printers, and probably some others that have a hole of the same size for the spool holder bracket. This search on Thingiverse might give you some more options that will work with your printer.
- An HX711 loadcell amplifier with loadcell – Amazon | AliExpress – I would recommend getting at least the 5kg version. I went with 10kg.
- Wemos D1 Mini – Amazon | AliExpress
- Dupont Jumper Wires – Amazon | AliExpress
- An object with a known weight. You want this to be accurate to the gram or better. This is what will calibrate the scale. I used the kitchen scale that I already had. It has a resolution of 1g. There are also scales with a resolution of .01g. But those higher accuracy scales hold much less weight, so they wouldn’t be very practical to own unless you are a drug dealer. You can also use common coins to calibrate. Here are the weights of many US coins. You may want to use a bunch of them.
- Soldering Iron – Amazon
- Install Arduino IDE
Here’s the schematic:
And here’s the updated code. You can access http://ip_address/json to get the sensor data in JSON format. I am going to use that in the OctoPrint plugin to pull the data into OctoPrint. It also has new settings for MQTT. So you can specify the MQTT broker to send that data to.
//One enables easy setup of Wifi and config settings, and the second is for the HX711 based weight sensor #include <IotWebConf.h> #include "HX711.h" #include <PubSubClient.h> //MQTT #include <Adafruit_BME280.h> #include <ArduinoJson.h> //******User Config Settings****** //You don't need to change the name or password here if you don't want to. //They can be changed later through the webUI //The last 2 settings set the pins for the weight sensor. If you use those 2 pins on a Wemos D1 Mini, //you don't need to change it. If you are using a different ESP8266 board, then you will need to change it const char thingName[] = "FilamentWeight"; //Initial Name const char wifiInitialApPassword[] = "ESP8266"; //Initial WiFi Password const int LOADCELL_DOUT_PIN = D3; const int LOADCELL_SCK_PIN = D4; //******End User Config****** #define STRING_LEN 128 #define NUMBER_LEN 32 #define SEALEVELPRESSURE_HPA (1013.25) HX711 loadcell; Adafruit_BME280 bme; // I2C //These 2 variables are used to control how often MQTT updates are sent. This sets the default to 1 minutes. //It can be configured in the settings page. unsigned long delayTime = 60000; long lastMsg = 0; //These are 2 functions towards the end of this code. ConfigSaved runs any time the config is saved. //formValidator can validate the inputs on the config page. I'm not using it, but left it there with the example //commented out in case you want to use it. void configSaved(); boolean formValidator(); //define the web server. DNSServer dnsServer; WebServer server(80); WiFiClient espClient; PubSubClient client(espClient); //MQTT client //These are the values that are set on the config page. char knownWeightValue[NUMBER_LEN]; char loadcellDividerValue[NUMBER_LEN]; char tareOffsetValue[NUMBER_LEN]; char spoolOffsetValue[NUMBER_LEN]; char weightIncreaseIgnoreValue[NUMBER_LEN];//the amount of the weight increase to ignore. char mqttServerValue[STRING_LEN]; char mqttUpdateIntervalValue[NUMBER_LEN]; char mqttUserValue[STRING_LEN]; char mqttPassValue[STRING_LEN]; char mqttTopicValue[STRING_LEN]; //Variables to figure out the weight of the filament //I am trying to get logic in here to ignore readings where the filament weight goes up by less than X grams. //The reason for this is that when the printer is printing, it will pull on the filament spool, which will put a little bit more //force on the loadcell. This will help account for that, and ignore any times where the weight goes up. float sensorWeight; //the weight on the sensor float filamentWeight; //the weight of the filament after accounting for the printer pulling and the spool offset float temperature; float humidity; float pressure; //float tempWeight; //temp variable to hold the current reading //initialize the web config object and the various custom settings we will need. IotWebConf iotWebConf(thingName, &dnsServer, &server, wifiInitialApPassword,"mqt1"); IotWebConfSeparator separator2 = IotWebConfSeparator("Calibration factor"); IotWebConfParameter knownWeight = IotWebConfParameter("Known Weight (g)", "knownWeight", knownWeightValue, NUMBER_LEN, "number", "e.g. 23.4", NULL, "step='0.01'"); IotWebConfParameter loadcellDivider = IotWebConfParameter("Loadcell Divider", "loadcellDivider", loadcellDividerValue, NUMBER_LEN, "number", "Divider", NULL, "step='0.0000001'"); IotWebConfParameter tareOffset = IotWebConfParameter("Tare Offset", "tareOffset", tareOffsetValue, NUMBER_LEN, "number", "e.g. 23.4", NULL, "step='0.01'"); IotWebConfParameter spoolOffset = IotWebConfParameter("Spool Offset (g)", "spoolOffset", spoolOffsetValue, NUMBER_LEN, "number", "e.g. 23.4", NULL, "step='0.01'"); IotWebConfParameter weightIncreaseIgnore = IotWebConfParameter("Weight increase amount to ignore(g)", "weightIncreaseIgnore", weightIncreaseIgnoreValue, NUMBER_LEN, "number", "e.g. 23.4", NULL, "step='0.01'"); IotWebConfParameter mqttServer = IotWebConfParameter("MQTT Server IP)", "mqttServer", mqttServerValue, STRING_LEN); IotWebConfParameter mqttUpdateInterval = IotWebConfParameter("MQTT Update Interval(ms)", "mqttUpdateInterval", mqttUpdateIntervalValue, NUMBER_LEN, "number", "e.g. 60000 for every minute", NULL, "step='1'"); IotWebConfParameter mqttUser = IotWebConfParameter("MQTT Username", "mqttUser", mqttUserValue, STRING_LEN); IotWebConfParameter mqttPass = IotWebConfParameter("MQTT Password", "mqttPass", mqttPassValue, STRING_LEN); IotWebConfParameter mqttTopic= IotWebConfParameter("MQTT Topic", "mqttTopic", mqttTopicValue, STRING_LEN); //initialize the update server which will let you push any updates for this code via the web interface. HTTPUpdateServer httpUpdater; //This setup() code runs once when the ESP starts up. void setup() { Serial.begin(115200); Serial.println(); Serial.println("Starting up..."); iotWebConf.setupUpdateServer(&httpUpdater); //add the parameters to the web config object. iotWebConf.addParameter(&separator2); iotWebConf.addParameter(&knownWeight); iotWebConf.addParameter(&loadcellDivider); iotWebConf.addParameter(&tareOffset); iotWebConf.addParameter(&spoolOffset); iotWebConf.addParameter(&weightIncreaseIgnore); iotWebConf.addParameter(&mqttServer); iotWebConf.addParameter(&mqttUpdateInterval); iotWebConf.addParameter(&mqttUser); iotWebConf.addParameter(&mqttPass); iotWebConf.addParameter(&mqttTopic); //set the functinos to call for config being saved and validating the config options(this isn't being used) iotWebConf.setConfigSavedCallback(&configSaved); iotWebConf.setFormValidator(&formValidator); iotWebConf.getApTimeoutParameter()->visible = true; //don't run the AP if you already have the WiFi settings. Speeds up startup by a lot. iotWebConf.skipApStartup(); //start up the web config iotWebConf.init(); // -- Initializing the configuration. bool status; status = bme.begin(0x76); if (!status) { Serial.println("Could not find a valid BME280 sensor, check wiring!"); while (1); } //start up the loadcell loadcell.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); loadcell.set_scale(strtof(loadcellDividerValue, NULL)); loadcell.set_offset(strtof(tareOffsetValue, NULL)); delay(250); //Get the first weight reading if (loadcell.wait_ready_timeout(1000)) { filamentWeight = loadcell.get_units(20) - strtof(spoolOffsetValue,NULL); Serial.println(filamentWeight); sensorWeight = loadcell.get_units(10); Serial.println(sensorWeight); } else { Serial.println("HX711 not found."); } // -- Set up required URL handlers on the web server. //This tells it which funtion to run (The second parameter) when the first parameter is called by the web browser server.on("/", handleRoot); server.on("/weight", handleWeight); server.on("/calWiz", handleCalWiz); server.on("/calWiz2", handleCalWiz2); server.on("/calWiz3", handleCalWiz3); server.on("/tare", handleTare); server.on("/json", handleJSON); server.on("/tare2", handleTare2); server.on("/config", []{ iotWebConf.handleConfig(); }); server.onNotFound([](){ iotWebConf.handleNotFound(); }); client.setServer(mqttServerValue, 1883); if(mqttUpdateIntervalValue[0] != '\0'){ delayTime = strtof(mqttUpdateIntervalValue, NULL); } } void loop() { //take a load cell reading if (loadcell.wait_ready_timeout(1000)) { sensorWeight = loadcell.get_units(10); } else { Serial.println("HX711 not found."); } //if the weight went up more than weightIncreaseIgnore value or went down, then update filament weight with the new value //otherwise, just leave it the same. if (sensorWeight > (filamentWeight + strtof(spoolOffsetValue,NULL) + strtof(weightIncreaseIgnoreValue,NULL)) || sensorWeight < filamentWeight + strtof(spoolOffsetValue,NULL)){ filamentWeight = sensorWeight - strtof(spoolOffsetValue,NULL); Serial.println("Changed."); } else Serial.println("Not Changed."); iotWebConf.doLoop(); long now = millis(); if (now - lastMsg > delayTime) { lastMsg = now; if(mqttServerValue[0] == '\0') { Serial.println("MQTT Not configured."); } else{ client.loop(); if (!client.connected()) { reconnect(); } } printValues(); publishValues(); } } void publishValues() { char temp_buff[8]; char tempTopic[STRING_LEN]; String pubStringTemp; strcpy(tempTopic, mqttTopicValue); strcat(tempTopic , "/weight"); pubStringTemp = String(filamentWeight); pubStringTemp.toCharArray(temp_buff, pubStringTemp.length()+1); client.publish( tempTopic , temp_buff); strcpy(tempTopic, mqttTopicValue); strcat(tempTopic , "/temperature"); pubStringTemp = String(temperature); pubStringTemp.toCharArray(temp_buff, pubStringTemp.length()+1); client.publish( tempTopic , temp_buff); strcpy(tempTopic, mqttTopicValue); strcat(tempTopic , "/humidity"); pubStringTemp = String(humidity); pubStringTemp.toCharArray(temp_buff, pubStringTemp.length()+1); client.publish( tempTopic , temp_buff); strcpy(tempTopic, mqttTopicValue); strcat(tempTopic , "/pressure"); pubStringTemp = String(pressure); pubStringTemp.toCharArray(temp_buff, pubStringTemp.length()+1); client.publish( tempTopic , temp_buff); } void reconnect() { //Reconnect to Wifi and to MQTT. If Wifi is already connected, then autoconnect doesn't do anything. Serial.print("Attempting MQTT connection..."); if (client.connect(thingName, mqttUserValue, mqttPassValue)) { Serial.println("connected"); } else { Serial.print("failed, rc="); Serial.print(client.state()); Serial.println(" try again later"); } } void handleRoot() //Handles the Root of the web server { // -- Let IotWebConf test and handle captive portal requests. if (iotWebConf.handleCaptivePortal()) { // -- Captive portal request were already served. return; } //create a string with the html for the page. It contains the weight, and links to the settings, calibration wiz, and scale tare String s = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>"; s += "<title>Filament Weight</title></head><body>"; s += "Filament Remaining: "; s += String(filamentWeight, 2); s += "g left<br>"; s += "Temperature: "; s += String(temperature, 2); s += "C<br>"; s += "Humidity:: "; s += String(humidity, 2); s += "%<br>"; s += "Pressure: "; s += String(pressure, 2); s += "<br>"; s += "<a href='config'>Settings</a><br>"; s += "<a href='calWiz'>Calibration Wizard</a><br>"; s += "<a href='tare'>Tare</a><br>"; s += "<br><br><footer><a href='http://automatedhome.party'>Automatedhome.party</a></footer></body></html>\n"; //send the html string server.send(200, "text/html", s); } void handleWeight() { //create a string with the html for the page. It contains only the weight. This might be useful for something like //Octoprint to capture the weight. Would require a plugin for OP, which doesn't exist at the time I wrote this code. String s = String(filamentWeight, 2); server.send(200, "text/html", s); } void handleJSON() { //return text with temp, pressure, humidity, and weight in JSON format StaticJsonDocument<200> root; root["weight"] = (String)filamentWeight; root["temperature"] = (String)temperature; root["humidity"] = (String)humidity; root["pressure"] = (String)pressure; String output; serializeJson(root, output); server.send(200, "text/html", output); } void handleCalWiz() { //First page of the calibration wize String s = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>"; s += "<title>Filament Weight - Calibration Wizard</title></head><body>"; s += "This wizard will walk you through calibrating the scale. "; s += "You will need a weight, with a known weight. The easiest way to get that is by using cheap digital kitchen scale. "; s += "You need to enter that known weight into the config page and restart this wizard. <br><br> Your known weight is: "; s += knownWeightValue; s += "g. If it is set to 0, then change it because that will cause a divide by zero error."; s += "When ready, take everything off the scale and press next. <br><br>"; s += "<a href='calWiz2'>NEXT</a><br>"; s += "<br><br><footer><a href='http://automatedhome.party'>Automatedhome.party</a></footer></body></html>\n"; server.send(200, "text/html", s); } void handleCalWiz2() { //Second page of the cal wiz. First it tares the scale Serial.println("Set Scale"); loadcell.set_scale(); Serial.println("Tare"); loadcell.tare(); String s = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>"; s += "<title>Filament Weight - Calibration Wizard</title></head><body>"; s += "Put your known weight on the scale, then wait a couple seconds and press next. "; s += "If your weight is swingning or anything like that, then wait for it to be still before clicking next. <br>"; s += "<a href='calWiz3'>NEXT</a><br>"; s += "<br><br><footer><a href='http://automatedhome.party'>Automatedhome.party</a></body></html>\n"; server.send(200, "text/html", s); } void handleCalWiz3() { //third page of the cal wiz. It makes sure that the known weight that is used to calibrate is greater than 0. Otherwise, it won't work. String s = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>"; s += "<title>Filament Weight - Calibration Wizard</title></head><body>"; if (strtof(knownWeightValue, NULL) > 0){ snprintf (loadcellDividerValue , sizeof loadcellDividerValue, "%f", loadcell.get_units(10) / strtof(knownWeightValue, NULL)) ; s += loadcellDividerValue; s += "<br>"; s += "<a href='/'>FINISH</a><br>"; s += "<br><br><footer><a href='http://automatedhome.party'>Automatedhome.party</a></body></html>\n"; } else { s += "Your known weight cannot be zero. Go to settings and fix that"; s += "<a href='config'>Settings</a><br>"; s += "<br><br><footer><a href='http://automatedhome.party'>Automatedhome.party</a></body></html>\n"; } server.send(200, "text/html", s); iotWebConf.configSave(); } void handleTare() { //Page 1 for Tare String s = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>"; s += "<title>Filament Weight</title></head><body>"; s += "Take all the weight off the scale, and then click <a href='tare2'>TARE</a>"; s += "<br><br><footer><a href='http://automatedhome.party'>Automatedhome.party</a></footer></body></html>\n"; server.send(200, "text/html", s); } void handleTare2() { //page 2 for Tare. This one actually tares. Then it gets the Tare offset and updates the config settings for it. loadcell.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN); loadcell.tare(); Serial.println(loadcell.get_offset()); float temp = loadcell.get_offset(); snprintf(tareOffsetValue, sizeof tareOffsetValue, "%f", temp, NULL); String s = "<!DOCTYPE html><html lang=\"en\"><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>"; s += "<title>Filament Weight</title></head><body>"; s += "Your offset value is now "; s += tareOffsetValue; s += "<br><a href='/'>Home</a>"; s += "<br><br><footer><a href='http://automatedhome.party'>Automatedhome.party</a></footer></body></html>\n"; server.send(200, "text/html", s); iotWebConf.configSave(); } void configSaved() { //re-apply loadcell settings whenever the config page is saved. Serial.println("Configuration was updated."); loadcell.set_scale(strtof(loadcellDividerValue, NULL)); loadcell.set_offset(strtof(tareOffsetValue, NULL)); if(mqttUpdateIntervalValue[0] != '\0'){ delayTime = strtof(mqttUpdateIntervalValue, NULL); } } boolean formValidator() { //I'm not using this at the time. There's not a lot of settings, and I don't feel it's that hard to really mess them up. But it's here with an example. //In this example, stringParam would be an IotWebConfParameter object that doesn't exist in this code. But you can do the same stuff //for any of the other IotWebConfParameter objects. Serial.println("Validating form."); boolean valid = true; // // int l = server.arg(stringParam.getId()).length(); // if (l < 3) // { // stringParam.errorMessage = "Please provide at least 3 characters for this test!"; // valid = false; // } return valid; } void printValues() { Serial.print("Temperature = "); Serial.print(bme.readTemperature()); Serial.println(" *C"); // Convert temperature to Fahrenheit Serial.print("Temperature = "); Serial.print(1.8 * bme.readTemperature() + 32); Serial.println(" *F"); temperature = bme.readTemperature(); Serial.print("Pressure = "); Serial.print(bme.readPressure() / 100.0F); Serial.println(" hPa"); pressure = bme.readPressure(); Serial.print("Approx. Altitude = "); Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA)); Serial.println(" m"); Serial.print("Humidity = "); Serial.print(bme.readHumidity()); Serial.println(" %"); humidity = bme.readHumidity(); Serial.println(); }
Home Assistant YAML Code
Here’s the code I’m using in Home Assistant to pull this data in:
- platform: mqtt state_topic: "homeassistant/filaweigher/weight" unit_of_measurement: "g" name: "3DPrint Filament Weight" - platform: mqtt state_topic: "homeassistant/filaweigher/temperature" unit_of_measurement: "°C" name: "3DPrint Room Temperature" - platform: mqtt state_topic: "homeassistant/filaweigher/humidity" unit_of_measurement: "%" name: "3DPrint Humidity" - platform: mqtt state_topic: "homeassistant/filaweigher/pressure" unit_of_measurement: "Pa" name: "3DPrint Air Pressure"
Awesome job buddy!
You may want to mention in the walkthrough that the user name/password to get into the config after setup is;
u: admin
p: <AP password>
This wasn’t obvious to me and took some poking around before I figured it out.
I already say that in the original article. This particular post was just providing the updated code.
Would love to see a version with bme280 optional, the code from version 1 on the old page has some type of encoding problem and i can not copy & paste it in a way it works and i don’t have nor want to buy a bme280.
I could be wrong, but I thought I tested it without the BME, and it worked. Did you try it without that? Do you get any errors when you try it that way?