Circular Buffer For Handling Rapid Particle Events

If you know the max number of possible entries in the list and array is the easiest way to do it.
And by default global variables (including arrays) are filled with all-zero values.
If you need any other initial values, you can do that static in the variable declaration section or in setup().

1 Like

@ScruffR

I made some progress but I think I am missing something here.

#include <PublishManager.h>

PublishManager<20,5,70> publishManager;

const unsigned long interval = 8000;

String testers[4] = {"TestTrackTag01", "TestTrackTag02", "TestTrackTag03", "TestTrackTag04"};

unsigned long testersInterval[4] = {0,0,0,0};

void setup() {
    Serial1.begin(115200);
}

void loop() {
    unsigned long currentMillis = millis();
    if(Serial1.available()){
        String s = Serial1.readStringUntil('\n'); // From Sparkfun RedBoard
        //Serial.println(s.c_str());
        for (int i = 0; i < 4; i++){
        //   if (strncmp(s, testers[i], 14) == 0 && currentMillis - testersInterval[i] >= interval){
        //         testersInterval[i] = currentMillis;
        //         publishWithTimeStamp("Lap", s);
        //     } 
            if (currentMillis - testersInterval[i] >= interval){
                publishWithTimeStamp("Lap", s);
                testersInterval[i] = currentMillis; 
            }
        }
    } 
}

void publishWithTimeStamp(String eventName, String data){
        char buffer[255];

        sprintf(buffer, "{\"tag\": \"%s\", \"time\": %u}",data.c_str(), Time.now());

        publishManager.publish(eventName, buffer);
} 

As soon as load the firmware and hold on RFID tag up to the reader it will wait for about 8 seconds and then publish the tag. BUT only once and all other tags are ignored until I reset the photon. I don’t know why I am struggling with this so much. What I was hoping it would do is:

Did I find a tag that matches one in the list?
If yes:
Publish the tag read as a lap
Then make this one wait for about 8 seconds before I care about when I see it next.

So, I did something similar… using std::vector. I’ve adapted for your application.

I think I’ve done a good job documenting the code with good variable names, but just ask if you have questions.

// This #include statement was automatically added by the Particle IDE.
#include <PublishManager.h>
#include <vector>

constexpr size_t MAX_MESSAGE_LENGTH = 32;
constexpr uint16_t CROSSING_HYSTERESIS_SECONDS = 3;

PublishManager<> publishManager;

struct Runner {
    char runnerTag[32];
    uint32_t ts;
};

std::vector<Runner> runners;

const char* knownRunners[] = {"TestTrackTag01", "TestTrackTag02", "TestTrackTag03", "TestTrackTag04"};

void setup() {
    Serial1.begin(115200);
}

void loop() {
    publishManager.process();
    if (const char* crossingRunner = checkForNewRunner('\n')) {
        uint32_t crossingTime = Time.now();
        bool isKnownRunner = false;
        for (auto& t : knownRunners) {
            if (strcmp(crossingRunner, t) == 0) {
                isKnownRunner = true;
            }
        }
        if (isKnownRunner) {
            Runner newRunner;
            strcpy(newRunner.runnerTag, crossingRunner);
            newRunner.ts = crossingTime;
            for (size_t i = 0; i < runners.size(); i++) {
                if (strcmp(runners[i].runnerTag, newRunner.runnerTag) == 0) {
                    runners.erase(runners.begin() + i);
                }
            }
            runners.insert(runners.begin(), newRunner);
        }
    }
    
    if (runners.size()) {
        if (Time.now() - runners.back().ts > CROSSING_HYSTERESIS_SECONDS) {
            char pubMessage[64];
            sprintf(pubMessage, "{\"tag\":\"%s\",\"time\":%d}", runners.back().runnerTag, runners.back().ts);
            publishManager.publish("Lap", pubMessage);
            runners.pop_back();
        }
    }
    
}

