ESP8266 memory crashes

I'm VERY new to Ardiuno, having only ever worked with Raspberry Pis and even then never with the actual pins. This is my very first hardware project.

Project: automated cat feeder for use while I'm on vacation

Issue: I'm having memory crash/reboots at random and I'm unable to determine the cause. I cannot tell if I have a faulty board or if my limited C knowledge has caused memory issues.

Connected hardware: RFID reader, servo, LED

Board: https://www.amazon.com/gp/product/B07333L9VR/ (this is exactly the board I purchased based upon a tutorial I found)

RFID Reader: 125Khz RFID module - UART | Seeed Studio Wiki

Code: attached

Known issues: these occurred after I began noticing the crash/reboot process. I find it highly unlikely that they are the issue.

  • I'm not great at soldering. I know it's not the best job but I've ensured there aren't any bridges causing shorts.
  • I somehow managed to mess up the LED. I don't know if I soldered one of the resisters on backwards, if I fried something in it while attaching the leads and resisters or if I managed to somehow attach the leads to the GPIO pins in the wrong order. I only added the LED for troubleshooting while detached from my computer. The crashes occurred before the addition of the light and I used it to tell when reboot/wifi reconnect occurred.

Configuration:

  • all 5v power is tied together to a 5v transformer (both positive and ground) so as to remove the load from the USB port
  • Servo data line is attached to pin D1
  • LED R is pin D5
  • LED G is pin D6
  • LED B is pin D7
  • RFID TX is pin D2
  • Error log readout is attached

Other notes:

  • As I mentioned, this is my first project. I'll be going on vacation at the end of the month and I need to get this running in a stable manner before then.
  • The entire reason for the different RFID tags is because 1 cat is on a diet, 1 cat never gets to eat her food because the one on the diet steals it, and one cat is a kitten that needs free access to food right now. This type of access control is easily handled manually but I need to ensure their diet is followed while I'm gone.
  • I can provide the API I have set up if needed. It's built in Node.JS and SQLite and running on a Raspberry Pi Zero.
  • My understanding of C syntax is limited. I work in software development but I'm teaching myself this language for hobby use in my free time. If I've made an error I would very much appreciate the opportunity to learn through examples rather than being told what I've done wrong. I can't spontaneously know what I don't already know.
  • The log files are just a sample. I can provide more if requested.

UPDATE
I removed the original attachment and have included the updated code below. I re-wrapped the RFID.available() as the trigger in the main loop rather than calling a function blindly and THEN testing. I've also included a second log. I'm repeatedly getting the following error with some variation of stack trace (see attached for trace).

Exception 0: Illegal instruction
PC: 0x4022fe48
EXCVADDR: 0x00000000

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <SoftwareSerial.h>
#include <Servo.h>

SoftwareSerial RFID(D2, D3); // RX and TX
Servo servo;
HTTPClient http;
const char* ssid = "YourSSID";
const char* password = "YOURPASSWORD";
// use first sketch in http://wp.me/p3LK05-3Gk to get your tag numbers
int tag1[14] = {2, 54, 56, 48, 48, 65, 57, 54, 67, 54, 48, 67, 68, 3}; //Medli
int tag2[14] = {2, 54, 56, 48, 48, 65, 57, 66, 55, 69, 53, 57, 51, 3}; //testing
//int tag2[14] = {2, 54, 56, 48, 48, 65, 57, 56, 52, 53, 52, 49, 49, 3}; //Pixel
int tag3[14] = {2, 54, 56, 48, 48, 65, 57, 56, 52, 53, 49, 49, 52, 3}; //Majora

void setup()
{
  pinMode(D5, OUTPUT);
  pinMode(D6, OUTPUT);
  pinMode(D7, OUTPUT);
  Serial.begin(9600);  // start serial to PC
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    analogWrite(D5, 0); //R
    analogWrite(D6, 0); //G
    analogWrite(D7, 0); //B
    delay(500);
    analogWrite(D5, 255);
    analogWrite(D6, 0);
    analogWrite(D7, 0);
    delay(500);
  }
  analogWrite(D5, 0);
  analogWrite(D6, 255);
  analogWrite(D7, 0);
  RFID.begin(9600);    // start serial to RFID reader
}

void loop()
{
  if (RFID.available() > 0) {
    Serial.println(F("RFID Available"));
    readTags();
  }
}

boolean comparetag(int a[14], int b[14])
{
  int d = 0;
  for (int c = 0 ; c < 14 ; c++)
  {
    if (a[c] == b[c])
    {
      d++;
    }
  }
  if (d == 14)
  {
    return true;
  }
  return false;
}

