Stephanie VicarteIrak MayerElizabeth Vicarte
Published © GPL3+

No Kid Left Behind

A system that keeps track of attendees and attendance on field trips faster and easier, and more!

IntermediateFull instructions providedOver 1 day1,045

Things used in this project

Hardware components

Adafruit HUZZAH ESP8266 Breakout
Adafruit HUZZAH ESP8266 Breakout
×6
Adafruit HUZZA ESP32 Feather board
×3
Adafruit Feather HUZZAH with ESP8266 WiFi
Adafruit Feather HUZZAH with ESP8266 WiFi
×1
Adafruit TFT FeatherWing - 2.4" 320x240 Touchscreen For All Feathers
×1
Adafruit Lithium Ion Polymer Battery - 3.7v 500mAh
×10

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Master Location Dashboard Diagram

Code

Master Location Dashboard

Arduino
This module keeps track of all the range comm badges.
//Master Location Dashboard. 

//The program controls up to 20 devices internally and 15 devices on screen
//It uses the SD card to log data into two different files. 
//DataRSSI file contains the RSSI values of the AP devices in the ESPNow channel.
//DataPing file contains the packet latency akcnowledgement time for each one of the data packages sent.
//It keeps track of all the devices connected by verifying that their SSID format matches the costum string.
//The SSID is formatted as TOURNAME:COSTUMERNAME:MAC_ADDRESS. Where TOURNAME is no longer than 6 characters, 
//COSTUMERNAME is up to 5 characters and the MAC_ADDRESS is a six touple value.
//The graphical interface supports touch screen input and displays up to 15 devices. It allows the user to select one 
//device and display MAC address, costumer name assigned and tour name.
//Finally, the program alerts in case any of the devices losing connection and shows the costumer name it was assigned to.

//NOTE: Performanace improvement remove all the serial print commands.

//This program uses as a basic template the master.ino program from the ESP32\EspNow 

#include <SPI.h>
#include <Wire.h>      
#include <SD.h>

#include <Adafruit_GFX.h>         // Core graphics library
#include <Adafruit_ILI9341.h>     // Hardware-specific library
#include <Adafruit_STMPE610.h>

//ESPNow library
#include <esp_now.h>
#include <WiFi.h>

//Tour name. Sample: WAMALL --> Washington Mall
#define TOURNAME "WAMALL"

// Pin definitions for 2.4" TFT FeatherWing vary among boards...
//We could have got away with just the ESP32, but we could have used a ESP8266. Left for future use.
#if defined(ESP8266)
  #define TFT_CS   0
  #define TFT_DC   15
  #define SD_CS    2
#elif defined(ESP32)
  #define TFT_CS   15
  #define TFT_DC   33
  #define SD_CS    14
#endif

#ifdef ESP32
   #define STMPE_CS 32
   #define TFT_CS   15
   #define TFT_DC   33
   #define SD_CS    14
#endif

// This is calibration data for the raw touch data to the screen coordinates
#define TS_MINX 3800
#define TS_MAXX 100
#define TS_MINY 100
#define TS_MAXY 3750

#define CHANNEL 4
#define PRINTSCANRESULTS 0

// Global copy of slave
#define NUMSLAVES 20
//Device distance range status constants
#define STATUS_HERE     0
#define STATUS_LOST     1
#define STATUS_CLOSE    2
#define STATUS_FAR      3

//Tailored structure to keep track of the connected devices.
typedef struct nodeData{
  String  nodeSSID;
  String  tourName;
  String  costumerName;
  int     Status;
  int     Current_RSSI;
};

//Filename definitions and constants
#define UNIQUE_ID_FILENAME "/FileNameId.txt"
#define FILE_RSSI "/DataRSSI_"
#define FILE_PING "/DataPing_"
#define FILE_EXT  ".vis"

bool openSDFlg=false;
bool openRSSIFlg=false;
bool openPingFlg=false;
File fnCounter;
File dataRSSI;
File dataPing;
String fnPing;
String fnRSSI;
int fnCounterId =0; //Automatic incremental file id to generate unique files every time it runs

bool PanicFlag=false; //Alert a device(s) have lost connection
bool ClearDisFlag=false;
bool NeedRefresh = false;
//Costum array data for each device connected
nodeData peerNodeData[NUMSLAVES];

//Internal device connection
esp_now_peer_info_t slaves[NUMSLAVES] = {};
int checkConCounter[NUMSLAVES];
int SlaveCnt = 0;
int PrevSel = 0;