const char* checkForNewRunner(const char endMarker) {
  static char incomingMessage[MAX_MESSAGE_LENGTH] = "";
  static byte idx = 0;
  if (Serial1.available()) {
    incomingMessage[idx] = Serial1.read();
    if (incomingMessage[idx] == endMarker) {
      incomingMessage[idx] = '\0';
      idx = 0;
      return incomingMessage;
    } else {
      idx++;
      if (idx > MAX_MESSAGE_LENGTH - 1) {
        //stream.print(F("{\"error\":\"message too long\"}\n"));  //you can send an error to sender here
        idx = 0;
        incomingMessage[idx] = '\0';
      }
    }
  }
  return nullptr;
}
3 Likes

This looks brilliant!

Not 100% sure what every bit means yet but I will google it to learn. Thanks a bunch for jumping in with great input! I will ask questions I’m sure.

1 Like

@BulldogLowell I uploaded it for fun. I am not getting any tag reads to the console. I will put some serial prints in to see what is going on programmatically. Any good C++ books you would recommend?

Let me make some changes That will allow you to debug a little easier.

// This #include statement was automatically added by the Particle IDE.
#include <PublishManager.h>
#include <vector>

constexpr size_t MAX_MESSAGE_LENGTH = 32;
constexpr uint16_t CROSSING_HYSTERESIS_SECONDS = 3;

PublishManager<> publishManager;

const unsigned long interval = 8000;

struct Runner {
    char runnerTag[32];
    uint32_t ts;
};

std::vector<Runner> runners;

const char* knownRunners[] = {"TestTrackTag01", "TestTrackTag02", "TestTrackTag03", "TestTrackTag04"};

void setup() {
    Serial.begin(9600);  //<<<<< don't forget!
    Serial1.begin(115200);
}

void loop() {
    publishManager.process();
    if (const char* crossingRunner = checkForNewRunner('\n')) {
        Serial.printf("%s just crossed the line...", crossingRunner);
        uint32_t crossingTime = Time.now();
        bool isKnownRunner = false;
        for (auto& t : knownRunners) {
            if (strcmp(crossingRunner, t) == 0) {
                isKnownRunner = true;
                Serial.printf("%s is known to me", crossingRunner);
            }
        }
        if (isKnownRunner) {
            Runner newRunner;
            strcpy(newRunner.runnerTag, crossingRunner);
            newRunner.ts = crossingTime;
            for (size_t i = 0; i < runners.size(); i++) {
                if (strcmp(runners[i].runnerTag, newRunner.runnerTag) == 0) {
                    runners.erase(runners.begin() + i);
                    Serial.printf("%s was in the queue.", crossingRunner);
                }
            }
            runners.insert(runners.begin(), newRunner);
            Serial.printf("%s added to the queue.", crossingRunner);
        }
    }
    
    if (runners.size()) {
        if (Time.now() - runners.back().ts > CROSSING_HYSTERESIS_SECONDS) {
            char pubMessage[64];
            sprintf(pubMessage, "{\"tag\":\"%s\",\"time\":%d}", runners.back().runnerTag, runners.back().ts);
            publishManager.publish("Lap", pubMessage);
            Serial.printf("%s sent to publishManager with a time of: %d.", runners.back().runnerTag , runners.back().ts);
            runners.pop_back();
        }
    }
    
}

const char* checkForNewRunner(const char endMarker) {
  static char incomingMessage[MAX_MESSAGE_LENGTH] = "";
  static byte idx = 0;
  if (Serial1.available()) {
    incomingMessage[idx] = Serial1.read();
    if (incomingMessage[idx] == endMarker) {
      incomingMessage[idx] = '\0';
      idx = 0;
      return incomingMessage;
    } else {
      idx++;
      if (idx > MAX_MESSAGE_LENGTH - 1) {
        //stream.print(F("{\"error\":\"message too long\"}\n"));  //you can send an error to sender here
        idx = 0;
        incomingMessage[idx] = '\0';
      }
    }
  }
  return nullptr;
}

try this as is and let me know…

just crossed the line...TestTrackTag02.

Is what comes across in the serial monitor. So it’s reading the tags. The publish does not occur.

I would also like to mention that I do appreciate your assistance. I found a good book at the store but it’s like $80. So I read through it but couldn’t see how relevant it was next to your code.