int checkmytags(int newtag[14]) // compares each tag against the tag just read
{
  int cat = -1;
  if (comparetag(newtag, tag1) == true)
  {
    cat = 0;
  }
  if (comparetag(newtag, tag2) == true)
  {
    cat = 2;
  }
  if (comparetag(newtag, tag3) == true)
  {
    cat = 1;
  }
  if (calculateUsage(cat) == 0) {
    return 0;
  }
  else {
    return 1;
  }
}

void readTags()
{
  int newtag[14] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // used for read comparisons
  int tagCheck = -1;
  //if (RFID.available() > 0)
  //{
    // read tag numbers
    delay(100); // needed to allow time for the data to come in from the serial buffer.

    for (int z = 0 ; z < 14 ; z++) // read the rest of the tag
    {
      int data1 = RFID.read();
      newtag[z] = data1;
    }
    RFID.flush(); // stops multiple reads
    analogWrite(D5, 0);
    analogWrite(D6, 0);
    analogWrite(D7, 255);

    //do the tags match?
    tagCheck = checkmytags(newtag);
  //}


  // now do something based on tag type
  if (tagCheck == 0) // if we had a match
  {
    analogWrite(D5, 128);
    analogWrite(D6, 0);
    analogWrite(D7, 128);
    feed();
    delay(600000);
  }
  else if (tagCheck != 0) // if we didn't have a match
  {
    analogWrite(D5, 255);
    analogWrite(D6, 255);
    analogWrite(D7, 0);
    delay(1000);
  }

}

void feed() {
  servo.attach(5);
  servo.write(180);
  delay(250);
  servo.write(90);
  servo.detach();
}

int checkCap(int cat) {
  String address = "http://192.168.2.19:3000/cats/";
  String uriValue = "/cap";
  String string = address + cat + uriValue;
  if (WiFi.status() == 3) {
    http.begin(string);
    int httpCode = http.GET();
    String payload = http.getString();
    http.end();
    if (httpCode != 200) {
      return -1;
    }
    int validate = payload.toInt();
    return validate;
  }
  else {
    return -1;
  }
}

int checkUsage(int cat) {
  String address = "http://192.168.2.19:3000/cats/";
  String uriValue = "/usage";
  String string = address + cat + uriValue;
  if (WiFi.status() == 3) {
    http.begin(string);
    int httpCode = http.GET();
    String payload = http.getString();
    http.end();
    if (httpCode != 200) {
      return -1;
    }
    int validate = payload.toInt();
    return validate;
  }
  else {
    return -1;
  }
}

int calculateUsage(int cat) {
  int usage = checkUsage(cat);
  int counter = 0;
  while (usage < 0 && counter <= 5) {
    delay(1000);
    int usage = checkUsage(cat);
    counter++;
  }
  if (counter == 5 && usage < 0) {
    return -1;
  }
  delay(1000);
  counter = 0;
  int cap = checkCap(cat);
  while (cap < 0 && counter <= 5) {
    delay(500);
    int cap = checkCap(cat);
    counter++;
  }
  if (counter == 5 && cap < 0) {
    return -1;
  }
  counter = 0;
  if (cap == 0) {
    return 0;
  }
  if (usage < cap) {
    int updateUsageResult = updateUsage(cat, usage);
    while (updateUsageResult != 0 && counter <= 5) {
      delay(1000);
      int updateUsageResult = updateUsage(cat, usage);
      counter++;
    }
    if (counter == 5 && updateUsageResult != 0) {
      return -1;
    }
    return 0;
  }
  else {
    return 1;
  }
}

int updateUsage(int cat, int usage) {
  int updateValue = usage + 1;

  String address = "http://192.168.2.19:3000/cats/update";
  String put1 = "cat=";
  String put2 = "&";
  String put3 = "usage=";
  String putValue = put1 + cat + put2 + put3 + updateValue;
  if (WiFi.status() == 3) {
    http.begin(address);
    http.addHeader("Content-Type", "application/x-www-form-urlencoded");
    int httpCode = http.PUT(putValue);
    String payload = http.getString();
    http.end();
    if (payload == "Ok") {
      return 0;
    } else {
      return -1;
    }
  }
  else {
    return -1;
  }
}

log.txt (187 KB)

log2.txt (63.3 KB)

Several times inside this function

