/*
* 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);
}
}
Comments
Please log in or sign up to comment.