//Instantiation of the display and touch screen interface objects.
Adafruit_ILI9341  tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
Adafruit_STMPE610 ts  = Adafruit_STMPE610(STMPE_CS);
//Timing variables
unsigned long start =0;
unsigned long startPeer[NUMSLAVES];
unsigned long refreshTime =0;
//Variables that defines the display dimensions.
int rectHeight;
int rectWidth;
int radius;
//Define constants for device status
#define STATUS_UNKNOWN  0
#define STATUS_OK       1
#define STATUS_WARNING  2
#define STATUS_DANGER   3
//Define constants for radio frequency strenght
#define RANGE_OK        45
#define RANGE_WARNING   69
#define RANGE_DANGER    70

//More display constants to grid the screen
#define DISCON_MAX  5
#define GRID_MAX_X    5
#define GRID_MAX_Y    3
#define USE_SCREEN_Y    1 
#define USE_SCREEN_DIV  2 
//Amount of time to rescan the network
#define REFRESH_THRESHOLD 3000000
//More display variables
int OffsetX=0;
int OffsetY=0;
//Debug control flags. Did not work that well.
#define DEBUG_ESPNOW  1
//#define DEBUG_TS      1
//Device selected flag.
int FlgSelect = 0;

//Function that initializes the file logging.
//Opens an index file that contains the sequential unique counter to be attached to the login filename, to create a unique filename each time the device runs.
void InitLog()
{
  if(!SD.begin(SD_CS)) 
  {
    Serial.println(F("failed!"));
    openSDFlg = false;
  }
  else
  {
    uint8_t cardType = SD.cardType();
    Serial.println("SD OK");
    if(cardType == CARD_NONE){
        Serial.println("No SD card attached");
        openSDFlg = false;
    }
    else
    {
      static uint8_t buf[512];
      Serial.println("Check file Exists");
      if (SD.exists(UNIQUE_ID_FILENAME))
      {
        Serial.println("File Exists");
        fnCounter = SD.open(UNIQUE_ID_FILENAME);
        if (fnCounter)
        {
          Serial.println("Open File and read");
          Serial.println(fnCounter.size());
          fnCounter.read(buf,fnCounter.size());
          Serial.println(String((char *)buf));
          fnCounterId = String((char *)buf).toInt();
          Serial.print("Counter: ");
          Serial.println(fnCounterId);
          fnCounter.close();
          fnCounter=SD.open(UNIQUE_ID_FILENAME,FILE_WRITE);
          fnCounter.print(String(fnCounterId+1));
          fnCounter.close();
        }
      }
      else
      {
        Serial.println("Create File");
        fnCounter = SD.open(UNIQUE_ID_FILENAME,FILE_WRITE);
        if (fnCounter)
        {
          Serial.println("File open for write");
          fnCounter.print("0");
          fnCounter.close();
        }
        else
          Serial.println("Error");
        fnCounterId =0;
      }

      String tmpStr = FILE_RSSI;
      tmpStr.concat(String(fnCounterId));
      tmpStr.concat(FILE_EXT);
      fnRSSI = tmpStr;
      dataRSSI = SD.open(tmpStr,FILE_APPEND);
      if (dataRSSI)
      {
        Serial.println("RSSI ready");
        openRSSIFlg = true;
        //dataRSSI.print("THIs is atest");
        dataRSSI.flush();
      }

      tmpStr = FILE_PING;
      tmpStr.concat(String(fnCounterId));
      tmpStr.concat(FILE_EXT);
      fnPing = tmpStr;
      dataPing = SD.open(tmpStr,FILE_APPEND);
      if (dataPing)
      {
        Serial.print(tmpStr);
        Serial.println("Ping ready");
        openPingFlg = true;
        //dataPing.print("THIs is atest");
        dataPing.flush();
      }
      
      openSDFlg = true;
    }
  }
  
}

//This function cycles the close/open process of the logging files.
//This function is needed due to the flush function now working. The function is call every 3 seconds and dumps the buffer to the files.
void RefreshFiles()
{
    dataRSSI.close();
    dataRSSI = SD.open(fnRSSI,FILE_APPEND);
    if (dataRSSI)
    {
      Serial.println("RSSI ready");
      openRSSIFlg = true;
      dataRSSI.flush();
    }
    else
      openPingFlg = false;

    dataPing.close();
    dataPing = SD.open(fnPing,FILE_APPEND);
    if (dataPing)
    {
      Serial.println("Ping ready");
      openPingFlg = true;
      dataPing.flush();
    }
    else
      openPingFlg = false;
}