int calculateUsage(int cat) {
  int usage = checkUsage(cat);
  int counter = 0;
  while (usage < 0 && counter <= 5) {
    delay(1000);
    int usage = checkUsage(cat);
    counter++;
  }
  if (counter == 5 && usage < 0) {
    return -1;
  }
  ...

You are declaring a DIFFERENT variable inside your while loop compared to the variable declared outside it. You don't need to do this, just assign to the already existing variable

int calculateUsage(int cat) {
  int usage = checkUsage(cat);
  int counter = 0;
  while (usage < 0 && counter <= 5) {
    delay(1000);
    usage = checkUsage(cat);
    counter++;
  }
  if (counter == 5 && usage < 0) {
    return -1;
  }
   ...
[code]

Also, it appears you are attaching your servo to pin 5 which you are also using as an LED.  Are you sure your power supply can supply enough current to run the servo?

You are declaring a DIFFERENT variable inside your while loop compared to the variable declared outside it. You don't need to do this, just assign to the already existing variable

I cannot believe I missed that. Total rookie mistake. Thanks for pointing that out!

Also, it appears you are attaching your servo to pin 5 which you are also using as an LED. Are you sure your power supply can supply enough current to run the servo?

For some reason on this board the pin mappings are odd. Pin D5 is not the same as Pin 5. Pin 5 actually correlates to D1. As I was following someone else's original example I didn't modify it. I'll change to the D# format and see if that makes a difference.

I updated my ESP8266 library and the stack trace is now different. I'm entirely at a loss for what the issue is since the trace appears to be in the system library? Any help deciphering this would be incredibly appreciated.

Original stack from COM Log:
Attached because character limit reached.

Decoded trace using Stack Trace Decoder (GitHub - me-no-dev/EspExceptionDecoder: Exception Stack Trace Decoder for ESP8266 and ESP32) writted by @m3-no-dev:

Exception 0: Illegal instruction
PC: 0x4022ffec
EXCVADDR: 0x00000045

Decoding stack results
0x40100bf4: pvPortMalloc(size_t, char const*, int) at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\heap.cpp line 68
0x40206958: calloc_loc(size_t, size_t, char const*, int) at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\heap.cpp line 134
0x4020765f: _umm_free(void*) at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\umm_malloc\umm_malloc.cpp line 1318
0x40207610: _umm_free(void*) at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\umm_malloc\umm_malloc.cpp line 1304
0x4021198d: sys_timeout_abs at core/timeouts.c line 189
0x40207610: _umm_free(void*) at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\umm_malloc\umm_malloc.cpp line 1304
0x40207a38: free(void*) at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\umm_malloc\umm_malloc.cpp line 1764
0x40100bdc: vPortFree(void*, char const*, int) at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\heap.cpp line 61
0x402068ec: malloc_loc(size_t, char const*, int) at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\heap.cpp line 126
0x40207854: umm_malloc(size_t) at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\umm_malloc\umm_malloc.cpp line 1677
0x40100b0e: __attachInterruptArg(uint8_t, voidFuncPtr, void*, int) at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\core_esp8266_wiring_digital.cpp line 201
0x40100404: sws_isr0>() at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\libraries\SoftwareSerial\src\SoftwareSerial.cpp line 37
0x40205ba4: esp_yield() at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\core_esp8266_main.cpp line 91
0x4020611f: initTimer() at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\core_esp8266_waveform.cpp line 88
0x40100b32: __attachInterrupt(uint8_t, voidFuncPtr, int) at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\core_esp8266_wiring_digital.cpp line 206
0x40203ad8: SoftwareSerial::enableRx(bool) at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\libraries\SoftwareSerial\src\SoftwareSerial.cpp line 190
0x402043b0: EspClass::getCpuFreqMHz() at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\Esp.cpp line 264
0x40204006: SoftwareSerial::available() at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\libraries\SoftwareSerial\src\SoftwareSerial.cpp line 220
0x40205bc5: esp_schedule() at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\core_esp8266_main.cpp line 95
0x40205c5a: loop_wrapper() at C:\Users\Lane\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.2\cores\esp8266\core_esp8266_main.cpp line 127

log3.txt (17.2 KB)

Please post your updated code.

wbmattt:
For some reason on this board the pin mappings are odd. Pin D5 is not the same as Pin 5. Pin 5 actually correlates to D1. As I was following someone else's original example I didn't modify it. I'll change to the D# format and see if that makes a difference.

Isn't that confusing? It had me running around in circles for a while the first time I used ESP8266. I was used to some Arduino boards like the Nano having the pins marked like D5 on the silkscreen but you used 5 to refer to that pin in your code but the ESP8266 developers did things differently. Here, you can see how the pins are mapped for your D1 Mini Pro:

static const uint8_t D0   = 16;
static const uint8_t D1   = 5;
static const uint8_t D2   = 4;
static const uint8_t D3   = 0;
static const uint8_t D4   = 2;
static const uint8_t D5   = 14;
static const uint8_t D6   = 12;
static const uint8_t D7   = 13;
static const uint8_t D8   = 15;
static const uint8_t RX   = 3;
static const uint8_t TX   = 1;

So there is no functional difference between using 5 or D1 in your code but I think your code will be much easier to understand if you refer to the pins as they are marked on the silkscreen of the board with the Dn notation.

Updated code, as requested. My apologies for not uploading it in my reply. It was past midnight and I was VERY tired.

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <SoftwareSerial.h>
#include <Servo.h>

SoftwareSerial RFID(D2, D3); // RX and TX
Servo servo;
HTTPClient http;
const char* ssid = "SSID";
const char* password = "PASSWORD";
// use first sketch in http://wp.me/p3LK05-3Gk to get your tag numbers
int tag1[14] = {2, 54, 56, 48, 48, 65, 57, 54, 67, 54, 48, 67, 68, 3}; //Medli
int tag2[14] = {2, 54, 56, 48, 48, 65, 57, 66, 55, 69, 53, 57, 51, 3}; //testing
//int tag2[14] = {2, 54, 56, 48, 48, 65, 57, 56, 52, 53, 52, 49, 49, 3}; //Pixel
int tag3[14] = {2, 54, 56, 48, 48, 65, 57, 56, 52, 53, 49, 49, 52, 3}; //Majora

void setup()
{
  pinMode(D5, OUTPUT);
  pinMode(D6, OUTPUT);
  pinMode(D7, OUTPUT);
  Serial.begin(115200);  // start serial to PC
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    analogWrite(D5, 0); //R
    analogWrite(D6, 0); //G
    analogWrite(D7, 0); //B
    delay(500);
    analogWrite(D5, 255);
    analogWrite(D6, 0);
    analogWrite(D7, 0);
    delay(500);
  }
  analogWrite(D5, 0);
  analogWrite(D6, 255);
  analogWrite(D7, 0);
  RFID.begin(9600);    // start serial to RFID reader
}

void loop()
{
  if (RFID.available() > 0) {
    Serial.println(F("RFID Available"));
    readTags();
  }
}

boolean comparetag(int a[14], int b[14])
{
  int d = 0;
  for (int c = 0 ; c < 14 ; c++)
  {
    if (a[c] == b[c])
    {
      d++;
    }
  }
  if (d == 14)
  {
    return true;
  }
  return false;
}

int checkmytags(int newtag[14]) // compares each tag against the tag just read
{
  int cat = -1;
  if (comparetag(newtag, tag1) == true)
  {
    cat = 0;
  }
  if (comparetag(newtag, tag2) == true)
  {
    cat = 2;
  }
  if (comparetag(newtag, tag3) == true)
  {
    cat = 1;
  }
  if (calculateUsage(cat) == 0) {
    return 0;
  }
  else {
    return 1;
  }
}

void readTags()
{
  int newtag[14] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // used for read comparisons
  int tagCheck = -1;
  //if (RFID.available() > 0)
  //{
    // read tag numbers
    delay(100); // needed to allow time for the data to come in from the serial buffer.

    for (int z = 0 ; z < 14 ; z++) // read the rest of the tag
    {
      int data1 = RFID.read();
      newtag[z] = data1;
    }
    RFID.flush(); // stops multiple reads
    analogWrite(D5, 0);
    analogWrite(D6, 0);
    analogWrite(D7, 255);

    //do the tags match?
    tagCheck = checkmytags(newtag);
  //}


  // now do something based on tag type
  if (tagCheck == 0) // if we had a match
  {
    analogWrite(D5, 128);
    analogWrite(D6, 0);
    analogWrite(D7, 128);
    feed();
    delay(600000);
  }
  else if (tagCheck != 0) // if we didn't have a match
  {
    analogWrite(D5, 255);
    analogWrite(D6, 255);
    analogWrite(D7, 0);
    delay(1000);
  }

}

void feed() {
  servo.attach(D1);
  servo.write(180);
  delay(250);
  servo.write(90);
  servo.detach();
}

int checkCap(int cat) {
  String address = "http://192.168.2.19:3000/cats/";
  String uriValue = "/cap";
  String string = address + cat + uriValue;
  if (WiFi.status() == 3) {
    http.begin(string);
    int httpCode = http.GET();
    String payload = http.getString();
    http.end();
    if (httpCode != 200) {
      return -1;
    }
    int validate = payload.toInt();
    return validate;
  }
  else {
    return -1;
  }
}

int checkUsage(int cat) {
  String address = "http://192.168.2.19:3000/cats/";
  String uriValue = "/usage";
  String string = address + cat + uriValue;
  if (WiFi.status() == 3) {
    http.begin(string);
    int httpCode = http.GET();
    String payload = http.getString();
    http.end();
    if (httpCode != 200) {
      return -1;
    }
    int validate = payload.toInt();
    return validate;
  }
  else {
    return -1;
  }
}

int calculateUsage(int cat) {
  int usage = checkUsage(cat);
  int counter = 0;
  while (usage < 0 && counter <= 5) {
    delay(1000);
    usage = checkUsage(cat);
    counter++;
  }
  if (counter == 5 && usage < 0) {
    return -1;
  }
  delay(1000);
  counter = 0;
  int cap = checkCap(cat);
  while (cap < 0 && counter <= 5) {
    delay(500);
    cap = checkCap(cat);
    counter++;
  }
  if (counter == 5 && cap < 0) {
    return -1;
  }
  counter = 0;
  if (cap == 0) {
    return 0;
  }
  if (usage < cap) {
    int updateUsageResult = updateUsage(cat, usage);
    while (updateUsageResult != 0 && counter <= 5) {
      delay(1000);
      updateUsageResult = updateUsage(cat, usage);
      counter++;
    }
    if (counter == 5 && updateUsageResult != 0) {
      return -1;
    }
    return 0;
  }
  else {
    return 1;
  }
}

int updateUsage(int cat, int usage) {
  int updateValue = usage + 1;

  String address = "http://192.168.2.19:3000/cats/update";
  String put1 = "cat=";
  String put2 = "&";
  String put3 = "usage=";
  String putValue = put1 + cat + put2 + put3 + updateValue;
  if (WiFi.status() == 3) {
    http.begin(address);
    http.addHeader("Content-Type", "application/x-www-form-urlencoded");
    int httpCode = http.PUT(putValue);
    String payload = http.getString();
    http.end();
    if (payload == "Ok") {
      return 0;
    } else {
      return -1;
    }
  }
  else {
    return -1;
  }
}

Note: The if cap ==0 part in the calculateUsage function is because one of the cats is a kitten and has no cap, therefore zero since infinity isn't an option in integers.

Also, the ESP8266 library is version 2.5.2 now.
Also also, pin D3 is not in use as TX from ESP8266 to RFID board doesn't appear to either be active nor do anything.

The first thing i found was that you don't check during the reading of the buffer if there is anything still in there ?

for (int z = 0 ; z < 14 ; z++) // read the rest of the tag
    {
      int data1 = RFID.read();
      newtag[z] = data1;
    }

You seem to assume that if something is in there it will be your 14 (bytes ?) numbers. What if the reading is incomplete ? i suggest to change this to

for (int z = 0 ; z < 14 ; z++) // read the rest of the tag
    {
      if (RFID.available()) newtag[z] = RFID.read();
      
    }

if there is an incorrect data amount for whatever reason the tags won't be recognized anyway so that won't do any harm.
There are quite a few 'style' suggestions which would make the program more readable (for me) and therefore make it easier to find bugs, but this one may cause a memory corruption issue.

I personally found the esp8266 a little flaky for running projects long term.

Random reboots seemed to occur and in the end I opted to use the esp8266 with AT firmware as a peripheral to another microcontroller, and use if for just getting the time, or sending an email. With the reset line connected and used in that way it very reliable.

FFMan:
I personally found the esp8266 a little flaky for running projects long term.

Random reboots seemed to occur and in the end I opted to use the esp8266 with AT firmware as a peripheral to another microcontroller, and use if for just getting the time, or sending an email. With the reset line connected and used in that way it very reliable.

Understandable though i had similar issues at first i found solutions. 4 things,

  • Power properly and protect the pins from over or reverse voltages.
  • keep the WDT in mind, a yield() needs to happen every so often.
  • Turning off interrupts causes issues in general and in combination with WIFI in particular.
  • Make sure you do not divide by 0, on an AVR this is not a problem, but on an ESP it causes a reset.

ESP8266 has a lot of speed, a lot of memory, so much in fact that using the String class hardly causes issues (though for global Strings a .reserve() is recommended)

People have reported ESP units running for over a year without fault here on the forum.

Deva_Rishi:
People have reported ESP units running for over a year without fault here on the forum.

I am a fan of the ESP32 w/freeRTOS. I have one that has been running for a bit over 12 months without an issue.

I'll post new code soon (as soon as I've finished it) but my current thought is because the device crashes so often, because I'm getting closer to my deadline and because I haven't yet identified what the issue is in order to fix it I've decided to modify both the sketch and associated API.

