So we've managed to convert a force into a voltage. How the heck do we read and interpret this? First of all, we use the Arduino to read the voltage, and then send the reading via bluetooth or USB to a laptop. On the laptop there is a GUI (Graphical User Interface) python program that allows us to view the readings and interact with the sensor. We can select which sensor to connect to (in case you have multiple lol?), what sample rate we want to use, and what baudrate to use for a USB connection. Then, the readings received are displayed on a dynamically updating plot. You can calibrate the sensor using the program too, by taking sample points of known voltage and weight, and then doing a linear regression to find the line of best fit. After calibrating, all the readings can be displayed with their real weight. It is also possible to save and load these calibrations, as well as recordings of what the scale reads, so you can go back and interpret experiments from the past.
All of the code can be found on a GitHub repository here, as well as a pretty decent description of what is going on. Self-documenting code yay!
All of the code can be found on a GitHub repository here, as well as a pretty decent description of what is going on. Self-documenting code yay!
On the right is the updating plot of readings. The left axis is the raw voltage reading, and the right axis shows the real weight after the voltage is translated through the calibration fit. On the left is the interface for editing a calibration, by adding or removing weight/voltage pairs, and viewing the fit of the samples. There is also a tab on the left for changing the settings of the scale. At the top are some menus for changing the units (lbs, kgs, N) and for saving/loading calibrations and recordings.
Arduino Code
For the Arduino I wrote a really simple program that just queries the HX711 for a reading as quickly as possible, and as soon as the Arduino gets a response it emits that reading over both the USB cable and the bluetooth module. A computer can just listen in on either of these two lines of communication. I wanted the Arduino program to be as simple as possible so that it would be as fast as possible, and to minimize the amount that I would have to plug it back in and re-upload code to it. I left a lot of the calculations to the computer, such as figuring out a timestamp that a reading was recorded.
Laptop GUI
This was the bulk of the code. I wanted it to follow a nice Model-View-Controller Architecture, so a lot of the logic is separate from the actual visual representation on the screen
"View": LoadCellControll.ui, basiggui.py, and gui.py
The GUI is built using PyQt, a python library for GUI programs. I designed the layout of the GUI using the program called Qt Designer. It allows you to drag and drop elements of a GUI such as buttons, text boxes, tabs, windows, menus, and more, and set some of their basic behavior such as how they resize when you make the window larger or smaller. The output of Qt Designer is a file with a .ui extension that describes the layout of you GUI using a JSON-like syntax, with hierarchies of tabs, windows, and elements. PyQt contains a function for translating this .ui file into actual python code that will actually create the GUI. At the beginning of every program run I recompile the .ui file into python code in the basicgui.py file.
However, this code only defines the looks of the GUI, and doesn't create any of the complex behavior that a GUI needs such as "When you click this button, we need to get the entry from this text box, add it to the database, display in on the graph." I added this functionality in a file called gui.py. This is probably the most complicated part of the program, because there were so many little behaviors that I had to tweak. It also is some of the most boring code, so I don't want to get into it too much. However, it is good to note that I used the library pyqtgraph for plotting the calibration and the incoming data.
However, this code only defines the looks of the GUI, and doesn't create any of the complex behavior that a GUI needs such as "When you click this button, we need to get the entry from this text box, add it to the database, display in on the graph." I added this functionality in a file called gui.py. This is probably the most complicated part of the program, because there were so many little behaviors that I had to tweak. It also is some of the most boring code, so I don't want to get into it too much. However, it is good to note that I used the library pyqtgraph for plotting the calibration and the incoming data.
"Model" and "Controller": loadcellcontrol.py
That is all the "View" portion of the application. The "Model" and "Controller" portions of the application are taken care of in loadcellcontrol.py. Here is where I store all the calibration and recording data (the "Model"), as well as manage communication between the GUI and the Arduino, and deal with saving and loading data (the "Controller").
other: scale.py
Finally, the other piece of the application is scale.py, which is basically an Object-Oriented interface to talk with the Arduino over USB or Bluetooth, or to create a dummy scale which generates bogus readings, in case you want to test the GUI without actually using a real force sensor.
Something interesting to note here is my use of multiprocessing: The Arduino is constantly streaming readings to the computer, and I want to be able to receive them as soon as possible so that they are recorded at an accurate time, and so that the buffer which holds incoming USB and Bluetooth traffic does not overflow. Without multiprocessing (multithreading isn't an option with python because of the Global Interpreter Lock, blahblahblah who cares), too much of the computer's CPU time could be held up doing calculations for the GUI, so we might be late on reading from the scale. So there is some fun multiprocessing stuff going on in here!
Something interesting to note here is my use of multiprocessing: The Arduino is constantly streaming readings to the computer, and I want to be able to receive them as soon as possible so that they are recorded at an accurate time, and so that the buffer which holds incoming USB and Bluetooth traffic does not overflow. Without multiprocessing (multithreading isn't an option with python because of the Global Interpreter Lock, blahblahblah who cares), too much of the computer's CPU time could be held up doing calculations for the GUI, so we might be late on reading from the scale. So there is some fun multiprocessing stuff going on in here!