NOTE: I have updated this plugin. Please see the new blog post for the new code. The new code gives you a temperature, humidity, and pressure sensor along with MQTT. I am leaving this post as is for people that might not need those things.

UPDATED POST:https://automatedhome.party/2020/01/30/updated-filaweigher-filament-scale-now-with-temperature-humidity-pressure-and-mqtt/

I created this project so I can know exactly how much filament is left on a spool for my 3D printer, an Ender 5 I wrote about previously. However, it can very well be used to weigh anything else. I put a few settings that are specific to the filament, but they don’t have to be used.

I’ve seen similar setups using an HX711 load cell sensor directly connected to a Raspberry Pi running OctoPrint with a plugin to capture the data. However, I am running my OctoPrint directly on a Linux PC since it runs much better that way. However, that also means there are no GPIO pins. So I decided to pull some knowledge from my previous projects and use an ESP8266 based Wemos D1 Mini to get the readings. The advantage here is that it hardly costs anything and it doesn’t rely on anything else. It’s completely standalone.

Here’s what you will need:

Wiring the Loadcell sensor to the Wemos D1 Mini

The first thing to do is to solder the header pins onto the Wemos D1 Mini and the HX711. I directly soldered the wires from the Loadcell to the HX711. And used the pin header with dupont jumpers to go from the HX711 to the Wemos.

Connect everything based on the schematic below:

Load the Code Onto The Wemos D1 Mini

The next step is to load the code onto the Wemos D1 Mini using Arduino IDE. THIS PAGE has an excellent guide on setting up Arduino IDE for the Wemos D1 Mini. It takes about 5 minutes to do that. And while you are there adding the ESP8266 library into Arduino IDE, search for HX711 and install the one by Bogdan Necula.

Next, delete all the code that is in the Arduino IDE window, and paste the code below in there and then press the Upload button to compile it and send it to the Wemos. After loading the code you may want to do the WiFi setup portion of the “Configure the Settings on the Wemos” section of the article below. This way, you can use the serial monitor in Arduino to see the output of the code that tells you the IP address the Wemos was assigned.

I have tried to make sure I fully comment the code, but feel free to let me know if there’s anything you don’t understand about it. There’s not much to it, but I did try to account for the fact that the weight will increase as the printer pulls on the filament. So the code ignores any readings that incrase by less than the “Weight increase amount to ignore” in the settings. From my testing, my printer adds about 20 to 30 grams of force. So I set that amount to 60:

//2 libraries. 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" //******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 = D1; const int LOADCELL_SCK_PIN = D2; //******End User Config****** #define STRING_LEN 128 #define NUMBER_LEN 32 HX711 loadcell; //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); //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. //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 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'"); //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); //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. //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 reading the weight filamentWeight = loadcell.get_units(20) - strtof(spoolOffsetValue,NULL); // -- 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("/tare2", handleTare2); server.on("/config", []{ iotWebConf.handleConfig(); }); server.onNotFound([](){ iotWebConf.handleNotFound(); }); Serial.println("Ready."); } void loop() { //take a load cell reading sensorWeight = loadcell.get_units(10); //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(); } 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 += String(filamentWeight, 2); s += "g left<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='https://www.automatedhome.party'>Automatedhome.party</a></footer></body></html>

"; //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 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='https://www.automatedhome.party'>Automatedhome.party</a></footer></body></html>

"; 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='https://www.automatedhome.party'>Automatedhome.party</a></body></html>

"; 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='https://www.automatedhome.party'>Automatedhome.party</a></body></html>

"; } 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='https://www.automatedhome.party'>Automatedhome.party</a></body></html>

"; } 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='https://www.automatedhome.party'>Automatedhome.party</a></footer></body></html>

"; 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.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='https://www.automatedhome.party'>Automatedhome.party</a></footer></body></html>

"; 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)); } 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; }

Install the Spool Holder

This part is easy. Just put everything together as seen in the pictures. I don’t have a picture of it, but I currently just have the Wemos and the HX711 hanging to the left side.

After it’s installed, go ahead and plug it into power.

Configure the Settings on the Wemos



When you power on the Wemos, and it can’t connect o a WiFi network or if it isn’t setup yet, it will advertise itself as an access point. So on your phone or computer, connect to that network. It will show up as the ‘thingname’ that is configured in the code. If you left the default, then it’s going to be called FilamentWeight. And by default in the code, the password is ESP8266.

After you connect, it should redirect you to the configuration page automatically. If it doesn’t, I believe you can get to the config page by going to http://192.168.4.1 in your web browser. In the config name, you will need to type in the SSID and password of your network.

Once you do that, the device will connect to your network, and if you have it connected to your PC and view the serial monitor in ArduinoIDE, you can see the IP address it gets assigned and note that address down. I highly recommend creating a reservation in your router so it always gets that same IP address.

Then you can open your web browser and browse to http://ip_address. You should now see the home page of the device. It’s VERY bare-bones as web design is not my strong point. You may notice that the weight is completely wrong. That’s because it needs to be calibrated.

The main thing you need to configure here is the value for the “Known Weight”. If you want, you can enter the weight of an empty spool in the “Spool Offset” field. This will account for the weight of the spool so you know the true weight of the filament. After that, go to the home page, and go through the Calibration Wizard and be sure to read the instructions. After you do that your readings should now be fairly accurate. From the few tests I did, it appears to be within around .3%.

You can also just go to http://ip_address/weight and it will return just the weight as a number.

Future Plans

Here’s my todo list for this project. I have no idea when or if any of this gets done:

Design (or just find on Thingiverse) some sort of holder for the HX711 and the Wemos.

Add MQTT in there to send data to Home Assistant(or anything else that uses MQTT).

Make the Web interface nicer. If anyone can just provide me with HTML, I can add it into the code.

Write an OctoPrint plugin to get this info and display it. Maybe to add some logic in there to warn about a print if you don’t have enough filament.

Put this code up on GitHub so others can add to it, if there is actually interest in this project.

Add Telegram integration so you can ask you FilaWeigher how much weight is left. Or have it alert you when filament is low.

Add an option for a cheap OLED screen to display the weight.

Please let me know if there are other features you want. And which of these features you really want to see. I can focus more on the ones people really want.