Right now the API just returns numbers for GETs and an 'OK' for the PUT. Instead of calculating on the ESP8266 I'm going to do the calculation in the API such that the need for both cap and current usage aren't necessary. I'll write one for just 'am I allowed' in which the current time stamp in the database is checked against the most recently modified time stamp such that a certain amount of time must pass before a second feeding is allowed. This will also take into account the daily cap. So firstly, is the cap reached? If no, has enough time passed? If both conditions are met the API will then return simply an 'OK' and the after the feed has been performed, there will be a PUT reply in which the last known timestamp is updated and the current usage is updated. I'm hoping this results in fewer crashes as fewer variables will need to be stored in memory.

The only known issue I can think of at this point regarding this method is that I plan to set the PUT call after the feed action. So, if the crash occurs between the feed action and the PUT, the action won't be recorded and the feed won't have counted against the cap. If I had more equipment/sensors/etc I'd add a redundancy to check for that but I don't currently. I'm going to backlog that for v2.0 I think.

Thoughts? Is my logic sound enough to bother with? As mentioned I'll add the updated code once I actually get a chance to sit down and do it but this has been what's stewing around for now. I'll also add the API code in case I've done something just horribly wrong. It's written in NODE.js and I have legitimately no experience with it so I'm surprised it even works in the first place.

