Cmtelesann
Published

Task Manager

Make your kids help with your house chores! A scoreboard with all home chores for them to log using RFID cards! Lots of fun!

IntermediateShowcase (no instructions)2 hours3,888
Task Manager

Things used in this project

Hardware components

Arduino Mega
×1
Elegoo RFID Module RC522
×1
Elegoo RTC DS3231 Module
×1
Elegoo Rotary Encoder Module RY-040
×1
0.96" OLED 64x128 Display Module
ElectroPeak 0.96" OLED 64x128 Display Module
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
Premium Female/Male Extension Jumper Wires, 40 x 6" (150mm)
Premium Female/Male Extension Jumper Wires, 40 x 6" (150mm)
Drill, Screwdriver
Drill, Screwdriver
Hot glue gun (generic)
Hot glue gun (generic)

Story

Read more

Schematics

Schematic

Code

Arduino Code

Arduino
Soul of your project!
/* 
 *  Working on Mega2560 R3.
 *  Mega AVR (Wire library) - v1.8.1
 *  Adafruit_SSD1306 by Adafruit - v1.2.9
 *  Mega AVR (EEPROM) - v1.8.1
 *  Encoder by Paul Stoffregen - v1.4.1
 *  DS1307RTC by Michael Margolis - v1.4.0
 *  MFRC522 by GithubCommunity - v1.4.4
 *  
 *  PINOUT
   *  OLED
   *    GND - GND
   *    VCC - 5V
   *    SCL - SCL (21)
   *    SDA - SDA (20)
   *  RTC
   *    GND - GND
   *    VCC - 5V
   *    SCL - SCL (21)
   *    SDA - SDA (20)
   *  ENCODER
   *    GND - GND
   *     +  - 5V
   *    CLK - 2
   *    DT  - 3
   *  RFID
   *    3.3V - 3.3V
   *    RST  - 9
   *    GND  - GND (different from the others)
   *    MISO - 50
   *    MOSI - 51
   *    SCK  - 52
   *    SDA  - 53
 *    
*/
//**************************************** INCLUDE ****************************************
#include <Wire.h>                                      // WIRE library - used for chosing pins for the encoder.
#include <Adafruit_SSD1306.h>                          // OLED library
#include <EEPROM.h>                                    // Current state of tasks is persisted by saving/restoring to EEPROM memory
#include <Encoder.h>                                   // Encoder library
#include <DS1307RTC.h>                                 // Used to retrieve time information from Real-Time Clock (RTC) module https://github.com/PaulStoffregen/DS1307RTC
#include <MFRC522.h>                                   // RFID reader library

//**************************************** PIN SET ****************************************
Adafruit_SSD1306 display (128, 64, & Wire, -1);        // Resolution 128x64, -1 = no reset pin.(SDA - 20(SDA) SCL - 21(SCL))
Encoder myEnc(2, 3);                                   // Comments on Encoder_Mega_Test. Pins CLK - 2, DT - 3.
MFRC522 mfrc522(53, 9);                                // Create an instance of the MFRC522 class. The parameters specified in the constructor are (SS pin, Reset pin)

//**************************************** INTEGERS, BYTE, LONG ****************************************
byte button;                                           // for use of the button
byte arrowpos = 0;                                     // Arrow position - used to save the arrow position to be used in different screens
long oldPosition  = -999;                              // Initialize the encoder position.
int menuIndex = 0;                                     // 0 means it will start on main menu. It will be used to track which submenu you want to go in.
int listItems = 2;                                     // Number of index on *listsubMenu - change to fit the size of your array
const char ver[6]= "V0.1";                             // Version number - To help you keep track of your project
int userID2;
int selectedTask = 0;
byte userTemp;

//**************************************** MENULISTSCHARS ****************************************
char *score[] = {"1 Place:", "2 Place:", "3 Place:", "4 Place:", "5 Place:"};
char *listMenu[] = {"Validate Tasks", "Scoredboard"}; // Names of your menus used on main menu.

//**************************************** STRUCTURES ****************************************
struct task {                                          // This data structure encapsulates all the properties associated with an individual task
  char taskName[16];                                   // The short, "friendly" name for this task as will appear on the display
  int repeatEachXDays;                                 // Regularity, in days, with which this task is repeated. 1=Daily, 7=Weekly etc.
  unsigned long lastCompletedTime;                     // Timestamp at which this task was last completed
  int lastCompletedBy;                                 // ID of the person who last completed this task
};