//This function updates the status of the display interface and logs the changes per device.
void UpdateStatus(int Pos,int numTotX,int Status,int offsetPos,bool refreshFile)
{
  int PosX = Pos%numTotX;
  int PosY = Pos/numTotX;
  int startY = rectHeight * offsetPos;
  String tmpStr;
  
  switch (Status)
  {
    case STATUS_UNKNOWN:
        tft.fillCircle((PosX*rectWidth)+rectWidth/2,startY + (PosY*rectHeight)+rectHeight/2,radius,ILI9341_WHITE);
        peerNodeData[Pos].Status = STATUS_LOST;
        if (openRSSIFlg && refreshFile)
        {
          tmpStr = "Status : ALERT LOST "+peerNodeData[Pos].costumerName+" "+peerNodeData[Pos].nodeSSID+" "+String(micros()/1000);
          dataRSSI.println(tmpStr);
          dataRSSI.flush();
          Serial.print(tmpStr);
        }
      break;
    case STATUS_OK:
        tft.fillCircle((PosX*rectWidth)+rectWidth/2,startY + (PosY*rectHeight)+rectHeight/2,radius,ILI9341_GREEN);
        peerNodeData[Pos].Status = STATUS_HERE;
        if (openRSSIFlg && refreshFile)
        {
          tmpStr = "Status : Here "+peerNodeData[Pos].costumerName+" "+peerNodeData[Pos].nodeSSID+" "+String(peerNodeData[Pos].Current_RSSI)+" "+String(micros()/1000);
          dataRSSI.println(tmpStr);
          dataRSSI.flush();
          Serial.print(tmpStr);
        }
      break;
    case STATUS_WARNING:
        tft.fillCircle((PosX*rectWidth)+rectWidth/2,startY + (PosY*rectHeight)+rectHeight/2,radius,ILI9341_YELLOW);
        peerNodeData[Pos].Status = STATUS_CLOSE;
        if (openRSSIFlg && refreshFile)
        {
          tmpStr = "Status : Close "+peerNodeData[Pos].costumerName+" "+peerNodeData[Pos].nodeSSID+" "+String(peerNodeData[Pos].Current_RSSI)+" "+String(micros()/1000);
          dataRSSI.println(tmpStr);
          dataRSSI.flush();
          Serial.print(tmpStr);
        }
      break;
    case STATUS_DANGER:
        tft.fillCircle((PosX*rectWidth)+rectWidth/2,startY + (PosY*rectHeight)+rectHeight/2,radius,ILI9341_RED);
        peerNodeData[Pos].Status = STATUS_FAR;
        if (openRSSIFlg && refreshFile)
        {
          tmpStr = "Status : Far "+peerNodeData[Pos].costumerName+" "+peerNodeData[Pos].nodeSSID+" "+String(peerNodeData[Pos].Current_RSSI)+" "+String(micros()/1000);
          dataRSSI.println(tmpStr);
          dataRSSI.flush();
          Serial.print(tmpStr);
        }
      break;
  }
  tft.fillCircle((PosX*rectWidth)+rectWidth/2,startY + (PosY*rectHeight)+rectHeight/2,radius-4,ILI9341_BLACK);
}

//This function selects the node on the display according to the touch screen cordinates passed.
//The function tries to flush all the commands in the touch screen stack, before returning.
void SelectNode(TS_Point posScreen,int offsetPos)
{
  int posX;
  int posY;
  int pos;
  TS_Point p;
  int startY = rectHeight * offsetPos;

  posX = posScreen.x /rectWidth ;
  posY = posScreen.y /rectHeight ;
  pos = posX + (posY*GRID_MAX_X);

#ifdef DEBUG_TS
  Serial.print("X = "); Serial.print(posScreen.x);
  Serial.print("\tY = "); Serial.println(posScreen.y);
  Serial.print("PX = "); Serial.print(posX);
  Serial.print("\tPY = "); Serial.println(posY);
  Serial.print("Pos = "); Serial.print(pos);
  Serial.print("\tPrev = "); Serial.println(PrevSel);
#endif

  if ((pos >= offsetPos && pos < offsetPos+SlaveCnt) && pos != PrevSel)
  {
     
    tft.drawRect((PrevSel%GRID_MAX_X)*rectWidth,startY+(PrevSel/GRID_MAX_X)*rectHeight,rectWidth,rectHeight,ILI9341_WHITE);
    tft.drawRect(posX*rectWidth,startY+(posY*rectHeight),rectWidth,rectHeight,ILI9341_BLUE);
    PrevSel = pos;
  }

  if (FlgSelect > 1 && (posScreen.x > tft.width()-rectWidth && posScreen.y > tft.height()-rectHeight)) 
  {
    FlgSelect=0;
    tft.fillRect(tft.width()-rectWidth,startY+(tft.height()-rectHeight), rectWidth,rectHeight, ILI9341_BLACK);
    tft.drawRect(tft.width()-rectWidth,startY+(tft.height()-rectHeight),rectWidth,rectHeight,ILI9341_CYAN);
    ClearScreen();
    CreateGrid(GRID_MAX_X,GRID_MAX_Y,0);
    CreateGrid(GRID_MAX_X,GRID_MAX_Y,3);
  }
  else
    FlgSelect++;

  while (ts.touched())
  {
    p = ts.getPoint();
  }
    //delay(100);
  while (!ts.bufferEmpty())
  {
    p = ts.getPoint();
  }
}