Thanks for all of your help so far. Each and every one of you have had awesome constructive criticism and I truly appreciate the opportunity to learn. It's refreshing and y'all deserve kudos for that!

And here's my updated project. I changed some things around as indicated in the comments.

doStuff.ino

#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <SoftwareSerial.h>
#include <Servo.h>

SoftwareSerial RFID(D2, D3); // RX and TX
Servo servo;
HTTPClient http;
const char* ssid = "SSID";
const char* password = "PASSWORD";
// use first sketch in http://wp.me/p3LK05-3Gk to get your tag numbers
int tag0[14] = {2, 54, 56, 48, 48, 65, 57, 66, 55, 69, 53, 57, 51, 3}; //testing
int tag1[14] = {2, 54, 56, 48, 48, 65, 57, 54, 67, 54, 48, 67, 68, 3}; //Medli
int tag2[14] = {2, 54, 56, 48, 48, 65, 57, 56, 52, 53, 52, 49, 49, 3}; //Pixel
int tag3[14] = {2, 54, 56, 48, 48, 65, 57, 56, 52, 53, 49, 49, 52, 3}; //Majora

void setup()
{
  pinMode(D5, OUTPUT); //R
  pinMode(D6, OUTPUT); //G - apparently I messed up this connection? But it's present in the code for when I fix it eventually
  pinMode(D7, OUTPUT); //B
  Serial.begin(115200);  // start serial to PC
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    analogWrite(D5, 0);
    analogWrite(D6, 0);
    analogWrite(D7, 0);
    delay(500);
    analogWrite(D5, 255);
    analogWrite(D6, 0);
    analogWrite(D7, 0);
    delay(500);
  }
  analogWrite(D5, 255);
  analogWrite(D6, 0);
  analogWrite(D7, 255);
  RFID.begin(9600);    // start serial to RFID reader
  Serial.println(F("Ready to read"));
}