struct user {                                          // This data structure represents a user
  int userID;                                          // Unique ID - matches that stored in the "lastCompletedBy" field of a task
  char userName[8];                                    // Display name of this user
  byte rfidUID[4];                                     // Unique identifier of the RFID tag this user uses 
  int score;
};

int struct_cmp_by_score(const void *a, const void *b) { 
    struct user *ia = (struct user *)a;
    struct user *ib = (struct user *)b;
    return (int)(ia->score - ib->score);               // float comparison: returns negative if b > a and positive if a > b. 
}; 

//**************************************** CONSTANTS ****************************************
const byte eepromSignature = 2;                       // This byte defines whether the data stored on EEPROM is valid. Valid values range from 0-254 and reflect the version of the dataset - it should be changed every time the structure of the underlying data is updated (tasks added/removed etc.)
const int numTasks = 12;                              // The total number of different tasks available to perform - change to match the amount of tasks
const int numUsers = 5;                               // The total number of users known to the system

//**************************************** GLOBALS ****************************************
user userList[numUsers] = {                           // The dictionary of users
  { 0, "      ", {0x12, 0x34, 0x56, 0x78}, 0},        // Dummy user! Don't change
  { 1, "User1 ", {0xC2, 0x52, 0xD3, 0x04}, 0},        // Change to your card!
  { 2, "User2 ", {0x40, 0x7B, 0x79, 0x19}, 0},        // Change to your card!
  { 3, "User3 ", {0x04, 0x03, 0x96, 0x92}, 0},        // Change to your card!
  { 4, "User4 ", {0x3D, 0x3F, 0x77, 0x62}, 0},        // Change to your card!     
};

task taskList[numTasks] = {                           // The taskList is an array of all the task information. 
  { "Task1          ", 7, "0", 0 },                   // Tasks are initialised with a lastCompletedTime=0 and lastCompletedBy=-1, 
  { "Task2          ", 3, "0", 0 },                   // but these values will be overwritted with data from EEPROM if valid stored data exists. 
  { "Task3          ", 3, "0", 0 },                   // If this list is changed, be sure to also update the EEPROM signature value above 
  { "Task4          ", 3, "0", 0 },                   // otherwise the program will attempt to restore mismatched stored data from EEPROM.   
  { "Task5          ", 1, "0", 0 }, 
  { "Task6          ", 1, "0", 0 },
  { "Task7          ", 3, "0", 0 }, 
  { "Task8          ", 7, "0", 0 },
  { "Task9          ", 1, "0", 0 },
  { "Task10         ", 30, "0",0 },
  { "Task11         ", 7, "0", 0 },
  { "Task12         ", 7, "0", 0 },  
};

//**************************************** NORMALIZE ****************************************
long normalize(long value, long radix) {               // to wrap your encoder count around your amount of submenus.
  long rval = value % radix;
  while(rval < 0) { 
    rval += abs(radix); 
  } 
  return rval;
  
};

//**************************************** COMPLETETASK ****************************************
void completeTask(int taskId, unsigned long completedTime, int completedBy) { // Completes the provided task id 
  taskList[taskId].lastCompletedTime = completedTime;
  taskList[taskId].lastCompletedBy = completedBy;
}

//**************************************** SETUP ****************************************
void setup() {
  Serial.begin(9600);                                  // Starting serial for debbuging purposes.
  
  Serial.print(F("Loading OLED..."));                  // Initializing OLED
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.display();
  display.clearDisplay();
  Serial.println(F("done."));

  Serial.print(F("Loading saved data..."));            // Load the stored data
  restoreFromEEPROM();
  Serial.println(F("done."));  
  delay(100);

  tmElements_t tm;                                     // Initializing RTC
  if(RTC.read(tm)) {
    Serial.print(F("Initializng RTC... "));
    char timeString[16];
    memset(timeString, 0, sizeof timeString);
    snprintf(timeString, 16, "%i/%i/%i %i:%02i", tm.Day, tm.Month, 1970+tm.Year-2000, tm.Hour, tm.Minute);
    Serial.println(timeString);
  }
  else {
    if(RTC.chipPresent()) {
      Serial.println("FAILED. The DS1307 is not initialised. Please run the SetTime sketch to set correct time.");
    } 
    else {
      Serial.println("FAILED. Cannot connect to DS1307. Please check your wiring.");
    }
  }

  Serial.print(F("Initialising SPI Communication...")); // Initiate RFID
  SPI.begin();                                          // Initiate SPI bus. Wire library.
  Serial.println(F("done."));
    
  Serial.print(F("Initialising RFID reader..."));       // Initiate RFID
  mfrc522.PCD_Init();
  Serial.println(F("done."));
  
}