//Creates the grid on the display according to the number of bins to supper per row and column
void CreateGrid(int numNodesHorz, int numNodesVert,int offsetPos)
{
  int i,j;
  int cx = USE_SCREEN_Y*(tft.height()/USE_SCREEN_DIV)-1;
  int startY=0;

  rectHeight = cx / numNodesVert;
  rectWidth = tft.width() / numNodesHorz;
  radius = (((rectHeight < rectWidth)? rectHeight: rectWidth)/2)-2;
  startY = rectHeight * offsetPos;

  for(i=0;i<numNodesVert;i++)
  {
    for (j=0;j<numNodesHorz;j++)
    {
      tft.drawRect(j*rectWidth,startY+(i*rectHeight),rectWidth,rectHeight,ILI9341_WHITE);
      tft.fillCircle((j*rectWidth)+rectWidth/2,startY+((i*rectHeight)+rectHeight/2),radius,ILI9341_WHITE);
      tft.fillCircle((j*rectWidth)+rectWidth/2,startY+((i*rectHeight)+rectHeight/2),radius-4,ILI9341_BLACK);
    }
  }

  tft.drawRect(tft.width()-rectWidth,tft.height()-rectHeight,rectWidth,rectHeight,ILI9341_CYAN);
  
}

//This function clear the screen and sets the default values for text.
void ClearScreen()
{
  tft.fillScreen(ILI9341_BLACK);
  tft.setCursor(0, 2);
  tft.setTextColor(ILI9341_WHITE,ILI9341_BLACK);  
  tft.setTextSize(2);  
}

//Refresh the status of the cube, depending on the RSSI value passed.
void RefreshCube(int Pos,int Value,int offsetPos, bool refreshFile)
{
   if (Value <= RANGE_OK)
      UpdateStatus(Pos,GRID_MAX_X,STATUS_OK,offsetPos,refreshFile);
   else if (Value <= RANGE_WARNING)
      UpdateStatus(Pos,GRID_MAX_X,STATUS_WARNING, offsetPos,refreshFile);
   else if (Value >= RANGE_DANGER)
      UpdateStatus(Pos,GRID_MAX_X,STATUS_DANGER,offsetPos,refreshFile);
   else
      UpdateStatus(Pos,GRID_MAX_X,STATUS_UNKNOWN,offsetPos,refreshFile);
}

//This function runs over the array of devices and check the number of consecutive attempts to connect to the device that went unsuccesfull
void ConfirmConnection(int offsetPos)
{
  bool foundDisconnect=false;
  for(int i=0;i<SlaveCnt;i++)
  {
     if (checkConCounter[i] >  DISCON_MAX)
     {
        UpdateStatus(i,GRID_MAX_X,STATUS_UNKNOWN,offsetPos,false);
        PanicFlag = true;
        foundDisconnect = true;
     }
  }

  if (!foundDisconnect)
  {
    PanicFlag= false;
    ClearDisFlag=false;
    if (NeedRefresh)
    {
      NeedRefresh=false;
      ClearScreen();
      CreateGrid(GRID_MAX_X,GRID_MAX_Y,0);
      CreateGrid(GRID_MAX_X,GRID_MAX_Y,3);
    }
  }
  else 
  {
    String tmpStr="Lost: ";
    bool FirstTime=true;

    tft.fillRect(0,tft.height()/2, tft.width(),tft.height()/2, ILI9341_BLACK);    
    tft.setTextColor(ILI9341_RED);  
    tft.setTextSize(3);
    printDisplay(15,"ALERT",0);
    tft.setTextColor(ILI9341_WHITE);  
    tft.setTextSize(2);
    for (int i=0;i<SlaveCnt;i++)
    {
      if (peerNodeData[i].Status == STATUS_LOST)
      {
        if (!FirstTime)
        {
          tmpStr.concat(",");
        }
        FirstTime=false;
        tmpStr.concat(peerNodeData[i].costumerName);
      }
    }
    printDisplay(20,tmpStr,0);    
    tft.setTextColor(ILI9341_WHITE);  
    tft.setTextSize(2);
    delay(200);

    if (!ClearDisFlag)
    {
      ClearScreen();
      CreateGrid(GRID_MAX_X,GRID_MAX_Y,0);
      ClearDisFlag=true;
      NeedRefresh=true;
    }
  }
}

//Initialize ESPNow protocol
void InitESPNow() 
{
  WiFi.disconnect();
  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init Success");
  }
  else {
    Serial.println("ESPNow Init Failed");
    ESP.restart();
  }
}