For example, their use of Vector

std::vector<int> = myVec

There was no example to show how to use it for other cases. Like Runner.

std::vector<this_is_the_kind_of_object_of_which_the_vector_stores> = this_is_the_name

and the other events...? Did they get reported?

No just "Crossed the line... TestTrackTag" n being which ever tag is read. But this is in Serial Only. So nothing is pub'd.

so now the next block then… we have to search the tag to make sure it is on the known list (i.e. error check).

  bool isKnownRunner = false;
  for (auto& t : knownRunners) {
    if (strcmp(crossingRunner, t) == 0) {
      isKnownRunner = true;
        Serial.printf("%s is known to me", crossingRunner);
    }
  }

are you using my code EXACTLY as I just gave it to you?

…by the way, this worked for me (published)

Exactly as provided. Literally did a copy and paste.

let's eliminate the effects of the check by changing this:

bool isKnownRunner = false;

to this:

bool isKnownRunner = true;

Just made the change as your described and now two tags have pub’d to the cloud!

sent to publishManager with a time of: 1526350706.TestTrackTag02

Do you recon using printlnf would make more space for the Serial output?

OK, I think that there is a carriage return and a line feed coming in from your tag reader… yes?

Yes I believe \n is at the end as I am using ptSerial.println(); at the end of the read on the arduino with the tag reader on it.

#include "SparkFun_UHF_RFID_Reader.h"
#include <SoftwareSerial.h>

SoftwareSerial rfSerial(2,3);
SoftwareSerial ptSerial(8,9);

RFID rfid;

void setup()
{
  Serial.begin(115200);
  ptSerial.begin(115200);

  Serial.println("Initializing...");
  ptSerial.println("Initializing...");

  if (setupNano(38400) == false)
  {
    Serial.println("Check Wiring");
    ptSerial.println("Check Wiring");
    while (1);
  }

  rfid.setRegion(REGION_NORTHAMERICA);
  rfid.setReadPower(500); // 5.00 dBm

  Serial.println("Starting Scan...");
  ptSerial.println("Starting Scan");

  rfid.startReading();
}

void loop()
{
  if (rfid.check() == true)
  {
    byte responseType = rfid.parseResponse();

    if (responseType == RESPONSE_IS_KEEPALIVE)
    {
      Serial.println(F("Scanning"));
      //ptSerial.println(F("Scanning"));
    }
    else if (responseType == RESPONSE_IS_TAGFOUND)
    {
      //If we have a full record we can pull out the fun bits
      int rssi = rfid.getTagRSSI(); //Get the RSSI for this tag read
      long freq = rfid.getTagFreq(); //Get the frequency this tag was detected at
      long timeStamp = rfid.getTagTimestamp(); //Get the time this was read, (ms) since last keep-alive message
      byte tagEPCBytes = rfid.getTagEPCBytes(); //Get the number of bytes of EPC from response

      Serial.print(F(" rssi["));
      Serial.print(rssi);
      Serial.print(F("]"));

      Serial.print(F(" freq["));
      Serial.print(freq);
      Serial.print(F("]"));

      Serial.print(F(" time["));
      Serial.print(timeStamp);
      Serial.print(F("]"));

//      ptSerial.print(F("RSSI: "));
//      ptSerial.print(rssi);
//      ptSerial.print(F(", "));
//
//      ptSerial.print(F("Freq: "));
//      ptSerial.print(freq);
//      ptSerial.print(F(", "));

      //Print EPC bytes, this is a subsection of bytes from the response/msg array
      Serial.print(F("Tag: "));
      for (byte x = 0 ; x < tagEPCBytes ; x++)
      {
        if (rfid.msg[31 + x] < 0x10) Serial.print(F("0"));//Pretty print
        Serial.print((char) rfid.msg[31 + x]);
        ptSerial.print((char) rfid.msg[31 + x]);
      }
      Serial.print(F(" "));
      Serial.println();
      ptSerial.println();
    }
    else if (responseType == ERROR_CORRUPT_RESPONSE)
    {
      Serial.println(F("Bad CRC"));
      ptSerial.println(F("Bad CRC"));
    }
    else
    {
      Serial.println(F("Unknown Error"));
      ptSerial.println(F("Unknown Error"));
    }
  }
}

