S.H.E.L.F. V2
1. Introduction
Hi! For this Project14, I decided to tackle a problem I already tried solving once in a different way. I'll begin by outlining the problem I wish to tackle. When going to the market or when I'm out of the house and want to make something to eat at home, there is always that question of, do I have that ingredient at home, or do I have enough of that left at home or when is the expiry date of the bag of pasta and so on. To solve this, the idea is having all of the information needed on me at all times, in other words, having an app for my phone.
S.H.E.L.F. V1
The first project I ever posted on elemnt14 was my attempt at making a smart shelf. This was for the PiChef Design Challenge, where as the brain of the whole thing I went with a Raspberry Pi. I considered RFID back then, but in the end went with a computer vision approach. The idea was having specially designed labels on the top of all of the containers which the camera would recognize. While I still like the concept, it requires quite a bit of work to get it to any decent level. Here is a blog post that summarizes my journey for that attempt: S.H.E.L.F. - Pi Chef Design Challenge
V1 Results
While I still like the idea of using computer vision for this, there is a couple of projects that I've noticed, first off all the complexity of it, for it to work smoothly it requires a lot of work. The labels would need a redesign as well, I tried making them as intuitive and as easy to use as possible, but since I depended on recognizing colors, the lighting in the room played a crucial role. Also since the labels had to be visible at all times, no stacking of items was allowed on the shelf. There was also a problem of what to do with bottles for example, as the camera was top mounted, so the label had to be on top, this also meant that the area above the shelf had to be completely clear so the camera can cover the whole shelf.
2. Plan
For the V2 simply said, the plan is to use RFID stickers and RFID tags to identify the containers/items on the Last time as the brain I used a Raspberry, this time, I will be switching to an Arduino Mega 2560 and an ESP8266-07. I don't need the image processing which I needed in V1 so I can with something which is much lower powered, but which will be easier to develop, and easier to interface with all of the sensors. The ESP8266-07 will be used to upload data online so I can later access it using my phone. Last time I used a third party app which showed the value, but didn't have the interface I wanted, so this time I will be making a simple Android app which can read data from ThingSpeak (at least for now I will be sticking with ThingSpeak, since I really don't know that much about that sphere and this is the easiest route for me to establish communication between the Arduino and my phone).
How will it work?
- I want to use 2 RC522 RFID modules, one in vertical position and the other one in horizontal, this is because I plan on putting some tags on the side and some of the bottom of items
- I'll find a couple of containers which will have the sticker on them permanently, while I will attach tags with elastic string for example, to things like bottles where the packaging gets thrown away after using it
- The shelf will be used by picking off the shelf or placing on the shelf item by item, where all you need to do is go over the scanner
- The stickers can be found really cheap (as low as 10 cents a piece if you buy 100 of them in pack), and I was afraid that the scanner won't read them fast, but it turns out it reads incredibly fast
- As for the design itself, I actually found V1 so I will be using the same piece as well as the same sensors, since they gave really accurate results last time
- Every time an item is added or removed from the shelf, the Arduino will over serial communicate with ESP8266-07 to update the data on Thingspeak for that field
Features
- One feature that came to my mind is why not use the shelf as scale also, for some rougher measurements where accuracy isn't that critical. I already have the sensors which are accurate enough for most stuff in the kitchen, so I will be adding a small button that will transfer the shelf into scale mode when pressed, where it will calibrate the shelf to 0g when pressed and measure the mass from there.
- Touchscreen interface
- I want to have some kind of interface to ensure the user that he has removed/added something and that everything went smoothly and a way to just cycle through all of the items, or just do calibration and stuff like that
- Expiry date
- Giving the user an option to enter an expiry date for the item (by default the user wouldn't have to do this, to not make the shelf hard and slow to use), which would help with certain kind of items. Then the whole system could send out a notification when something is a week from expiring
- MicroSD and back up power supply
- Of course this will need to be connected to a charger, but what happens if there is a blackout? If anything I want to have a microSD to save all of the current states, and would like to integrate a power bank which would be kept charged by the charger and used when the power goes out
3. Build
There won't be a lot of work for the mechanical part of the build, since I've already done that, there will be some small stuff which I will be covering in the end. I'll separate the build into small segments based around single sensors and then combine all of that into one bigger thing, I found this is usually the easiest and fastest way for me to work, rather than trying to get multiple things to work at once.
ESP8266-07 Module
This is the module I will be using for connecting to the internet. It's small, runs on 3.3V and it's pretty easy to program. It supports serial communication (Rx & Tx pins) which I will be using to get the data from the Arduino, and upload it over to ThingSpeak. To test everything out, I made a test channel on ThingSpeak and wanted to try uploading some data, I decided just to try and upload random numbers.
Connection for the ESP8266-07
All of the ESP8266 boards I've seen run on 3.3V, be sure to check that, because the board can easily be fried when connected to 5V
There are 2 ways I considered for programming the ESP8266, either using an Arduino Uno, removing the AtMega328 from it and using the board as a programmer or going with a USB to Serial converter, in the end I went with the converter. As for how to connect the pins from ESP8266 this is how it should be done:
- +3.3V to VCC
- GND to GND
- Tx on the board to Rx on the converter/Arduino Uno board
- Rx on board to Tx on the converter/Arduino Uno board
- CH_PD (CH_PC) to VCC/3.3V
- GPIO0 on the board to GND
- GPIO15 on the board to GND
Uploading to ThingSpeak
For the code itself, there are a lot of great tutorials online on uploading data to ThingSpeak, I tried a couple, and will be showing the option I found to be the easiest for me.
#include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> //WiFi Settings const char *ssid = "Put your SSID here"; const char *password = "Put your Password here"; //Site Settings const char *host = "api.thingspeak.com"; String apiKey = "Your ThingSpeak Write Key Here"; const int port = 80; String link, data; void setup() { Serial.begin(9600); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); //Connecting to WiFi //With this while loop we are waiting for a connection while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } //Writing to Serial when we connect to WiFi Serial.println(""); Serial.println(""); Serial.println(""); Serial.print("Connected to "); Serial.println(ssid); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } void loop() { WiFiClient client; if(!client.connect(host, port)){ Serial.println("Connection Failed"); delay(500); return; data = String(random(500)+500); //Generating a random number link = "GET /update?api_key="+apiKey+"&field1="; //If we want to write to other fields we just have to change field1 to field2 etc link = link + data; link = link + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"; client.print(link); delay(6000); }
This code should be uploading a random number between 500 and 1000 to my channel field 1 every 6 seconds. For this version of the project all I need is one way communication, uploading the data to ThingSpeak, which I will then fetch using an Android app. Here is how the ThingSpeak Side looks now:
For uploading to other fields in the channel all we have to do is change where is says field1 in the code to field2, field3 and so on. The free ThingSpeak account allows 4 channels and 8 fields per channel, for testing purposes I will be sticking to on channel and 8 fields. Adding more wouldn't be a problem by simply adding another apiKey for the second channel and so on. All that's left to do with ESP8266 now is configure it to talk to the Arduino and depending on the data sent from the Arduino upload a certain value to a specific field.
First Serial Test
For the first Serial communication test, all I want to do is establish a communication between the ESP8266-07 and Arduino Mega 2560 and decode the data and upload it then to ThingSpeak. Since I will be only uploading integer values to ThingSpeak the way I'll be coding everything gets very simple. As the last digit of every message I send from the Arduino will be the field number and the rest will be the information that we want to upload. In other words to get the field to which I want to upload, all we have to do is get the modulo (field = data % 10), while we will get the data as data/10 (whole number division). Here are the codes I used
ESP8266
#include <ESP8266WiFi.h> #include <WiFiClient.h> #include <ESP8266WebServer.h> //WiFi Settings const char *ssid = "Put your SSID here"; const char *password = "Put your WiFi password here"; //Site Settings const char *host = "api.thingspeak.com"; String apiKey = "Put your API write key here"; const int port = 80; String link; int data = 0; //Values for different fields String serialData = ""; int fieldSelector = 1; int f1Value = 0; int f2Value = 0; int f3Value = 0; int f4Value = 0; int f5Value = 0; int f6Value = 0; int f7Value = 0; int f8Value = 0; void setup() { Serial.begin(9600); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); //Connecting to WiFi //With this while loop we are waiting for a connection while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } //Writing to Serial when we connect to WiFi Serial.println(""); Serial.println(""); Serial.println(""); Serial.print("Connected to "); Serial.println(ssid); Serial.print("IP address: "); Serial.println(WiFi.localIP()); } void loop() { WiFiClient client; fieldSelector = 0; if(Serial.available() > 0){ serialData = Serial.readString(); data = serialData.toInt(); fieldSelector = data % 10; data = data / 10; } if(!client.connect(host, port)){ Serial.println("Connection Failed"); delay(500); return; } switch(fieldSelector){ case 1: link = "GET /update?api_key="+apiKey+"&field1="; link = link + data; link = link + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"; client.print(link); delay(500); break; case 2: link = "GET /update?api_key="+apiKey+"&field2="; link = link + data; link = link + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"; client.print(link); delay(500); break; case 3: link = "GET /update?api_key="+apiKey+"&field3="; link = link + data; link = link + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"; client.print(link); delay(500); break; case 4: link = "GET /update?api_key="+apiKey+"&field4="; link = link + data; link = link + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"; client.print(link); delay(500); break; case 5: link = "GET /update?api_key="+apiKey+"&field5="; link = link + data; link = link + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"; client.print(link); delay(500); break; case 6: link = "GET /update?api_key="+apiKey+"&field6="; link = link + data; link = link + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"; client.print(link); delay(500); break; case 7: link = "GET /update?api_key="+apiKey+"&field7="; link = link + data; link = link + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"; client.print(link); delay(500); break; case 8: link = "GET /update?api_key="+apiKey+"&field8="; link = link + data; link = link + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n"; client.print(link); delay(500); break; default: break; } delay(2000); }
Arduino
void setup() { // put your setup code here, to run once: Serial.begin(9600); delay(60000); } void loop() { // put your main code here, to run repeatedly: Serial.println("1001"); delay(60000); Serial.println("2002"); delay(60000); Serial.println("3003"); delay(60000); Serial.println("4004"); delay(60000); Serial.println("5005"); delay(60000); Serial.println("6006"); delay(60000); Serial.println("7007"); delay(60000); Serial.println("8008"); delay(60000); }
To be sure it works ok, I put a delay of 1 minute between sending the data. My first test was with 10 seconds between sending the messages. There are some limitations to ThingSpeak where it is blocking some requests if they are too frequent, so I had a problem of only every second request going through. For the purposes of this project I really don't need any high speed communication. Here are the ThingSpeak charts.
This system is working without any problems, I will play around with different delays a bit more, to see what works and what doesn't but the main part when it comes to the ESP8266-07 is complete with this. For the internet part all that is left to do is make an Android app which I will cover a bit later in this blog, time to focus on the scale sensors now!
Scale Sensors
For measuring the weight of the items on the shelf I'll be using 2 load cells and 2 amplifiers. While the amplifiers I have can both handle 2 cells each, last time I was working with these load cells I found it worked much better when using 2 amplifiers, and it's also a bit easier to do. To use the cells with the Arduino, I downloaded the HX711 library from here https://github.com/bogde/HX711 . The library has some great examples to get started with the cells and it's also explained how to calibrate the load cells. This is how I've connected the cells to the Arduino:
Connection and Code
The board I have doesn't have the pins exactly labeled as the one I used in the schematic, but the connections go like this:
- Red (Load Cell) --- E+ (HX711)
- Black (Load Cell) --- E- (HX711)
- Green (Load Cell) --- A- (HX711)
- White (Load Cell) --- A+ (HX711)
The code with the library I am using is incredibly simple, I went through the examples and extracted the things I needed from them and added other stuff that I needed, and ended up with this:
#include "HX711.h" //Dual calibrated scales working const int LOADCELL1_DOUT_PIN = 2; const int LOADCELL1_SCK_PIN = 3; const int LOADCELL2_DOUT_PIN = 4; const int LOADCELL2_SCK_PIN = 5; HX711 scale1; HX711 scale2; void setup() { Serial.begin(9600); //Starting up the scale sensors Serial.println("Starting Up"); scale1.begin(LOADCELL1_DOUT_PIN, LOADCELL1_SCK_PIN); scale2.begin(LOADCELL2_DOUT_PIN, LOADCELL2_SCK_PIN); //Calibrating the load cells //scale.tare sets the scale to zero scale1.set_scale(-205); scale1.tare(); scale2.set_scale(-220); scale2.tare(); Serial.println("Exiting setup"); } void loop() { Serial.print("Scale1:\t"); Serial.println(scale1.get_units(), 1); Serial.print("Scale2:\t"); Serial.println(scale2.get_units(), 1); Serial.print("Combined mass:\t"); Serial.println(scale1.get_units()+scale2.get_units()); scale1.power_down(); // put the ADC in sleep mode scale2.power_down(); // put the ADC in sleep mode delay(1000); scale1.power_up(); scale2.power_up(); }
Testing
{gallery} Load Cell Testing |
---|
Test Bench: My build from SHELF V1 that I am using again |
Reference Scale: The weight of my notebook is around 419g measured on a standard kitchen scale |
First Results: And here we have the results from the load cells, I am extremely happy with the values I'm getting |
Plot1: I let the code run for a bit, here is how the values look like |
Plot 2: Here is the same plot when it scales down the value we are looking at, while it's not perfectly consistent, for what I'm planning to use it for, it will work great, since the peak to peak is less than 4g |
One thing that can always be improved are the scale calibration factors. Because I am using 2 separate load cells on 2 sides of the shelf, how much force each of the cells experience changes depending on the position of the thing that is put on the shelf. If the cells aren't calibrated good, the combined mass can change drastically while moving the item around, for that my current deviation is about 3-4g which isn't that bad, but I might play a bit more with the final setup to make it more accurate.
RC522 RFID Module
Just like with the ESP8266 modules this module runs on 3.3V make sure to double check that, since 5V can damage the board
Now that weighing everything and uploading data online is sorted out, it's time to do the part which is the theme for this Project14, RFID. I won't be doing anything complicated with RFID, I will keep it simple, but it will the whole project work a lot smoother. I will use the standard module for RFID the RC522 module, since I already have experience using it before, because its easily accessible from anywhere and there are lot of projects online using this module, so getting any help for it is easy. Here is a blog where I already covered some things about this module: Fingerprint Skeleton Key - RFID Module - Design for a Cause Challenge - Blog Post #3 . Last time I only kept using the RFID tags, which don't get me wrong, work great, but while looking around for this project I found something that will be perfect, RFID stickers.
The stickers are extremely cheap, I didn't buy a lot of them, so they were around 40 cents a piece, but if you buy, let's say a 100 of them, you can get them for somewhere around 10 cents a piece, which is amazing. If it suits some product, I can also use the RFID card or tag besides the stickers. For matching the objects to the tags I'll be using only the UUID like last time, since it's the thing that's read first and goes really fast with this module. For connecting it to the Arduino Mega I just used the recommended pin layout as it was in the library except the 2 easily configurable pins. Here is the code that gets the UUID and matches it with one of the saves UUID-s.
#include <SPI.h> #include <MFRC522.h> /* Typical pin layout used: * ----------------------------------------------------------------------------------------- * MFRC522 Arduino Arduino Arduino Arduino Arduino * Reader/PCD Uno/101 Mega Nano v3 Leonardo/Micro Pro Micro * Signal Pin Pin Pin Pin Pin Pin * ----------------------------------------------------------------------------------------- * RST/Reset RST 9 5 D9 RESET/ICSP-5 RST * SPI SS SDA(SS) 10 53 D10 10 10 * SPI MOSI MOSI 11 / ICSP-4 51 D11 ICSP-4 16 * SPI MISO MISO 12 / ICSP-1 50 D12 ICSP-1 14 * SPI SCK SCK 13 / ICSP-3 52 D13 ICSP-3 15*/ constexpr uint8_t RST_PIN = 9; // Configurable, see typical pin layout above constexpr uint8_t SS_PIN = 8; // Configurable, see typical pin layout above MFRC522 mfrc522(SS_PIN, RST_PIN); // Instance of the class void setup() { Serial.begin(9600); Serial.println("Setup started"); SPI.begin(); mfrc522.PCD_Init(); // Init MFRC522 Serial.println("Exiting setup"); } int readRFID(){ // Look for new cards if ( ! mfrc522.PICC_IsNewCardPresent()) { return 0; } // Select one of the cards if ( ! mfrc522.PICC_ReadCardSerial()) { return 0; } //Sticker 1 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0xf9 && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("1"); return 1; } //Sticker 2 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0xf5 && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("2"); return 2; } //Sticker 3 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0x30 && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("3"); return 3; } //Sticker 4 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0x2c && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("4"); return 4; } //Sticker 5 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0x27 && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("5"); return 5; } //Sticker 6 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0xe9 && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("6"); return 6; } //Sticker 7 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0xed && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("7"); return 7; } //Sticker 8 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0xf1 && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("8"); return 8; } return 0; } int card = 0; void loop() { card = readRFID(); if(card != 0){ Serial.println(card); delay(500); } }
The code is really simple, it's a modified example. The biggest thing I changed is that I've packed it all inside of a function, because it will be easier for me to use later on in the main code of the project. I tested this out with a couple of stickers and here are the results:
The code works without any problems. There are some things I just want to point out, the UUID is longer than this, but all of the stickers I had were exactly the same except for the second uid byte, so I didn't go all through. Also, my idea was to use 2 of these modules, one vertical and one horizontal, I had problems with getting one of the modules to work properly in the beginning, and in the end, it refused to work at all. I tried using the multiRead example which can utilize more than 1 of these modules and managed to get these results:
The Reader0 is the new module I bought which works without a problem, while the Reader1 is an old module I had from before, but which worked the last time I used it. It didn't want to show the firmware version and would only read sometimes. I tried switching the modules around to see if it was to the faulty wiring, but than the Reader1 in the code would work and the Reader0 wouldn't, so it was a problem on the board. I tried inspecting the board and resoldering the pins (one of the pads broke off the PCB), but no luck. So in the end I will only be using one module.
4. Android App
I'll upload all of the source codes for the app, as well as the .apk files on my Github, which I'll link at the end of this blog
I had 2 choices for making the app, going the longer more customizable route of using Android Studio, or using MIT App Inventor 2. Because I didn't need any special functionality from the app besides accessing the internet and storing some data on the phone, I decided to give MIT App Inventor 2 a try for once. MIT App Inventor 2 is a free to use, online app developing tool for Android. It requires minimal coding, since it's drag and drop. The first thing I wanted to do is get absolutely any data from ThingSpeak to my phone.
Extracting data from ThingSpeak
We will need a couple of simple components for this work, a few labels, a button, a clock widget and a web component. We need a couple of labels, because I found the easiest way to extract the value from the string as you will see soon. Let's begin with the design of the app.
That's all of the UI elements that we will need, now in the block diagram, using the web component we have to load the data and this is how we do that:
You get the URL value from your ThingSpeak account, when you go to your channel, than to API keys, in the bottom right corner you will see a couple of links, the one we are looking for is Get a Channel Field, since we plan on using multiple fields in the future. The code above tries to get data everytime the clock ticks, or whenever we press the button. When we paste the link in question into the browser here is what we get:
We get a lot of text, pretty much, one thing I did was change the number of results to 1, since we only need the last one, in the first highlighted area in the link you can see "fields/1", by changing the number after the fields we can choose the channel from which we want to get info. Some problems arise when we upload to multiple fields which I'll discuss later. Also, be aware that ThingSpeak allows the fields value to be updated every 15 seconds. Now back to the data we got, at the end of the link we can see "field1":"111", the 111 is the value we are trying to extract, here is a simple not so pretty way of doing that, but it worked great for me:
The goal is to see what's unique in the string and take it apart piece by piece until we are left with the value. There are certainly better ways of doing this, the way I did it was finding where the "feeds" part of the string is, and after that finding the "field1" part and extracting the value after that. With that the value extractor from ThingSpeak is complete, here is how it looks like on my phone:
On the first picture is the starting screen of our app, we have the 4 labels and the button we are using as the refresh the button, if we let the clock click and update that way, or if we click the button we should get something that looks like the second picture. In the second picture as the first label we can see exactly the same thing we saw when we put our link into the browser, second label is a string cut from "feeds" to the end, third one is cut from "field1" to the end, and in the end in the forth label we have the value itself from the desired field. A simpler way would maybe be trying to go from position to position in the string but there is a problem because the thing like entry_id can get another digit which would set off the whole string and so on. With this simple version done, it's time to make something more useful than this.
Complete App V1
While our first app gets us the value we want, we need to make it useful for the project. For the second app I will be limiting it to 8 fields, since that is the maximum number of fields a ThingSpeak channel allows, we can go more by using multiple channels or by using something other than ThingSpeak, but for now I will stick with 8. Besides that I want to keep the Refresh button, because I think it's useful to have it. One more big problem that the first app has is that it's not able to save any data, so everytime we restart the app, it needs an internet connection to get the data again, even if we updated everything when we left home for example. The last thing is that we now only see values, I want to make it configurable to show the thing that is actually behind every of the channels. Here we go:
Screen1
Screen1 will be out startup screen and our main screen. All of the things I want to have on screen1 are the refresh button, settings button and all of the field values. When we press the Refresh button, the app should try refreshing all of the data, and we press settings, the app will go to the second screen. Here is the design of the first screen:
It has labels for all of the values, labels for all of the names of the fields, the 2 buttons as well as the background components, which include 8 web components, a clock component and a tinyDB component. The biggest change from the last app is the tinyDB component. That component is used to easily store data on the device, because otherwise, every time we close the app everything would reset itself. As for the programming part, it's pretty much the same as the last app, here are the blocks:
{gallery} Screen 1 Blocks |
---|
Initialize State and refresh: Initializing all of the variables and handling the updates and the button for changing the screen |
Process Data: This is the same concept as in the first app, but with an important modification which I'll explain after this |
Process Data 1: We copy the whole structure and just change the numbering of the field wherever we see the name field, to get and update data for the other fields |
Process Data 2: We have to do that for all 8 of the fields |
Field Name Update: We are retrieving the data from tinyDB to update our field names, which we will configure on the second screen |
Whole code: And in the end for screen1 we end up with something like this |
As I've said above, I've made a small change to the data process part which I used in both this and previous app. The reason for the change is that I am now writing to multiple fields, and when we for example only write to field1 and try to get the update everything would work great. But, if we have more than one field and let's say we wrote data to field2 with our ESP8266, if we try to get the data the same way we will get null, since the link we used only shows the latest entry for the whole channel. Now, if you are looking at the field that wasn't the last one updated, the value you will get is null (There is probably a workaround this, but I didn't have time to fix it, so I solved it this way). To solve this problem, at the end of the data process structures for all of the fields, I just checked if the value was null, and if it was not than I can write the data to my label. Here are the blocks that made that change:
The same thing applies for all fields, we just have to configure everything to field2 or field 3 and so on. With that, our main screen is complete. It could use a nicer looking interface and stuff like that, but for now, it has the functionality we are after. Now let's do screen2.
Screen2
The purpose of screen2 is to configure all of the naming labels (and other stuff down the line if needed). As I've mentioned previously, we switch to screen2 by pressing the Settings button on screen1. On screen2 we will need text boxes for entering the names of all the fields, a button to update everything (because of how everything was made step by step, I have buttons left for updating every field individually, which can be removed, but they don't make a difference), we need a go back button, for switching to screen1, and we need a tinyDB component, to store all of the data. It's important the tags we have on screen1 for the field names, match the tags we are going to use here, if we want the data to go through. Here is the design of screen2:
Now all that's left is to program everything on this screen. This screen has a lot less functionality than the previous one, simply said, all it needs to do is save the data from the text box components when either the update all button is pressed or the update button for that channel. One thing I also added is for text boxes to have the starting text that was last entered in them, so you can easily modify it without having to write the whole thing from the beginning. Here are the blocks for this screen:
{gallery} Screen 2 Blocks |
---|
Single Field Update and Screen Switch: This is how we update single field names with the single field names button, and also switching to screen1 when BackButton is clicked |
Screen2 Initialize: This part of the program handles the starting text of the text boxes when we switch to screen2. It will show the values that are saved in the TinyDB which are the values we typed in the last time, since this is the only place we change those values |
Update All: The Button for updating all of the field names at once |
Whole Code: And this is what we end up with |
With that out app is complete. It should cover all of the basic functionality for the first prototype. It can easily be expanded to handle things like warnings when something is going to expire soon, a temperature sensor, or even a smoke sensor, since there is a good chance that this shelf will be used in a kitchen. Here are some pictures of the app working!
Test
{gallery} App Test |
---|
Start: When we first open the app this is what we see, before it updates any of the fields or before we configure the names. |
Settings 1: By pressing the Settings button we come to screen2, where we can enter the names of all the fields. |
Settings 2: I entered some random stuff from the kitchen that came first to mind, and now we click the update all button, and after that by clicking back we go back to screen1. |
Updated: After the update cycle finished, we can see all of the values connected to the names of the fields. |
5. First Prototype
With the app completed, all that's left to do is program the Arduino to work. For this version I won't be using a microSD card so I will be saving data using EEPROM, which turned out to work great since I don't have a lot of data. Because the max value that we can store in one address is 255, I saved all of the data in 2 address, by saving data/256 in the first address, and data%256 in the second (or just simply data, since it will do the same thing). One thing we need to do, before starting up the shelf the very first time is, setting up all of the values in EEPROM to 0. For doing that, I made a small sketch:
Arduino clear EEPROM
This is the first sketch we run, and if everything is set up correctly after that, we don't have to run it again, it just writes zeros to all of the address we plan on using.
#include <EEPROM.h> //Reseting EEPROM void setup() { // put your setup code here, to run once: Serial.begin(9600); EEPROM.write(0, 0); EEPROM.write(1, 0); EEPROM.write(2, 0); EEPROM.write(3, 0); EEPROM.write(4, 0); EEPROM.write(5, 0); EEPROM.write(6, 0); EEPROM.write(7, 0); EEPROM.write(8, 0); EEPROM.write(9, 0); EEPROM.write(10, 0); EEPROM.write(11, 0); EEPROM.write(12, 0); EEPROM.write(13, 0); EEPROM.write(14, 0); EEPROM.write(15, 0); } int i = 0; void loop() { // put your main code here, to run repeatedly: Serial.println(EEPROM.read(i)); i++; if(i == 15) i = 0; }
Arduino Main Code
After running the last sketch on the Arduino, when we're sure that everything is connected properly, we can finally upload the main sketch. The main code is a code made out of smaller segments that I presented earlier in the blog with some smaller changes. But this version handles putting items on the shelf, as well as getting them off the shelf, and uploading the data online. The code for the ESP8266-07 wasn't changed to the one I already showed as the final version in this blog.
#include <SPI.h> #include <MFRC522.h> #include "HX711.h" #include "TimerOne.h" #include <EEPROM.h> //Pins for connecting to the load cells const int LOADCELL1_DOUT_PIN = 2; const int LOADCELL1_SCK_PIN = 3; const int LOADCELL2_DOUT_PIN = 4; const int LOADCELL2_SCK_PIN = 5; HX711 scale1; HX711 scale2; /* Typical pin layout used: * ----------------------------------------------------------------------------------------- * MFRC522 Arduino Arduino Arduino Arduino Arduino * Reader/PCD Uno/101 Mega Nano v3 Leonardo/Micro Pro Micro * Signal Pin Pin Pin Pin Pin Pin * ----------------------------------------------------------------------------------------- * RST/Reset RST 9 5 D9 RESET/ICSP-5 RST * SPI SS SDA(SS) 10 53 D10 10 10 * SPI MOSI MOSI 11 / ICSP-4 51 D11 ICSP-4 16 * SPI MISO MISO 12 / ICSP-1 50 D12 ICSP-1 14 * SPI SCK SCK 13 / ICSP-3 52 D13 ICSP-3 15*/ constexpr uint8_t RST_PIN = 9; constexpr uint8_t SS_PIN = 8; // Instance of the class MFRC522 mfrc522(SS_PIN, RST_PIN); //Current Field Values int f1 = 0, f2 = 0, f3 = 0, f4 = 0, f5 = 0, f6 = 0, f7 = 0, f8 = 0; //Variables int currentMass = 0, previousMass = 0; bool massChangedToHigher = false; bool massChangedToLower = false; bool knownRFID = false; volatile bool sendData = false; int currentField = 1; int seconds = 0; int prev = 0; int lastCardRead = 0; int a,b; //Constants const int massThreshold = 20; //Minimum loadcell reading difference needed void setup() { //Serial 1 will be used for communicating with the ESP8266-07 Serial.begin(9600); Serial1.begin(9600); //SPI bus SPI.begin(); //Initialize MFRC522 mfrc522.PCD_Init(); //Initialize Load cells scale1.begin(LOADCELL1_DOUT_PIN, LOADCELL1_SCK_PIN); scale2.begin(LOADCELL2_DOUT_PIN, LOADCELL2_SCK_PIN); //Calibrating the load cells //scale.tare sets the scale to zero Serial.println("Setting up load cells."); scale1.set_scale(-205); scale1.tare(); scale2.set_scale(-220); scale2.tare(); //Timer used for sending data every 15 seconds to the ESP8266 Timer1.initialize(1000000); Timer1.attachInterrupt(triggerSend); a = readScales(); Serial.print("Mass on shelf: "); Serial.println(a); currentMass = a; previousMass = currentMass; Serial.println("Exiting Setup"); } void triggerSend(){ seconds++; } int readScales(){ //This function returns the combined mass shown by the load cells scale1.power_up(); scale2.power_up(); int val = int(scale1.get_units() + scale2.get_units()); scale1.power_down(); scale2.power_down(); return val; } void uploadData(int c){ //This function, depending on the field select c, upload data over serial to ESP8266-07 which will then upload data to ThingSpeak int data; switch(c){ case 1: data = EEPROM.read(0)*256+EEPROM.read(1); data = data*10 + 1; Serial1.println(data); Serial.print("Data sent for field1:\t"); Serial.println(data); return; case 2: data = EEPROM.read(2)*256+EEPROM.read(3); data = data*10 + 2; Serial1.println(data); Serial.print("Data sent for field2:\t"); Serial.println(data); return; case 3: data = EEPROM.read(4)*256+EEPROM.read(5); data = data*10 + 3; Serial1.println(data); Serial.print("Data sent for field3:\t"); Serial.println(data); return; case 4: data = EEPROM.read(6)*256+EEPROM.read(7); data = data*10 + 4; Serial1.println(data); Serial.print("Data sent for field4:\t"); Serial.println(data); return; case 5: data = EEPROM.read(8)*256+EEPROM.read(9); data = data*10 + 5; Serial1.println(data); Serial.print("Data sent for field5:\t"); Serial.println(data); return; case 6: data = EEPROM.read(10)*256+EEPROM.read(11); data = data*10 + 6; Serial1.println(data); Serial.print("Data sent for field6:\t"); Serial.println(data); return; case 7: data = EEPROM.read(12)*256+EEPROM.read(13); data = data*10 + 7; Serial1.println(data); Serial.print("Data sent for field7:\t"); Serial.println(data); return; case 8: data = EEPROM.read(14)*256+EEPROM.read(15); data = data*10 + 8; Serial1.println(data); Serial.print("Data sent for field8:\t"); Serial.println(data); return; default: Serial.println("Channel selected badly"); return; } return; } int readRFID(){ //This functions returns the number of the sticker that has read, or return zero if no sticker, or if an unknown sticker is detected // Look for new cards if ( ! mfrc522.PICC_IsNewCardPresent()) { return 0; } // Select one of the cards if ( ! mfrc522.PICC_ReadCardSerial()) { return 0; } //Sticker 1 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0xf9 && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("Sticker 1 detected"); return 1; } //Sticker 2 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0xf5 && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("Sticker 2 detected"); return 2; } //Sticker 3 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0x30 && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("Sticker 3 detected"); return 3; } //Sticker 4 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0x2c && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("Sticker 4 detected"); return 4; } //Sticker 5 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0x27 && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("Sticker 5 detected"); return 5; } //Sticker 6 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0xe9 && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("Sticker 6 detected"); return 6; } //Sticker 7 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0xed && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("Sticker 7 detected"); return 7; } //Sticker 8 if (mfrc522.uid.uidByte[0] == 0x04 && mfrc522.uid.uidByte[1] == 0xf1 && mfrc522.uid.uidByte[2] == 0xef && mfrc522.uid.uidByte[3] == 0xba){ Serial.println("Sticker 8 detected"); return 8; } return 0; } void loop() { //We update ThingSpeak every 16 seconds if(seconds%16 == 0 && seconds!=prev){ Serial.print("This is how many seconds passed: "); Serial.println(seconds); sendData = false; uploadData(currentField); if(currentField == 8){ currentField = 1; } else{ currentField++; } prev = seconds; } currentMass = readScales(); if(massChangedToHigher == false && massChangedToLower == false){ Serial.println("Waiting for mass change"); if(currentMass - previousMass >= massThreshold) { Serial.println(currentMass); Serial.println(previousMass); Serial.println("And here"); massChangedToHigher = true; massChangedToLower = false; } if(previousMass - currentMass >= massThreshold){ massChangedToLower = true; massChangedToHigher = false; } } //Checking if a new tag/sticker/card has been detected if(lastCardRead != 0) { knownRFID = true; } else{ lastCardRead = readRFID(); } //This is the case when we detect a card/tag/sticker and see a signifcant increase in mass if(knownRFID == true && massChangedToHigher == true){ //We add the item on the shelf and upload the data Serial.println("Item of sufficient mass and known UUID added to the shelf, proceeding with uploading the data"); delay(1000); currentMass = readScales(); a = currentMass - previousMass; switch(lastCardRead){ case 1: f1 = currentMass - previousMass; EEPROM.write(0,a/256); EEPROM.write(1,a); Serial.print("Field 1 changed to: "); Serial.println(f1); break; case 2: f2 = currentMass - previousMass; EEPROM.write(2,a/256); EEPROM.write(3,a); Serial.print("Field 2 changed to: "); Serial.println(f2); break; case 3: f3 = currentMass - previousMass; EEPROM.write(4,a/256); EEPROM.write(5,a); Serial.print("Field 3 changed to: "); Serial.println(f3); break; case 4: f4 = currentMass - previousMass; EEPROM.write(6,a/256); EEPROM.write(7,a); Serial.print("Field 4 changed to: "); Serial.println(f4); break; case 5: f5 = currentMass - previousMass; EEPROM.write(8,a/256); EEPROM.write(9,a); Serial.print("Field 5 changed to: "); Serial.println(f5); break; case 6: f6 = currentMass - previousMass; EEPROM.write(10,a/256); EEPROM.write(11,a); Serial.print("Field 6 changed to: "); Serial.println(f6); break; case 7: f7 = currentMass - previousMass; EEPROM.write(12,a/256); EEPROM.write(13,a); Serial.print("Field 7 changed to: "); Serial.println(f7); break; case 8: f8 = currentMass - previousMass; EEPROM.write(14,a/256); EEPROM.write(15,a); Serial.print("Field 8 changed to: "); Serial.println(f8); break; default: break; } lastCardRead = 0; currentMass = readScales(); previousMass = currentMass; knownRFID = false; massChangedToHigher = false; massChangedToLower = false; delay(500); } //This is the case when we detect a card/tag/sticker and see a signifcant decrease in mass if(knownRFID == true && massChangedToLower == true){ //In this case, the user took the item off the shelf and read its tag, so we set its mass to zero Serial.println("Removing item from the shelf."); switch(lastCardRead){ case 1: f1 = 0; EEPROM.write(0,0); EEPROM.write(1,0); Serial.print("Field 1 changed to: "); Serial.println(f1); break; case 2: f2 = 0; EEPROM.write(2,0); EEPROM.write(3,0); Serial.print("Field 2 changed to: "); Serial.println(f2); break; case 3: f3 = 0; EEPROM.write(4,0); EEPROM.write(5,0); Serial.print("Field 3 changed to: "); Serial.println(f3); break; case 4: f4 = 0; EEPROM.write(6,0); EEPROM.write(7,0); Serial.print("Field 4 changed to: "); Serial.println(f4); break; case 5: f5 = 0; EEPROM.write(8,0); EEPROM.write(9,0); Serial.print("Field 5 changed to: "); Serial.println(f5); break; case 6: f6 = 0; EEPROM.write(10,0); EEPROM.write(11,0); Serial.print("Field 6 changed to: "); Serial.println(f6); break; case 7: f7 = 0; EEPROM.write(12,0); EEPROM.write(13,0); Serial.print("Field 7 changed to: "); Serial.println(f7); break; case 8: f8 = 0; EEPROM.write(14,0); EEPROM.write(15,0); Serial.print("Field 8 changed to: "); Serial.println(f8); break; default: break; } lastCardRead = 0; currentMass = readScales(); previousMass = currentMass; knownRFID = false; massChangedToHigher = false; massChangedToLower = false; delay(500); } }
6. Testing
Here are the masses of the things I used above:
{gallery} Mass |
---|
Cookies: Accuracy isn't bad |
Sunflower Seeds: Accuracy great as well, I just grabbed snacks I could find around |
Almonds: And finally some almonds |
7. Summary
This was a really fun project to revisit, specially considering that V1 of this project was my first project here. For it to being usable it has to undergo a few changes, first of all ThingSpeak. While ThingSpeak is a great free service for experimenting, it has some limitations that limit the project in some ways. Because of the 15 second update policy, and null values, you have to wait for Arduino to cycle through all of the values. I also had an idea to add a touchscreen and maybe a RTC module to integrate expiration dates as well. One thing I forgot to add which can be added easily is a couple of LEDs as indicators what's happening. But all in all, I'm really happy how it turned out in the end. Thanks for reading the blog, hope you like it!
Milos