//This function searches in the internal array if the peer is already register
int searchPeer(int peerMAC[])
{
  int retValue=-1;
  
  if (SlaveCnt ==0)
    retValue=-1;
  else
  {
    int i,j;
    bool FlgDifferent = false;

    for (i=0;i<SlaveCnt;i++)
    {
      FlgDifferent = false;
      for (j=0;j<6;j++)
      {
        if (peerMAC[j] != slaves[i].peer_addr[j])
        {
          FlgDifferent = true;
          break;
        }
      }

      if (!FlgDifferent)
      {
        retValue = i;
        break;
      }
        
    }
  }

  return retValue;
}

//This function performs a scan network searching for the devices that match the SSID format. 
//If found they are included in the register peers array.
void ScanForSlave() {
  int8_t scanResults = WiFi.scanNetworks(false,false,false,500);
  if (scanResults == 0) {
#ifdef DEBUG_ESPNOW
    Serial.println("No WiFi devices in AP Mode found");
#endif    
  } else {
#ifdef DEBUG_ESPNOW
    Serial.print("Found "); Serial.print(scanResults); Serial.println(" devices ");
#endif    
    for (int i = 0; i < scanResults; ++i) {
      // Print SSID and RSSI for each device found
      String SSID = WiFi.SSID(i);
      int32_t RSSI = WiFi.RSSI(i);
      String BSSIDstr = WiFi.BSSIDstr(i);

#ifdef DEBUG_ESPNOW
      if (PRINTSCANRESULTS) {
        Serial.print(i + 1); Serial.print(": "); Serial.print(SSID); Serial.print(" ["); Serial.print(BSSIDstr); Serial.print("]"); Serial.print(" ("); Serial.print(RSSI); Serial.print(")"); Serial.println("");
      }
#endif      
      if (SSID.indexOf(TOURNAME) == 0) {
        int mac[6];
        int CurPos;
        int numMacElem=0;

        numMacElem = sscanf(BSSIDstr.c_str(), "%x:%x:%x:%x:%x:%x%c",  &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5] );
        CurPos = searchPeer(mac);
        if (CurPos < 0)
        {
            // SSID of interest
#ifdef DEBUG_ESPNOW
            Serial.print(i + 1); Serial.print(": "); Serial.print(SSID); Serial.print(" ["); Serial.print(BSSIDstr); Serial.print("]"); Serial.print(" ("); Serial.print(RSSI); Serial.print(")"); Serial.println("");
#endif            
            // Get BSSID => Mac Address of the Slave
    
            if ( 6 == numMacElem ) {
              for (int ii = 0; ii < 6; ++ii ) {
                slaves[SlaveCnt].peer_addr[ii] = (uint8_t) mac[ii];
              }
            }
            slaves[SlaveCnt].channel = CHANNEL; // pick a channel
            slaves[SlaveCnt].encrypt = 0; // no encryption
            if ((SlaveCnt) == 0)
              tft.setCursor(12+(rectWidth*(SlaveCnt%GRID_MAX_X)),((rectHeight/2)*(SlaveCnt/GRID_MAX_X))+20); //(tft.height()/2) + 
            else
              tft.setCursor(12+(rectWidth*(SlaveCnt%GRID_MAX_X)),((rectHeight)*(SlaveCnt/GRID_MAX_X))+20); //(tft.height()/2) + 
            RefreshCube(SlaveCnt,-1*RSSI,0,true);
            tft.print(-1*RSSI);
            checkConCounter[SlaveCnt] = 0;
            int FirstInstance = SSID.indexOf(":");
            int SecondInstance = SSID.indexOf(":",FirstInstance+1);
            peerNodeData[SlaveCnt].nodeSSID = SSID.substring(SecondInstance+1);
            peerNodeData[SlaveCnt].tourName = SSID.substring(0,FirstInstance);
            peerNodeData[SlaveCnt].costumerName = SSID.substring(FirstInstance+1,SSID.indexOf(":",FirstInstance+1));
            peerNodeData[SlaveCnt].Status = STATUS_HERE;
            peerNodeData[SlaveCnt].Current_RSSI = RSSI;
            SlaveCnt++;
        }
        else
        {
            if ((CurPos) == 0)
              tft.setCursor(12+(rectWidth*(CurPos%GRID_MAX_X)),((rectHeight/2)*(CurPos/GRID_MAX_X))+20); 
            else
              tft.setCursor(12+(rectWidth*(CurPos%GRID_MAX_X)),((rectHeight)*(CurPos/GRID_MAX_X))+20); 
            peerNodeData[CurPos].Current_RSSI = RSSI;
            RefreshCube(CurPos,-1*RSSI,0,true);
            tft.print(-1*RSSI);
            checkConCounter[CurPos] = 0;
        }
      }
    }

    for(int i=0;i<SlaveCnt;i++)
      checkConCounter[i]++;
  }

