I am developing a project that scans rfid tags and weights the asset the tag is attached to, outputting relevant data to an LCD and posting to a mySQL server.
I'm getting some unwanted/unexpected behavior and was hoping to get some help in troubleshooting.
While the system runs as expected for some time, there are intermittent problems occurring anywhere from 5 minutes to 30 minutes of run time that lead me to believe there might be some issue with the code and in particular memory allocation (although I'm only guessing). I'm very inexperienced in C so might be approaching dealing with strings and char arrays improperly, particularly when formatting GET and POST requests over and over.
Main issues are;
1.LCD randomly becomes garbled at times (random characters and cursor jumping around. It does recover sometimes if left running, but usually needs a hard reset of the system. (pic below)
2. rfid scanner becomes unresponsive and won't read tags for random period of time (seconds to a few minutes) then goes back to normal, scanning as expected every time a tag is placed in front of the scanner.
I've attached a schematic of the system and a photo of the PCB I made. It might be that my PCB design is poor or improper. Cables to LCD, MFRC522 and load cells is shielded data cable with sheath connected to GND.
Code also shown below. I hope it's a software issue as it will be easier to resolve, but if the hardware needs a significant redesign I will do this as per suggestions.
/*
Version 2.d
This sketch utilises an ESP32 hooked up to a HX711 with 4 load cells and an MFRC522 RFID scanner to read RFID tags attached to assets.
Identifying credentials are obtained from a mySQL server using http.GET and the weight of the asset is recorded and trasmitted via http.POST to a local SQL server.
*/
#include <soc/rtc.h>
#include "esp_wifi.h"
#include <WiFi.h>
#include <HTTPClient.h>
#include <ESPmDNS.h>
#include <LiquidCrystal_I2C.h>
#include <SPI.h>
#include <MFRC522.h>
#include <HX711.h>
// ===== User Defined Variables =====
const char* localHostname = "nodeboxin" ; // UDV // The hostname of this device
const char* ssid = "Apollo" ; // UDV // Your WiFi network NAME.
const char* password = "xxxxxxxxx" ; // UDV // Your WiFi network PASSWORD.
const char* regType = "IN" ; // UDV // Role if this NODE = "IN" or "OUT"
const char* sqlServer = "raspberrypi" ; // UDV // Hostname of the SQL Server.
const char* apiKeyValue = "xxxxxxxxxxx" ; // UDV // apiKey use for posting data.
const char* sqlPass = "xxxxxxxx" ; // UDV // SQL Server password
IPAddress serverIp(0, 0, 0, 0) ;
// ==== MFRC522 ====
#define SS_PIN 4
#define RST_PIN 5
MFRC522 rfid(SS_PIN, RST_PIN) ;
MFRC522::MIFARE_Key key ;
byte nuidPICC[4] ; // array that will store new UID (NUID)
boolean newTag = false ;
int newTagReg ; // when was the tag registered in millis()
boolean tagMatch = false;
const int tagRegThresh = 1000 ; // 2 seconds between scans. Don't want to keep scanning while there is weight on the scale.
const char* ver = "2.d";
// ==== HX711 ====
const int LOADCELL_DOUT_PIN = 25 ;
const int LOADCELL_SCK_PIN = 26 ;
HX711 scale ;
float weight ;
char weightChar[4] ;
const int weightThresh = 5 ; // UDV // How many kg on scale before RFID reader fires UP ?
const int calFact = -23040.00; //Assets IN // UDV // Calibration factor for the scales.
//const int calFact = -19830.00; //Assets OUT // UDV // Calibration factor for the scales.
// ==== LCD ====
int lcdColumns = 16 ;
int lcdRows = 2 ;
LiquidCrystal_I2C lcd(0x27, lcdColumns, lcdRows) ;
const int errorDelay = 2500 ; // How long to print Error Messages to LCD
const int printDelay = 1000 ; // How long to print regular Messages to LCD
// Char arrays for storing credentials
char sqlUID[18] ;
char brandID[4] = "UA1" ;
char loadID[10] ;
char boxnum[4] ;
char boxNum[4] ;
char rfidMatchChar[18] ;
// LED pins
const int RLED = 16 ;
const int GLED = 14 ;
const int BLED = 13 ;
void setup() {
pinMode(RLED, OUTPUT); digitalWrite(RLED, LOW);
pinMode(GLED, OUTPUT); digitalWrite(GLED, LOW);
pinMode(BLED, OUTPUT); digitalWrite(BLED, LOW);
// When setup runs, cycle RGB LED 3x
for (int i = 0 ; i < 3 ; i++) {
digitalWrite(BLED, HIGH); delay(200); digitalWrite(BLED, LOW);
digitalWrite(RLED, HIGH); delay(200); digitalWrite(RLED, LOW);
digitalWrite(GLED, HIGH); delay(200); digitalWrite(GLED, LOW);
}
rtc_clk_cpu_freq_set(RTC_CPU_FREQ_80M) ; // slow ESP32 to 80 mHz to prevent HX711 issues
Serial.begin(115200) ;
Serial.println(F("Begin Setup"));
lcd.init() ; // Inititalise LCD
lcd.backlight() ;
lcd.print(F("Ver: "));
lcd.print(ver); // print current sketch version
delay(printDelay);
lcd.setCursor(0, 0);
lcd.print(F("Welcome! ")) ;
lcd.setCursor(0, 1) ;
lcd.print(F("Node role: ")) ; lcd.print(regType) ;
delay(printDelay) ;
lcd.clear();
init_network() ; // connect to wifi and locate IP address of sql server
// initialise rfid scanner
SPI.begin() ;
delay(50) ;
rfid.PCD_Init() ; // Init MFRC522
rfid.PCD_SetRegisterBitMask(rfid.RFCfgReg, (0x07<<4)); // set gain to max (48 dB)
for (byte i = 0; i < 6; i++) { // Fill key with empty values
key.keyByte[i] = 0xFF ;
}
init_scale() ; // initialize scale
lcd.clear() ; lcd.print(F("All Services OK!")) ;
lcd.clear() ;
//end setup
}
void loop() {
lcd.home();
//check wifi connected
if ( !(WiFi.status() == WL_CONNECTED) ){
init_network();
}
weight = scale.get_units(3) ; // get weight at start of each loop
if (weight < 0 ){ // if scales report negative value report 0.00
weight = 0.00;
}
lcd.setCursor(0, 0);
if (strlen(boxnum) == 0){ // if this is the first box, ask for asset
lcd.print(F("Scan Box"));
}
else {
lcd.print(F("Prev BoxID: ")); lcd.print(boxnum);
}
lcd.setCursor(0, 1) ;
lcd.print(weight) ;
lcd.print(F(" kg ")) ;
if (int(weight) >= weightThresh ) { //if more than X kg on scale, fire up the rfid reader and look for a new tag
readTag();
if (!newTag){ // if tag wasn't read exit loop after short delay
delay(50);
return;
}
if (newTag) { // if we got a new tag from the reader...
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(F("New Tag Read"));
RGBVis(3); // flash blue LED 3 times
// get matching record from the mySQL Server
getRfidMatch() ;
if (tagMatch) { // if the tag exists in db and credentials were obtained...
splitCredentials(); // split rfidMatchChar to populate box credentials
weight = scale.get_units(5); // obtain stable weight
// print box info to lcd
lcd.clear();
lcd.setCursor(0, 0) ; lcd.print(F("BOX: ")); lcd.print(loadID); lcd.print(F(" ")); lcd.print(boxnum);
lcd.setCursor(0,1) ; lcd.print(weight); lcd.print(F(" kg"));
delay(printDelay);
lcd.clear();
lcd.setCursor(0,0); lcd.print(F("Posting DATA")) ;
postData() ; // Send data to SQL server using POST
}
else {
lcd.setCursor(0, 1); lcd.print(F("No Match in DB!!"));
Serial.println(F("No match for this ID!"));
delay(printDelay);
RGBVis(2); // flash LED RED
return;
}
}
}
// Finished with the tag so reset flags.
newTag = false ;
tagMatch = false ;
}
void getRfidMatch() {
// Empty Credentials
loadID[0] = '\0' ;
boxnum[0] = '\0' ;
boxNum[0] = '\0' ;
rfidMatchChar[0] = '\0' ;
tagMatch = false ;
char GET_URL[150] = "" ;
append("http://", GET_URL) ;
char IPChar[16] ;
serverIp.toString().toCharArray(IPChar, 16) ;
append(IPChar, GET_URL) ;
append("/rfid2hiveid_0.3_secure.php?", GET_URL) ;
// Format GET request
append2("api_key=", apiKeyValue, GET_URL) ;
append2("&pass=", sqlPass, GET_URL) ;
append2("&rfid=", sqlUID, GET_URL) ;
// *DEBUGGING report what is being sent in GET request
// Serial.print(F("GET Request: ")) ;
// Serial.println(GET_URL) ;
// Send formatted GET request
HTTPClient http ; // Declare object of class HTTPClient
boolean httpResponse = false; // flag to check for result
while (!httpResponse){
http.begin(GET_URL);
int httpResponseCode = http.GET() ;
if (httpResponseCode == 200){
httpResponse = true;
}
}
String payload = http.getString(); //Get the response payload
http.end(); // cleanup
if (payload == "0 results") { // if there's no match, report.
tagMatch = false;
return;
}
else {
tagMatch = true ; // Sql record returned form server
int payload_len = payload.length() + 1;
payload.toCharArray(rfidMatchChar, payload_len); // place returned ":" delimeted data into rfidMatchChar char
Serial.println(rfidMatchChar); // print the matching credentials
}
}
void postData() {
boolean dataWrite = false;
char POST_URL[200] ;
char php_loc[50] = {0} ;
dtostrf(weight, 5, 2, weightChar) ;
append("http://", php_loc) ;
char IPChar[16] ;
serverIp.toString().toCharArray(IPChar, 16) ;
append(IPChar, php_loc) ;
// Format POST request
append("/post-extraction-data.php?", php_loc) ;
append2("api_key=", apiKeyValue, POST_URL) ;
append2("®type=", regType, POST_URL) ;
append2("&brand=", brandID, POST_URL) ;
append2("&rfid=", sqlUID, POST_URL) ;
append2("&loadid=", loadID, POST_URL) ;
append2("&boxnum=", boxnum, POST_URL) ;
append2("&weight=", weightChar, POST_URL) ;
HTTPClient http ; //Declare object of class HTTPClient
while (!dataWrite) {
http.begin(php_loc) ;
http.addHeader("Content-Type", "application/x-www-form-urlencoded") ;
int httpResponseCode = http.POST(POST_URL) ;
Serial.print(F("Response code from POST request: ")) ; Serial.println(httpResponseCode) ;
if (httpResponseCode == 200) {
lcd.setCursor(0, 1) ; lcd.print("DATA WRITE OK! ") ;
RGBVis(1);
delay(printDelay) ;
dataWrite = true ;
}
else {
Serial.println(F("Data Post Error")) ;
lcd.setCursor(0,1) ; lcd.print(F("DATA WRITE FAIL!")) ;
RGBVis(2);
delay(errorDelay) ;
}
}
}
void init_network() { // Initialise network interface
esp_wifi_set_max_tx_power(20); // Set to low to reduce brown-out issues
lcd.clear(); lcd.setCursor(0, 0) ; lcd.print(F("Join WiFi ")) ;
WiFi.setHostname(localHostname) ; // set hostname
WiFi.begin(ssid, password) ;
while (WiFi.status() != WL_CONNECTED) {
delay(50) ;
Serial.print(F(". ")) ;
}
lcd.setCursor(14, 0); lcd.print(F("OK"));
// Start the mDNS responder for "nodeboxin.local"
while (!MDNS.begin(localHostname)) {
Serial.println(F("mDNS fail!")) ;
delay(500);
}
// Resolve the IP Address for the mySQL Server
serverIp = MDNS.queryHost(sqlServer) ;
lcd.setCursor(0, 1) ; lcd.print(F("Locate Server "));
while (serverIp.toString() == "0.0.0.0") {
Serial.println(F("Can't find sqlServer")) ;
Serial.println(F("Retrying")) ;
delay(50) ;
serverIp = MDNS.queryHost(sqlServer) ;
}
lcd.setCursor(14, 1); lcd.print(F("OK"));
delay(printDelay);
Serial.print(F("Server IP: ")) ;
Serial.println(serverIp.toString()) ;
}
void init_scale() {
Serial.println(F("Initializing Scale"));
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN) ;
scale.set_scale(calFact);
lcd.clear() ;
lcd.print(F("Tare function.")) ;
lcd.setCursor(0, 1) ;
lcd.print(F("Empty Scale: ")) ;
for (int i = 5; i > 0; i--) {
lcd.setCursor(13, 1) ;
lcd.print(i) ;
delay(200) ;
}
scale.tare() ; // reset the scale to 0
}
void readTag() { // scans for a new tag.
if ( ! rfid.PICC_IsNewCardPresent()) // Reset the loop if no new card present
return ;
if ( ! rfid.PICC_ReadCardSerial()) // Verify if the NUID has been read
return ;
MFRC522::PICC_Type piccType = rfid.PICC_GetType(rfid.uid.sak) ;
if (rfid.uid.uidByte[0] != nuidPICC[0] ||
rfid.uid.uidByte[1] != nuidPICC[1] ||
rfid.uid.uidByte[2] != nuidPICC[2] ||
rfid.uid.uidByte[3] != nuidPICC[3] ) {
newTag = true ; // Set newTag flag
Serial.println(F("A new card has been detected.")) ;
lcd.setCursor(0, 0); lcd.print(F("Tag Registered "));
for (byte i = 0; i < 4; i++) { // Store NUID into nuidPICC array
nuidPICC[i] = rfid.uid.uidByte[i] ;
}
array2delim(nuidPICC, sqlUID) ; // push uid byte array to delimeted char sqlUID
Serial.print(F("delimeted UID: ")); Serial.println(sqlUID); // print delimeted ID to the console
}
else {
Serial.println(F("Card read previously.")) ;
newTag = false;
}
// cleanup and exit func
rfid.PICC_HaltA() ; // Halt PICC
rfid.PCD_StopCrypto1() ; // Stop encryption on PCD
}
void array2delim(unsigned char* b, char* s) // converts byte array to delimeted char array
{
int i = 4 ;
for (;;) {
unsigned char f1 = *b % 10 ;
unsigned char f100 = *b / 10 ;
unsigned char f10 = f100 % 10 ;
f100 /= 10 ;
if (f100) {
*s++ = '0' + f100 ;
*s++ = '0' + f10 ;
} else if (f10)
*s++ = '0' + f10 ;
*s++ = '0' + f1 ;
if (!--i) {
*s = '\0' ;
return ;
}
*s++ = ':' ; // delimeter
b++ ;
}
}
void append (const char *s, char *c) { // append string s to char c
strncat (c, s, strlen(s)) ;
}
void append2 (const char *s, const char *t, char *c) { // append string s and char t to char c
strncat (c, s, strlen(s)) ;
strncat (c, t, strlen(t)) ;
}
void splitCredentials() { // helper func to split rfidMatch payload
char* ptr = strtok(rfidMatchChar, ":");
byte i = 0;
while (ptr) {
if (i == 0) {
strcpy(loadID, ptr);
}
else if (i == 1) {
strcpy(boxnum, ptr);
}
else if (i == 2) {
strcpy(boxNum, ptr);
}
ptr = strtok(NULL, ":");
i++;
}
}
void RGBVis(int t) {
if (t == 1) { // data transmitted successfully > Flash Green LED 5x
for (int i = 0 ; i < 5; i++) {
digitalWrite(GLED, HIGH);
delay(100);
digitalWrite(GLED, LOW);
delay(100);
}
}
if (t == 2) { // There is an error code. > Flash Red LED 10x
for (int i = 0 ; i < 10 ; i++) {
digitalWrite(RLED, HIGH);
delay(100);
digitalWrite(RLED, LOW);
delay(100);
}
}
if (t == 3) { // New Card captured >> flash Blue 3 times
for (int i = 0 ; i < 3 ; i++) {
digitalWrite(BLED, HIGH);
delay(50);
digitalWrite(BLED, LOW);
delay(50);
}
}
}
--- Schematic ---
--- Garbled Screen ---
--- PCB ---