void loop()
{
  analogWrite(D5, 255);
  analogWrite(D6, 0);
  analogWrite(D7, 255);
  int firstByte;
  if (RFID.available() > 0) {
    firstByte = RFID.read();
    while (firstByte != 2) {
      firstByte = RFID.read(); //throw away any bytes that aren't the beginning of the array
    }
    if (firstByte == 2) {
      readTags(firstByte);
    }
  }
}

boolean comparetag(int a[14], int b[14])
{
  int d = 0;
  for (int c = 0 ; c < 14 ; c++)
  {
    if (a[c] == b[c])
    {
      d++;
    }
  }
  if (d == 14)
  {
    return true;
  }
  return false;
}

int checkmytags(int newtag[14]) // compares each tag against the tag just read
{
  analogWrite(D5, 100);
  analogWrite(D6, 0);
  analogWrite(D7, 100);
  if (comparetag(newtag, tag1) == true)
  {
    return 0; //return cat ID of 0
  }
  if (comparetag(newtag, tag2) == true)
  {
    return 2; //return cat ID of 2
  }
  if (comparetag(newtag, tag0) == true) //this is a testing tag
  {
    return 2; //cat ID of 2 has unlimited dispenses, will update later as kitten grows out of unlimited feeding
  }
  if (comparetag(newtag, tag3) == true)
  {
    return 1; //return cat ID of 1
  }
  return -1; //if no comparison was made error out
}