#ifdef DEBUG_ESPNOW
  if (SlaveCnt > 0) {
    Serial.print(SlaveCnt); Serial.println(" Slave(s) found, processing..");
  } else {
    Serial.println("No Slave Found, trying again.");
  }
#endif

  // clean up ram
  WiFi.scanDelete();
}

// Check if the slave is already paired with the master.
// If not, pair the slave with master
void manageSlave() {
  if (SlaveCnt > 0) {
    for (int i = 0; i < SlaveCnt; i++) {
      const esp_now_peer_info_t *peer = &slaves[i];
      const uint8_t *peer_addr = slaves[i].peer_addr;
      Serial.print("Processing: ");
      for (int ii = 0; ii < 6; ++ii ) {
        Serial.print((uint8_t) slaves[i].peer_addr[ii], HEX);
        if (ii != 5) Serial.print(":");
      }
      Serial.print(" Status: ");
      // check if the peer exists
      bool exists = esp_now_is_peer_exist(peer_addr);
      if (exists) {
        // Slave already paired.
        Serial.println("Already Paired");
      } else {
        // Slave not paired, attempt pair
        esp_err_t addStatus = esp_now_add_peer(peer);
        if (addStatus == ESP_OK) {
          // Pair success
          Serial.println("Pair success");
        } else if (addStatus == ESP_ERR_ESPNOW_NOT_INIT) {
          // How did we get so far!!
          Serial.println("ESPNOW Not Init");
        } else if (addStatus == ESP_ERR_ESPNOW_ARG) {
          Serial.println("Add Peer - Invalid Argument");
        } else if (addStatus == ESP_ERR_ESPNOW_FULL) {
          Serial.println("Peer list full");
        } else if (addStatus == ESP_ERR_ESPNOW_NO_MEM) {
          Serial.println("Out of memory");
        } else if (addStatus == ESP_ERR_ESPNOW_EXIST) {
          Serial.println("Peer Exists");
        } else {
          Serial.println("Not sure what happened");
        }
      }
    }
  } else {
    // No slave found to process
#ifdef DEBUG_ESPNOW
    Serial.println("No Slave found to process");
#endif    
  }
}


uint8_t data = 0;
// send data
void sendData() {
  data++;
  for (int i = 0; i < SlaveCnt; i++) {
    const uint8_t *peer_addr = slaves[i].peer_addr;

#ifdef DEBUG_ESPNOW
    if (i == 0) { // print only for first slave
      Serial.print("Sending: ");
      Serial.println(data);
    }
#endif
    
    esp_err_t result = esp_now_send(peer_addr, &data, sizeof(data));
    Serial.print("Send Status: ");
    if (result == ESP_OK) {
      Serial.println("Success");
    } else if (result == ESP_ERR_ESPNOW_NOT_INIT) {
      // How did we get so far!!
      Serial.println("ESPNOW not Init.");
    } else if (result == ESP_ERR_ESPNOW_ARG) {
      Serial.println("Invalid Argument");
    } else if (result == ESP_ERR_ESPNOW_INTERNAL) {
      Serial.println("Internal Error");
    } else if (result == ESP_ERR_ESPNOW_NO_MEM) {
      Serial.println("ESP_ERR_ESPNOW_NO_MEM");
    } else if (result == ESP_ERR_ESPNOW_NOT_FOUND) {
      Serial.println("Peer not found.");
    } else {
      Serial.println("Not sure what happened");
    }
    startPeer[i] = micros();
  }
}

//Display at the specified position the meesage passed. 
void printDisplay(int iPos,String strData,int offsetPos)
{
    int startY = rectHeight * offsetPos;

    if ((iPos) == 0)
      tft.setCursor(12+(rectWidth*(iPos%GRID_MAX_X)),startY+((rectHeight/2)*(iPos/GRID_MAX_X))+20); 
    else
      tft.setCursor(12+(rectWidth*(iPos%GRID_MAX_X)),startY+((rectHeight)*(iPos/GRID_MAX_X))+20); 
    tft.print(strData);
}