//**************************************** LOOP ****************************************
void loop() {

  switch (menuIndex)  {
    case 0: 
      mainMenu();                                      // Main menu
      selectEncoder();
      selectButton();
      break;
    case 1: 
      validateTaskMenu();                              // Screen to validate tasks with RFID badges
      selectEncoder();
      selectButton();
      break;
    case 2:
      resultsTask();                                   // Screen to show users scores
      selectEncoder();
      selectButton();
      break;
  }
  display.clearDisplay();
  delay(150);
}

//**************************************** MAIN MENU ****************************************
void mainMenu() {                                      // Case 0: mainMenu

  listItems =2;
  
  display.setTextSize(2);                              // Set text size of Main Menu to 2
  display.setTextColor(WHITE);                         // To make the text show up on the screen.

  display.setCursor(30, 0);                             // Screen title position
  display.print("Menu");                               // Screen title
  display.setTextSize(1);                              // Set text size of Version and subMenus to 1
  display.setCursor(100, 56);                          // Program version position
  display.println(ver);                                // Program version
//-----------------------------------------------------// Above line static info. Below line variable info.

  for (int i = 1; i <= 2; i++){                        // Loop to show all Menus
    display.setCursor(10, (i*10)+6);
    display.println(listMenu[i-1]);                    // -1 to reduce the arrowpos() number to 4
  }
  if (arrowpos == 2) {                                 // Loop to position arrow in front of Menus
    display.setCursor(2, 16);                                   
    display.println(">");
  } else {
  display.setCursor(2, (arrowpos * 10) + 16);          // Arrow will move up/down on screen.
  display.println(">");
  }
  display.display();
  
}

//**************************************** VALIDATETASKMENU ****************************************
void validateTaskMenu() {                              // Case 1: validateTasks
  listItems = 12;
  arrowpos=2;

  display.clearDisplay();

  display.setTextSize(2);    
  display.setCursor(22, 0);
  display.print("Chores");
//-----------------------------------------------------// Above line static info. Below line variable info.
  display.setCursor(0, 16);
  display.setTextSize(1);
  display.setTextColor(BLACK, WHITE);                  // Draw 'inverse' text 
  display.print("<  ");
  display.setCursor(18, 16);
  display.print(taskList[selectedTask].taskName);
  display.setCursor(108, 16);
  display.println("  >");
  display.setCursor(0, 26);
  display.setTextColor(WHITE);  
  display.println("Approximate ID:");
  char timeString1[16];
  while (! mfrc522.PICC_ReadCardSerial() && mfrc522.PICC_IsNewCardPresent()) {
//    Serial.print("UID tag : ");                        // Show UID on serial monitor
    int userID = GetUserFromRFIDTag(mfrc522.uid.uidByte); // Get ID from RFID card
    if(userID != -1){
      tmElements_t tm;
      if(RTC.read(tm)) {
        memset(timeString1, 16, sizeof timeString1);
        snprintf(timeString1, 16, "%i.%i.%i %i:%02i", tm.Day, tm.Month, 1970+tm.Year-2000, tm.Hour, tm.Minute);
        time_t theTime = makeTime(tm);
        completeTask(selectedTask, theTime, userID);
//        Serial.println(userList[userID].userName);
//        Serial.println(userID);
        if (digitalRead(button) == HIGH){
          if (userID != userID2){
            userList[userID].score = userList[userID].score+1;
            userID2 = userID;
            saveToEEPROM(); 
            break;
          }
        }
      }
    }
  }

  display.setCursor(30, 36);
  display.setTextSize(2);
  display.println(userList[userID2].userName);

  display.display();

}