void readTags(int firstByte)
{
  int newtag[14] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // used for read comparisons
  int tagCheck = -1;
  int usageCheck = -1;
  newtag[0] = firstByte;

  for (int z = 1 ; z < 14 ; z++) // read the rest of the tag
  {
    if (RFID.available()) newtag[z] = RFID.read();
  }
  RFID.flush(); // stops multiple reads, I think?
  analogWrite(D5, 0);
  analogWrite(D6, 0);
  analogWrite(D7, 255);
  
  //do the tags match?
  tagCheck = checkmytags(newtag);

  // now do something based on tag type
  if (tagCheck >= 0) // if we had a match
  {
    usageCheck = calculateUsage(tagCheck); //check current available usage
  }

  if (usageCheck == 0) { //indicates available to feed
    analogWrite(D5, 128);
    analogWrite(D6, 0);
    analogWrite(D7, 128);
    int updateUsages = updateUsage(tagCheck);
    if (updateUsages != 0) { //update the current usage before feeding. If program crashes before feed occurs the cat will surive. vice versa could mean unlimited feeds when not intended.
      return;
    }
    feed();
    delay(600000); //delay feeding/rfid read for 10 minutes
  } else {
    delay(1000); //wait just a sec and try again
    return;
  }
}

void feed() {
  servo.attach(D1);
  servo.write(180);
  delay(250);
  servo.write(90);
  servo.detach();
}

int calculateUsage(int cat) {
  int counter = 0;
  String address = "http://192.168.2.19:3000/cats/";
  String uriValue = "/checkusage";
  String string = address + cat + uriValue;
  int httpCode;
  String payload;
  while (counter <= 5) {
    if (WiFi.status() == 3) {
      http.begin(string);
      httpCode = http.GET();
      payload = http.getString();
      http.end();
      if ((httpCode == 200) && (payload == "OK")) {
        return 0;
      } else if (payload == "No") {
        return 1;
      }
    }
    counter++;
    delay(1000);
  }
  if (counter == 5) {
    return -1;
  }
}

int updateUsage(int cat) {
  String address = "http://192.168.2.19:3000/cats/update";
  String put1 = "cat=";
  String putValue = put1 + cat;
  int counter = 0;
  int httpCode;
  String payload;
  while (counter <= 5) {
    if (WiFi.status() == 3) {
      http.begin(address);
      http.addHeader("Content-Type", "application/x-www-form-urlencoded");
      httpCode = http.PUT(putValue);
      payload = http.getString();
      http.end();
      if (payload == "Ok") {
        return 0;
      }
    }
    counter++;
    if (counter == 5) {
      return -1;
    }
  }
}

Node.JS API

var path = require('path');
var http = require('http');
var express = require('express');
var bodyParser = require('body-parser');

var app = express();

const sqlite3 = require('sqlite3').verbose();
let db = new sqlite3.Database('/home/pi/Desktop/webApp/feeder.db', (err) => {
        if (err) {
                console.error(err.message);
        }
        console.log('Connected to feeder database.');
});

app.use(bodyParser.urlencoded({extended: false}));
app.use(express['static'](__dirname ));


app.get('/cats/:cat', function(req, res) {
        let sql = 'select * from cats where id = ?';
        let id = req.params.cat;
        db.get(sql, [id], (err, row) => {
                if (err) throw err;
                res.status(200).send(row);
        });
});

app.get('/cats/:cat/cap', function(req, res) {
        let sql = 'select feedCap from cats where id = ?';
        let id = req.params.cat;
        db.get(sql, [id], (err, row) => {
                if (err) throw err;
                res.status(200).send(String(row.feedCap));
        });
});