// callback when data is sent from Master to Slave
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  char macStr[18];
  int CurPos;
  int mac[6];
  
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
           mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);

  for (int i=0;i<6;i++)
    mac[i] = mac_addr[i];
  CurPos = searchPeer(mac);
  unsigned long tmpTime =(micros()-startPeer[CurPos])/1000;

  if (status == ESP_NOW_SEND_SUCCESS && !PanicFlag)
  {
    RefreshCube(15+CurPos,0,0,false);
    printDisplay(15+CurPos,String(tmpTime),0);
    if (openPingFlg)
    {
      String tmpStr = "Received OK "+peerNodeData[CurPos].costumerName+" "+peerNodeData[CurPos].nodeSSID+" "+String(peerNodeData[CurPos].Current_RSSI)+" "+String(tmpTime);
      dataPing.println(tmpStr);
      dataPing.flush();
      Serial.print(tmpStr);
    }
  }
  else
  {
    if (status != ESP_NOW_SEND_SUCCESS)
    {
      if (openPingFlg)
      {
        String tmpStr = "Received Failed "+peerNodeData[CurPos].costumerName+" "+peerNodeData[CurPos].nodeSSID+" "+String(peerNodeData[CurPos].Current_RSSI)+" "+String(tmpTime);
        dataPing.println(tmpStr);
        dataPing.flush();
        Serial.print(tmpStr);
      }
    }
    
  }
  
#ifdef DEBUG_ESPNOW
  Serial.print("Last Packet Sent to: "); Serial.println(macStr);
  Serial.print("Last Packet Send Status: "); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
#endif  
  
}

//Initialize all the components.
void setup() {
  memset(slaves, 0, sizeof(slaves));
  memset(checkConCounter, 0, sizeof(checkConCounter));
  Serial.begin(115200);
  //Set device in STA mode to begin with
  WiFi.mode(WIFI_STA);
#ifdef DEBUG_ESPNOW
  Serial.println("ESPNow/Multi-Slave/Master Example");
  Serial.print("STA MAC: "); Serial.println(WiFi.macAddress());
#endif  
  InitESPNow();
  esp_now_register_send_cb(OnDataSent);

  ts.begin();
  tft.begin();          // Initialize screen

  ClearScreen();
  CreateGrid(GRID_MAX_X,GRID_MAX_Y,0);
  CreateGrid(GRID_MAX_X,GRID_MAX_Y,3);

  ScanForSlave();

  refreshTime = micros();

  InitLog();
}

int loopCnt=0;

//Main loop
void loop() {

  if (!FlgSelect)
  {
    // wait for 3seconds to run the logic again
    if (micros()-refreshTime > REFRESH_THRESHOLD)
    {
      ScanForSlave();
      refreshTime = micros();
      RefreshFiles();
    }
    
    if (SlaveCnt > 0) { 
      manageSlave();
      sendData();
    } 
  }
 
  ConfirmConnection(0);

  if (ts.touched())
  {
    TS_Point p = ts.getPoint();
    
    while (!ts.bufferEmpty())
    {
      p = ts.getPoint();
    }

    delay(100);

    p = ts.getPoint();
    p.x = map(p.x, TS_MINX, TS_MAXX, 0, tft.width());
    p.y = map(p.y, TS_MINY, TS_MAXY, 0, tft.height());

    if (FlgSelect)
    {
      p = ts.getPoint();
      p.x = map(p.x, TS_MINX, TS_MAXX, 0, tft.width());
      p.y = map(p.y, TS_MINY, TS_MAXY, 0, tft.height());
      SelectNode(p,0);
      if (FlgSelect)
      {
        ClearScreen();
        delay(100);
        tft.fillRect(tft.width()-rectWidth,tft.height()-rectHeight, rectWidth,rectHeight, ILI9341_CYAN);
        String strTempo = "MAC ";
        strTempo.concat(peerNodeData[PrevSel].nodeSSID); 
        printDisplay(0,strTempo,0);
        strTempo = "Name: ";
        strTempo.concat(peerNodeData[PrevSel].costumerName); 
        printDisplay(5,strTempo,0);
        strTempo = "Tour: ";
        strTempo.concat(peerNodeData[PrevSel].tourName); 
        printDisplay(10,strTempo,0);
        
      }
    }
    else
    {
 
      if (p.x > tft.width()-rectWidth && p.y > tft.height()-rectHeight) 
      {
        if (FlgSelect == 0)
        {
          FlgSelect++;
          tft.fillRect(tft.width()-rectWidth,tft.height()-rectHeight, rectWidth,rectHeight, ILI9341_CYAN);
  
          while (ts.touched())
          {
            p = ts.getPoint();
          }
            //delay(100);
          while (!ts.bufferEmpty())
          {
            p = ts.getPoint();
          }
        }
      }
    }
  
  }

}

ESP32 ESPNow slave program

Arduino
This module sets and ESP32 device to request access to the master ESPNow device.
//Based on the sample ESP32 EspNow slave program.
//The code was change to use a costum SSID that represents the Tour name and the person the device is assigned to.
//Each device will be burn with the name of the person it will be assigned to. Chaperones will have a special code to identify the device.
#include <esp_now.h>
#include <WiFi.h>

#define CHANNEL 4

// Init ESP Now with fallback
void InitESPNow() {
  WiFi.disconnect();
  if (esp_now_init() == ESP_OK) {
    Serial.println("ESPNow Init Success");
  }
  else {
    Serial.println("ESPNow Init Failed");
    ESP.restart();
  }
}