//**************************************** RESULTSTASK ****************************************
void resultsTask() {                                   // Case 4: resultsTask
  arrowpos = 2;

  sortScoreList();
  
  display.clearDisplay();

  display.setCursor(0, 0);
  display.println("Scoreboard:");
//-----------------------------------------------------// Above line static info. Below line variable info.
  
  for(int i=0; i<numUsers; i++) {
    display.setCursor(10, (i*10)+16);
    display.print(score[i]);
    display.setCursor(60, ((numUsers-i)*10)+6);        // To print in highest-lowest score.
    display.print(userList[i].userName);
    display.print(" - ");
    display.println(userList[i].score);
  }
  
  display.display();

}

//**************************************** RFIDGET ****************************************
int GetUserFromRFIDTag(byte RFID[]){
  // Loop over all the users
  for(int i=0; i<numUsers; i++) {
    if(memcmp(userList[i].rfidUID, RFID, sizeof userList[i].rfidUID) == 0) {
      return userList[i].userID;
    }
  }

//  Serial.print(F("Unknown RFID card detected: "));
  for(byte i=0; i<4; i++) {
//    Serial.print(RFID[i]<0x10 ? " 0" : " ");
//    Serial.print(RFID[i], HEX);
  }
  
}

//**************************************** SELECTENCODER ****************************************
void selectEncoder() {
  
//  Serial.print("Arrowpos: ");
//  Serial.println(arrowpos);
//  Serial.print("menuIndex: ");
//  Serial.println(menuIndex);
  Serial.print("Button: ");
  Serial.println(button);
  
  long encoderCount = myEnc.read()>>2;
  encoderCount = normalize(encoderCount, listItems);   // Number of lines to display affects the encoder count
  if (encoderCount != oldPosition) {
    oldPosition = encoderCount;
    arrowpos = encoderCount;                           // Positions the arrow on each menu
//    Serial.print("Encoder: ");
//    Serial.println(encoderCount);
    selectedTask = encoderCount;
  }
  
}

//**************************************** SELECTBUTTON ****************************************
void selectButton() {
  button = digitalRead(4);                            // Select task button

  if (menuIndex == 2) {                               // Menu2()
    if (button == LOW && arrowpos == 2) {             // Goes back to mainMenu()
      menuIndex = 0;   
    }
  }
  if (menuIndex == 1) {                               // Menu1()
    if (button == LOW && arrowpos == 2) {             // Goes to subMenu1()
      menuIndex = 0;   
    }
  }
  if (menuIndex == 0) {                               // mainMenu()
    if (button == LOW && arrowpos == 0) {             // Goes to Menu1()
      menuIndex = 1;
    }
    if (button == LOW && arrowpos == 1) {             // Goes to Menu2()
      menuIndex = 2;  
    }
    if (button == LOW && arrowpos == 2) {             // Goes back to mainMenu()
      menuIndex = 0;  
    }
  }

}

//**************************************** SORTSCORELIST ****************************************
void sortScoreList(void) { 
    qsort(userList, numUsers, sizeof(struct user), struct_cmp_by_score); // sort array using qsort functions

} 

//**************************************** SAVETOEEPROM ****************************************
void saveToEEPROM() {
  // First, overwrite the first byte of EEPROM as a checkbyte
  EEPROM.write(0, 255);
  // Write the tasklist to EEPROM, starting at the second byte
  EEPROM.put(1, userList);
//  Serial.println("Task list saved to EEPROM!");
  // Now restore the checkbyte
  EEPROM.write(0,eepromSignature);
}

//**************************************** RESTOREFROMEEPROM **************************************** 
void restoreFromEEPROM() {                        //Restores the saved state of items in the tasklist from EEPROM
  // Read the value of the checkbyte
  int checkByte = EEPROM.read(0);
  // If the signature matches the current version number
  if(checkByte == eepromSignature) {
    // Restore the tasklist
    EEPROM.get(1, userList);
  }
}

Credits

Cmtelesann
5 projects • 15 followers
Keeping my head busy! Looking for challenges! Trying to keep up with evolution! IoT is the future!

Comments