app.get('/cats/:cat/usage', function(req, res) {
        let sql = 'select currentUsage from cats where id = ?';
        let id = req.params.cat;
        db.get(sql, [id], (err, row) => {
                if (err) throw err;
                res.status(200).send(String(row.currentUsage));
        });

app.get('/cats/:cat/checkusage', function(req, res) {
        let sql = 'select feedCap, currentUsage, modified, datetime(\'now\',\'-10 minutes\')$
        let id = req.params.cat;
        db.get(sql, [id], (err, row) => {
                if (err) throw error;
                if (((row.currentUsage < row.feedCap) || (row.feedCap == 0)) && (row.modifie$
                        res.status(200).send('OK');
                } else {
                        res.status(200).send('No');
                }
        });
});

app.put('/cats/update', function(req, res) {
        let sql = 'update cats set currentUsage = currentUsage + 1, modified = DATETIME(\'no$
        let id = req.body.cat;
        db.run(sql, [id], function(err){
                if (err) throw err;
                res.status(200).send('Ok');
        });
});

app.get('*', function(req, res) {
        res.status(404).send('Unrecognized API call');
});

app.use(function(err, req, res, next) {
        if (req.xhr) {
                res.status(500).send('Oops, something went wrong!');
        } else {
                next(err);
        }
});

app.listen(3000);
console.log('App Server running at port 3000');

I'd love to hear feedback. This removes the dependency on the ESP8266 running calculations and now it just reads and responds.

You may notice that the checkusage API function is set to check if there's been a feed update in the last 10 minutes or not. I currently have this set to 10 minutes for training purposes. I'll put a small amount of treats in by hand and get them used to the dispensing process. This will change to several hours down the road. I also set their daily caps to 0 so as to give free reign during training. Also, if you were curious, here's the database entries for the cats:

id |catNumber|catName|feedCap|currentUsage|modified
0 |0 |Medli |0 |0 |2019-08-15 00:57:12
1 |1 |Majora |0 |0 |2019-08-03 22:06:56
2 |2 |Pixel |0 |1 |2019-08-15 03:05:56

maybe I missed it, but how often does it crash, and is it always accompanied with a stack dump?

SteveMann:
maybe I missed it, but how often does it crash, and is it always accompanied with a stack dump?

It varies on when the crash occurs. Sometimes it'll be stable for several minutes, other times it crashes and reboots repeatedly. Yes stack dumps/traces always occur when I have the board plugged into the computer. I've attached stack dumps to some of my previous posts. There's one specifically on August 4th that contains a sample. If you need something more recent I can get it when I get home. The stack trace occasionally varies but only slightly. It's usually a complaint about a watchdog timer or a malloc, which I'm not familiar enough to troubleshoot since I didn't actually use elements of that type in the sketch.

wbmattt:
It's usually a complaint about a watchdog timer

wdt automatically reboots the ESP when 2.5s there has been no reset of the wdt. delay() resets the wdt, yield() does as well (actually delay() calls yield() ) and at the end of loop() an automatic yield() is executed. Yield() starts executing background processes that the ESP needs to keep functioning. Try adding some yield() 's in places where there are loops taking up a fair amount of time

wbmattt:
or a malloc,

stands for memory allocation (i think) which probably means you are writing data into an area that is reserved for something else.
Actually this part of your sketch is still suspicious to me

if (RFID.available() > 0) {
    firstByte = RFID.read();
    while (firstByte != 2) {
      firstByte = RFID.read(); //throw away any bytes that aren't the beginning of the array
    }
    if (firstByte == 2) {
      readTags(firstByte);
    }
  }

mainly the while loop, you read from the RFID if the first byte isn't the one you are looking for, you don't check to see if there are other bytes available, but just go on reading another one to compare that. If there is nothing there you may read from parts of memory that might ave nothing ot do with the buffer (i don't know the library that well) and your condition may never be true, causing an endless loop and a wdt reset.
in other words,change that to

while ((firstByte != 2) && (RFID.available() )){
      firstByte = RFID.read(); //throw away any bytes that aren't the beginning of the array
      yield();
    }

Deva_Rishi:
in other words,change that to

while ((firstByte != 2) && (RFID.available() )){

firstByte = RFID.read(); //throw away any bytes that aren't the beginning of the array
      yield();
    }

What exactly does yield() do and where else should I use it? I'm not familiar with the concept of yielding other than the delay() function does something with it, at least as far as I understand.

EDIT: I found an article on yield() and I'll be implementing it in any of the other loops I have set up as they could all potentially time out the watchdog. Fascinating.

In this section:

for (int z = 1 ; z < 14 ; z++) // read the rest of the tag
  {
    if (RFID.available()) newtag[z] = RFID.read();
  }

is it best to leave as is or add a yield()?

for (int z = 1 ; z < 14 ; z++) // read the rest of the tag
  {
    if (RFID.available()) newtag[z] = RFID.read();
    yield();
  }

That code will take somewhere around 2 milliseconds to run. So it won't in itself cause the watchdog to time out. However, if you have other blocking code that runs before or after this then their combined execution times could accumulate to exceed the watchdog timeout duration. Certainly you don't need to call yield() inside the for statement as you have in the second version of the code above.

wbmattt:
In this section:

for (int z = 1 ; z < 14 ; z++) // read the rest of the tag

{
    if (RFID.available()) newtag[z] = RFID.read();
  }



is it best to leave as is or add a yield()?

i agree with pert, adding yield() is excessive, though what you should consider is when you reaad the rest of the tag, will it all be available in the buffer already all the time ? and if not what is the result for the data of the tag that has been read ?

About yield() i tend to add it to the end of functions and within espwebserver callbacks that use the String class (it can be time consuming, though never enough to cause a wdt timeout reboot) By executing the scheduled tasks regularly the wifi connectivity does become more stable, and really, yield() doesn't do anything if there is nothing to be done

I took your suggestions into account and only made the changes recommended.

Facebook post with pics and video of operation

Google drive link instead

I don't think it looks half bad if I do say so myself.