// config AP SSID
void configDeviceAP() {
  //String Prefix = "WAMALL:Liz:";
  //String Prefix = "WAMALL:Steph:";
  String Prefix = "WAMALL:Chap1:";
  String Mac = WiFi.macAddress();
  String SSID = Prefix + Mac;
  String Password = "123456789";
  bool result = WiFi.softAP(SSID.c_str(), Password.c_str(), CHANNEL, 0);
  if (!result) {
    Serial.println("AP Config failed.");
  } else {
    Serial.println("AP Config Success. Broadcasting with AP: " + String(SSID));
  }
}

void setup() {
  Serial.begin(115200);
  //Set device in AP mode to begin with
  WiFi.mode(WIFI_AP);
  // configure device AP mode
  configDeviceAP();
  // This is the mac address of the Slave in AP Mode
  Serial.print("AP MAC: "); Serial.println(WiFi.softAPmacAddress());
  // Init ESPNow with a fallback logic
  InitESPNow();
  // Once ESPNow is successfully Init, we will register for recv CB to
  // get recv packer info.
  esp_now_register_recv_cb(OnDataRecv);
}

// callback when data is recv from Master
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *data, int data_len) {
  char macStr[18];
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
           mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  Serial.print("Last Packet Recv from: "); Serial.println(macStr);
  Serial.print("Last Packet Recv Data: "); Serial.println(*data);
  Serial.print("Strength: "); Serial.println(WiFi.RSSI());
  Serial.println("");
}

void loop() {
}

ESP8266 ESPNow slave program

Arduino
This module sets and ESP8266 device to request access to the master ESPNow device.
//This program was modified from the below original. 
//The code was change to use a costum SSID that represents the Tour name and the person the device is assigned to.
//Each device will be burn with the name of the person it will be assigned to. Chaperones will have a special code to identify the device.

// Derived from: EspnowSlave.ino
// a minimal program derived from
//          https://github.com/HarringayMakerSpace/ESP-Now
// This is the program that receives the data. (The Slave)

//=============

#include <ESP8266WiFi.h>
extern "C" {
    #include <espnow.h>
     #include <user_interface.h>
}

byte devMac[6];
#define WIFI_CHANNEL 4

//==============


void configDeviceAP() {
  //String Prefix = "Slave:";
  //String Prefix = "WAMALL:Chap1:";
  //String Prefix = "WAMALL:Chap2:";
  //String Prefix = "WAMALL:Bill:";
  //String Prefix = "WAMALL:Linda:";
  //String Prefix = "WAMALL:April:";
  //String Prefix = "WAMALL:Mike:";
  String Prefix = "WAMALL:Qing:";
  String Mac = WiFi.macAddress();
  String SSID = Prefix + Mac;
  String Password = "123456789";
  bool result = WiFi.softAP(SSID.c_str(), Password.c_str(), WIFI_CHANNEL, 0);
  if (!result) {
    Serial.println("AP Config failed.");
  } else {
    Serial.println("AP Config Success. Broadcasting with AP: " + String(SSID));
  }
}

// must match the controller struct
struct __attribute__((packed)) DataStruct {
    char text[32];
    unsigned int time;
};

DataStruct myData;

//============

void setup() {
    Serial.begin(115200); Serial.println();
    Serial.println("Starting EspnowSlave.ino");
    //myinitVariant();
    configDeviceAP();
    
    Serial.print("This node AP mac: "); Serial.println(WiFi.softAPmacAddress());
    Serial.print("This node STA mac: "); Serial.println(WiFi.macAddress());

    if (esp_now_init()!=0) {
        Serial.println("*** ESP_Now init failed");
        while(true) {};
    }

    esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);

    esp_now_register_recv_cb(receiveCallBackFunction);


    Serial.println("End of setup - waiting for messages");
}

//============

void loop() {

}

//============

void receiveCallBackFunction(uint8_t *senderMac, uint8_t *incomingData, uint8_t len) {
    memcpy(&myData, incomingData, sizeof(myData));
    Serial.print("NewMsg ");
    Serial.print("MacAddr ");
    for (byte n = 0; n < 6; n++) {
        Serial.print (senderMac[n], HEX);
    }
    Serial.print("  MsgLen ");
    Serial.print(len);
    Serial.print("  Text ");
    Serial.print(myData.text);
    Serial.print("  Time ");
    Serial.print(myData.time);
    Serial.println();
}

Credits

Stephanie Vicarte

Stephanie Vicarte

14 projects • 12 followers
Irak Mayer

Irak Mayer

18 projects • 10 followers
Elizabeth Vicarte

Elizabeth Vicarte

13 projects • 7 followers

Comments