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,824
Task Manager

Things used in this project

Hardware components

Arduino Mega 2560
Arduino Mega 2560
×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!
Contact

Comments

Please log in or sign up to comment.