boolean setupNano(long baudRate)
{
  rfid.begin(rfSerial);
  rfSerial.begin(baudRate);

  while (!rfSerial);
  while (rfSerial.available()) rfSerial.read();

  rfid.getVersion();

  if (rfid.msg[0] == ERROR_WRONG_OPCODE_RESPONSE)
  {
    rfid.stopReading();
    delay(1500);
  }
  else
  {
    rfSerial.begin(115200);
    rfid.setBaud(baudRate);
    rfSerial.begin(baudRate);
  }

  rfid.getVersion();

  if (rfid.msg[0] != ALL_GOOD) return (false);
  rfid.setTagProtocol();
  rfid.setAntennaPort();
  return true;
}

The Arduino code…

yes, but you were not error checking, as i did.

Can you then try one more thing? This is my original version but I ignore a carriage return when buffering the Serial1...

// This #include statement was automatically added by the Particle IDE.
#include <PublishManager.h>
#include <vector>

constexpr size_t MAX_MESSAGE_LENGTH = 32;
constexpr uint16_t CROSSING_HYSTERESIS_SECONDS = 3;

PublishManager<> publishManager;

const unsigned long interval = 8000;

struct Runner {
    char runnerTag[32];
    uint32_t ts;
};

std::vector<Runner> runners;

const char* knownRunners[] = {"TestTrackTag01", "TestTrackTag02", "TestTrackTag03", "TestTrackTag04"};

void setup() {
    Serial1.begin(115200);
}

void loop() {
    publishManager.process();
    if (const char* crossingRunner = checkForNewRunner('\n')) {
        Serial.printf("%s just crossed the line...", crossingRunner);
        uint32_t crossingTime = Time.now();
        bool isKnownRunner = false;
        for (auto& t : knownRunners) {
            if (strcmp(crossingRunner, t) == 0) {
                isKnownRunner = true;
                Serial.printf("%s is known to me", crossingRunner);
            }
        }
        if (isKnownRunner) {
            Runner newRunner;
            strcpy(newRunner.runnerTag, crossingRunner);
            newRunner.ts = crossingTime;
            for (size_t i = 0; i < runners.size(); i++) {
                if (strcmp(runners[i].runnerTag, newRunner.runnerTag) == 0) {
                    runners.erase(runners.begin() + i);
                    Serial.printf("%s was in the queue.", crossingRunner);
                }
            }
            runners.insert(runners.begin(), newRunner);
            Serial.printf("%s added to the queue.", crossingRunner);
        }
    }
    
    if (runners.size()) {
        if (Time.now() - runners.back().ts > CROSSING_HYSTERESIS_SECONDS) {
            char pubMessage[64];
            sprintf(pubMessage, "{\"tag\":\"%s\",\"time\":%d}", runners.back().runnerTag, runners.back().ts);
            publishManager.publish("Lap", pubMessage);
            Serial.printf("%s sent to publishManager with a time of: %d.", runners.back().runnerTag , runners.back().ts);
            runners.pop_back();
        }
    }
    
}

const char* checkForNewRunner(const char endMarker) {
  static char incomingMessage[MAX_MESSAGE_LENGTH] = "";
  static byte idx = 0;
  if (Serial1.available()) {
    if (Serial1.peek() == '\r') { // ditch the <CR>
      Serial1.read();
      return nullptr;
    }
    incomingMessage[idx] = Serial1.read();
    if (incomingMessage[idx] == endMarker) {
      incomingMessage[idx] = '\0';
      idx = 0;
      return incomingMessage;
    } else {
      idx++;
      if (idx > MAX_MESSAGE_LENGTH - 1) {
        //stream.print(F("{\"error\":\"message too long\"}\n"));  //you can send an error to sender here
        idx = 0;
        incomingMessage[idx] = '\0';
      }
    }
  }
  return nullptr;
}