working version of song control with initialization from sd card

Dependencies:   MFRC522 NRF2401P SDFileSystem SPI_TFT_ILI9341 TFT_fonts mbed

Fork of Song_Control by Malcolm McCulloch

hub.cpp

Committer:
dxyang
Date:
2016-02-29
Revision:
9:72e93d9ddc8c
Parent:
8:b40c4553f6d4

File content as of revision 9:72e93d9ddc8c:

/**
* All the code associated to run the mbed as a hub.
* Link with locker
*/
#include "mbed.h"
#include "utils.h"
#include "NRF2401P.h"

#include "MFRC522.h"
#include "SPI_TFT_ILI9341.h"
#include "Arial24x23.h"
#include "SDFileSystem.h"

#define debug 

/*************************************************************************************************/
/* Global variables */
/*************************************************************************************************/
// tx nRF2401
extern char txBuff[];
extern NRF2401P nrf1;
extern int channel;
long long addrLcker=0xBBBBBBBBBB;

// MFRC522 RfChip (SPI_MOSI, SPI_MISO, SPI_SCLK, SPI_CS, MF_RESET); 
MFRC522 BatteryRfChipH (PTD2, PTD3, PTD1, PTE25, PTB3);
MFRC522 PoshoRfChipH (PTD2, PTD3, PTD1, PTB10, PTB11);
MFRC522 IncubatorRfChipH (PTD2, PTD3, PTD1, PTC11, PTC10);

// tickers and flags for checking rfid statuses
Ticker tickBatteryRfidH;
Ticker tickPoshoRfidH;
Ticker tickIncubatorRfidH;
char flagBatteryRfidH = 0;
char flagPoshoRfidH = 0;
char flagIncubatorRfidH = 0;

//SPI_TFT_ILI9341(PinName mosi, PinName miso, PinName sclk, 
//                PinName cs, PinName reset, PinName dc, const char* name ="TFT");
SPI_TFT_ILI9341 TFT_H(PTD2, PTD3, PTD1, PTB2, PTB20, PTB18,"TFT");

// button interrupt signals and respective flags
InterruptIn buttonOneH(PTC3);
InterruptIn buttonTwoH(PTC2);
InterruptIn buttonThreeH(PTA2);
InterruptIn buttonFourH(PTB23);
char flagButtonOneH = 0;
char flagButtonTwoH = 0;
char flagButtonThreeH = 0;
char flagButtonFourH = 0;

// ticker that causes time to be displayed
Ticker tickTimeCheckH;
char flagTimeCheckH = 0; 

// ticker that causes an interrupt to update the user table every 1/2 an hour
Ticker tickerUpdateUserTableH;
char flagUpdateUserTableH = 0; 

// ticker that goes off every second
unsigned char flag1sH = 0;
Ticker tick1sHub;

// Maximum current that each cube can consume.
float currentMaxH = 1.5; 

// A hubUser can be assigned any of four lockers, each with a unique locker address
enum LockerAddressH {
    lockerUnassigned,
    lockerOne,
    lockerTwo,
    lockerThree,
    lockerFour
};

// The hub display screen can involve multiple stages
enum HubScreenStageH {
    initialScanRfid,
    waitForRfid,
    batterySelectAction,
    batterySelectNumberForPickup,
    batterySelectNumberForDropoff,
    poshoSelectKilograms
};
enum HubScreenStageH currentScreenH = initialScanRfid;

// hubUser struct containing the users uid, rfid, number of batteries, current account credit,
// an enum corresponding to a locker channel address, and pod ID within a locker
struct hubUserH {
    uint32_t uid;
    uint32_t rfid;
    int32_t accountCredit;
    enum LockerAddressH lockerID;
    int32_t podID;
    int32_t batteriesOut;
    int32_t batterySubscription;
};

// community ID should be defined somewhere and accessible globally
uint32_t communityID_H = 1;

// array to store all users and index of end of the current list
hubUserH allUsersH[256];
uint8_t userCountH = 0;
uint8_t currentUserH;

// Data is being logged and collected
// these actions depend on a known user using the system
enum HubActionForLoggingH {
    hubAction_BatteryRfidScanned,
    hubAction_PoshoRfidScanned,
    hubAction_Exit,
    hubAction_OneBatteryDropped,
    hubAction_TwoBatteryDropped,
    hubAction_ThreeBatteryDropped,
    hubAction_OneBatteryPicked,
    hubAction_TwoBatteryPicked,
    hubAction_ThreeBatteryPicked,
    hubAction_OneKiloPosho,
    hubAction_TwoKiloPosho,
    hubAction_ThreeKiloPosho
};

// any rfid can log errors regarding an unknown tag being read
enum HubLoggingRfidSourceH {
    source_battery,
    source_posho,
    source_incubator
};

// price per kilogram of material to be used with the posho
int poshoPricePerKgH;

// flag for if the posho is already in use; multiple people cannot use at once
char flagPoshoAlreadyInUseH = 0;
Ticker tickPoshoInUseClearH;
int timeForOneKgH;
int timeForTwoKgH;
int timeForThreeKgH;

/*************************************************************************************************/
/* Set a flag when an interrupt is detected */
/*************************************************************************************************/
/**
 *  Time check interrupt
 */
void interruptTimeCheckH()
{ flagTimeCheckH = 1; }

/**
 *  Update user table interrupt
 */
void interruptUpdateUserTableH()
{ flagUpdateUserTableH = 1; }

/**
 *  Battery RFID reader interrupt 
 */
void interruptBatteryRfidH()
{ flagBatteryRfidH = 1; }

/**
 *  Posho RFID reader interrupt 
 */
void interruptPoshoRfidH()
{ flagPoshoRfidH = 1; }

/**
 *  Incubator RFID reader interrupt 
 */
void interruptIncubatorRfidH()
{ flagIncubatorRfidH = 1; }

/**
 *  buttone one interrupt 
 */
void interruptButtonOneH()
{ flagButtonOneH = 1; }

/**
 *  buttone two interrupt 
 */
void interruptButtonTwoH()
{ flagButtonTwoH = 1; }

/**
 *  buttone three interrupt 
 */
void interruptButtonThreeH()
{ flagButtonThreeH = 1; }

/**
 *  buttone four interrupt 
 */
void interruptButtonFourH()
{ flagButtonFourH = 1; }

/**
* Fast interrupt routine for every 1 second
*/
void int1sH()
{ flag1sH=1; }

/**
 *  interrupt to clear the posho is in use flag after a designated time
 */
void interruptPoshoInUseClearH()
{ 
    flagPoshoAlreadyInUseH = 0;
    tickPoshoInUseClearH.detach();
}

/*************************************************************************************************/
/* Transfer Info */
/*************************************************************************************************/
/**
 * Sends a time stamp
 */
void txTimeH()
{
#ifdef debug
    printf("Send time \n\r");
#endif
    time_t now= time(NULL);
    sprintf(txBuff,"T %X",now);
    nrf1.transmitData(txBuff,strlen(txBuff));
#ifdef debug
    printf("Tx %s [nrf:%s] \n\r", txBuff,nrf1.statusString());
#endif  
}

/**
 * Sends max Current
 */
void txCurrentH()
{
#ifdef debug
    printf("Send current \n\r");
#endif
    sprintf(txBuff,"I %04X", *((int *) &currentMaxH));
    nrf1.transmitData(txBuff,strlen(txBuff));
#ifdef debug
    printf("Tx %s [nrf:%s] \n\r", txBuff,nrf1.statusString());
#endif  
}

/**
 * Slow interrupt routine for every 1 second
 */
void do1sH()
{
    flag1sH = 0;
#ifdef debug
    printf("Hub 1s \n\r");
#endif
    
    time_t now= time(NULL);
    if ((now % 60)==0){
    txTimeH();  
    }
    txCurrentH();
#ifdef debug
    printf("Tx %s [nrf:%s] \n\r", txBuff,nrf1.statusString());
#endif  
}

/**
 * Sends a signal to a locker that the current user is picking up numBatteries batteries
 * @param numBatteries - int representing number of batteries user is picking up
 */
void txBatteryPickUp(int numBatteries) {

}

/**
 * Sends a signal to a locker that the current user is dropping off numBatteries batteries
 * @param numBatteries - int representing number of batteries user is dropping off
 */
void txBatteryDropOff(int numBatteries) {

}

/**
 * Sends a signal to a services mbed to schedule the posho/husker for use for x kilograms
 * @param numKilograms - int representing number of kilograms user is processing
 */
void txPoshoSerialUse(int numKilograms) {

}

/*************************************************************************************************/
/* Initialization */
/*************************************************************************************************/
/**
 * Initialize system on reset and set the time from the terminal
 */
void initializeTimeH()
{ 
    // get the current time from the terminal
    struct tm t;
    printf("Enter current date :\n\r");
    printf("YYYY MM DD [enter]\n\r");
    scanf("%d %d %d", &t.tm_year, &t.tm_mon, &t.tm_mday);
    printf("Enter current time:\n\r");
    printf("HH MM SS [enter]\n\r");
    scanf("%d %d %d", &t.tm_hour, &t.tm_min, &t.tm_sec);

    // adjust for tm structure required values
    t.tm_year = t.tm_year - 1900;
    t.tm_mon = t.tm_mon - 1;

    // set the time
    set_time(mktime(&t));
#ifdef debug
    printf("Time initialized\n\r");
#endif
}

/**
 *  Initialize all the interrupts 
 */
void initializeInterruptsH() 
{
    tickTimeCheckH.attach(&interruptTimeCheckH, 5.0);
    tickBatteryRfidH.attach(&interruptBatteryRfidH, 1.0);
    tickPoshoRfidH.attach(&interruptPoshoRfidH, 1.0);
    tickIncubatorRfidH.attach(&interruptIncubatorRfidH, 1.0);
    tickerUpdateUserTableH.attach(&interruptUpdateUserTableH, 1800.0);
    buttonOneH.rise(&interruptButtonOneH);
    buttonTwoH.rise(&interruptButtonTwoH);
    buttonThreeH.rise(&interruptButtonThreeH);
    buttonFourH.rise(&interruptButtonFourH);
    tick1sHub.attach(&int1sH, 10.0);

#ifdef debug
    printf("Interrupts initialized\n\r");
#endif
}

/**
 *  Initialize RFID readers 
 */
void initializeRfidReadersH() 
{
    BatteryRfChipH.PCD_Init();
    PoshoRfChipH.PCD_Init();
    IncubatorRfChipH.PCD_Init();

#ifdef debug
    uint8_t tempA = BatteryRfChipH.PCD_ReadRegister(MFRC522::VersionReg);
    printf("Battery MFRC522 version: %d\n\r", tempA & 0x07);
    printf("\n\r");
    uint8_t tempB = PoshoRfChipH.PCD_ReadRegister(MFRC522::VersionReg);
    printf("Posho MFRC522 version: %d\n\r", tempB & 0x07);
    printf("\n\r");
    uint8_t tempC = IncubatorRfChipH.PCD_ReadRegister(MFRC522::VersionReg);
    printf("Incubator MFRC522 version: %d\n\r", tempC & 0x07);
    printf("\n\r");
#endif

#ifdef debug
    printf("RFID readers initialized\n\r");
#endif
}

/**
 *  Initialize LCD screen 
 */
void initializeLCD_H() 
{
    TFT_H.background(Black);    // set background to black
    TFT_H.foreground(White);    // set chars to white
    TFT_H.cls();                // clear the screen
    TFT_H.set_font((unsigned char*) Arial24x23);
    TFT_H.set_orientation(3);   //Portrait, wiring on left
#ifdef debug
    printf("LCD initialized\n\r");
#endif
}

/**
 * Creates a new user by defining user attributes.
 * @param rfid - uint32_t corresponding to unique id of the RFID tag
 * @param accountCredit - int32 for user account balance (can be negative)
 * @param locker - int32_t for which locker (originaly 1-4) user is assigned
 * @param batterySubscription - max batteries user can get (can be 0)
 * @param batteriesOut - number of batteries user has out (usually 0 initially)
 * @param podID - pod associated with the user (originally 0-15) *must be 0 indexed*
 */
void createNewUserH(uint32_t rfid, int32_t accountCredit, int32_t locker, 
        int32_t batterySubscription, int32_t batteriesOut, int32_t podID)
{
    allUsersH[userCountH].rfid = rfid;
    allUsersH[userCountH].accountCredit = accountCredit;
    switch(locker) {
        case 1:
            allUsersH[userCountH].lockerID = lockerOne;
            break;
        case 2:
            allUsersH[userCountH].lockerID = lockerTwo;
            break;
        case 3:
            allUsersH[userCountH].lockerID = lockerThree;
            break;
        case 4:
            allUsersH[userCountH].lockerID = lockerFour;
            break;
        default:
            allUsersH[userCountH].lockerID = lockerUnassigned;
            break;
    }
    allUsersH[userCountH].batterySubscription = batterySubscription;
    allUsersH[userCountH].batteriesOut = batteriesOut;
    allUsersH[userCountH].podID = podID;

    // generate the user id, a 32 byte value
    // [community byte 1][community byte 2][locker byte 3][podID (1/2 byte) | 0000 (1/2 byte)]
    uint32_t actualUid = ((((uint32_t)communityID_H << 16) | ((uint32_t)locker << 8)) | 
                           ((uint32_t)podID << 4));
    allUsersH[userCountH].uid = actualUid;

    userCountH++;

#ifdef debug
    printf("UserID (decimal):%u\n\r", actualUid);
    printf("**************************\n\r");
#endif
}

/**
 * Initialize system with users from users.txt on SD card
 */
void initializeUsersFromSD_H() 
{
    spiSD();
    FILE *fp = fopen("/sd/users.txt", "r");
    
    if (fp == NULL) {
#ifdef debug
        printf("User text file can't be opened");
#endif
        return;
    }

    // the first line of the user file has the headers for the following roles - get to the next line 
    char line[180];
    if (fgets(line, 180, fp) != NULL) {
#ifdef debug
        printf("Format of text file:\n\r%s\n\r", line);
        printf("**************************\n\r");
#endif
    }

    // read a set of six values at a time, corresponding to a line, from the user text file. Generate a user per line.
    uint32_t rfid;
    int32_t accountCredit;
    int32_t locker;
    int32_t batterySubscription;
    int32_t batteriesOut;
    int32_t podID;
    while (fscanf(fp, "%u %d %d %d %d %d", &rfid, &accountCredit, &locker, &batterySubscription, &batteriesOut, &podID) != EOF) {
#ifdef debug
        printf("rfid: %u\n\r accountCredit: %d\n\r locker: %d\n\r batterySubscription: %d\n\r batteriesOut: %d\n\r podID: %d\n\r", 
                   rfid, accountCredit, locker, batterySubscription, batteriesOut, podID);
#endif
       createNewUserH(rfid, accountCredit, locker, batterySubscription, batteriesOut, podID);
    }
    fclose(fp);

#ifdef debug
        printf("Users created\n\r");
#endif
}

/**
 *  Initialize posho functionality 
 */
void initializePoshoFunctionality() {
    spiSD();
    FILE *fp = fopen("/sd/poshoInit.txt", "r");
    
    if (fp == NULL) {
#ifdef debug
        printf("User text file can't be opened");
#endif
        return;
    }

    // read a price per kg value.
    int32_t poshoPricePerKg;
    int32_t timeForOneKg;
    int32_t timeForTwoKg;
    int32_t timeForThreeKg;

    if (fscanf(fp,"%d %*c %*s",&poshoPricePerKg)!= 1) writeError("Posho: cannot read price to use");
    if (fscanf(fp,"%d %*c %*s",&timeForOneKg)!= 1) writeError("Posho: cannot read time to use");
    if (fscanf(fp,"%d %*c %*s",&timeForTwoKg)!= 1) writeError("Posho: cannot read time to use");
    if (fscanf(fp,"%d %*c %*s",&timeForThreeKg)!= 1) writeError("Posho: cannot read time to use");
    fclose(fp);

    poshoPricePerKgH = poshoPricePerKg;
    timeForOneKgH = timeForOneKg;
    timeForTwoKgH = timeForTwoKg;
    timeForThreeKgH = timeForThreeKg;

#ifdef debug
        printf("Posho values initialized\n\r");
        printf("Price per kg: %d\n\r", poshoPricePerKgH);
        printf("Time for 1: %d, 2: %d, and 3: %d kilograms\n\r", timeForOneKgH, timeForTwoKgH, timeForThreeKgH);
#endif
}

/*************************************************************************************************/
/* Logging */
/*************************************************************************************************/
/**
 * Log an action from a selection of predefined actions done by the current user
 * Actions used with this method should require a known user to be using system
 * @param action - enumerated HubActionForLogging that selects from predefined actions
 * @param userIndex - int corresponding to the index of the user in the user table
 */
void logActionWithUserInfoH(enum HubActionForLoggingH action, int userIndex)
{ 
    // Append to a log text file
    spiSD();
    char * name = "/sd/HubLog.txt";
    FILE *fp;
    fp = fopen(name, "a");
    
    // get the time and append it to an output buffer
    time_t seconds = time(NULL);
    char logLine[180];
    sprintf(logLine, "%x", seconds);

    // append relevant information for action
    switch (action) {
        case hubAction_BatteryRfidScanned:
            strcat(logLine, " RB ");
            break;
        case hubAction_PoshoRfidScanned:
            strcat(logLine, " RP ");
            break;
        case hubAction_Exit:
            strcat(logLine, " X ");
            break;
        case hubAction_OneBatteryDropped:
            strcat(logLine, " BD 1 ");
            break;
        case hubAction_TwoBatteryDropped:
            strcat(logLine, " BD 2 ");
            break;
        case hubAction_ThreeBatteryDropped:
            strcat(logLine, " BD 3 ");
            break;
        case hubAction_OneBatteryPicked:
            strcat(logLine, " BP 1 ");
            break;
        case hubAction_TwoBatteryPicked:
            strcat(logLine, " BP 2 ");
            break;
        case hubAction_ThreeBatteryPicked:
            strcat(logLine, " BP 3 ");
            break;
        case hubAction_OneKiloPosho:
            strcat(logLine, " PO 1 ");
            break;
        case hubAction_TwoKiloPosho:
            strcat(logLine, " PO 2 ");
            break;
        case hubAction_ThreeKiloPosho:
            strcat(logLine, " PO 3 ");
            break;
    }

    // append general user information
    char rfidBuffer[20];
    char uidBuffer[20];
    char acctBalanceBuffer[20];
    sprintf(rfidBuffer, "%u ", allUsersH[userIndex].rfid);                                                                                 
    sprintf(uidBuffer, "%u ", allUsersH[userIndex].uid);
    sprintf(acctBalanceBuffer, "%d\n", allUsersH[userIndex].accountCredit);
    strcat(logLine, rfidBuffer);
    strcat(logLine, uidBuffer);
    strcat(logLine, acctBalanceBuffer);

    // write the line to the log file and close the file
    fputs(logLine, fp);
    fclose(fp);

#ifdef debug
    printf("%s\n\r", logLine);
#endif

}

/**
 * Log an error - RFID not associated with a known user is scanned
 * @param unknownRfid - uint32 for the unique ID of the unknown rfid tag
 */
void logErrorUnknownRfidScannedH(uint32_t unknownRfid, HubLoggingRfidSourceH source)
{ 
    // Append to a log text file
    char * name = "/sd/HubLog.txt";
    spiSD();
    FILE *fp;
    fp = fopen(name, "a");
    
    // get the time and append it to an output buffer
    time_t seconds = time(NULL);
    char logLine[180];
    sprintf(logLine, "%x", seconds);

    // RFID action
    switch (source) {
        case source_battery:
            strcat(logLine, " R X B ");
            break;
        case source_posho:
            strcat(logLine, " R X P");
            break;
        case source_incubator:
            strcat(logLine, " R X I");
            break;
    }

    // include just the RFID (indicates that no known user was found)
    char rfidBuffer[20];
    sprintf(rfidBuffer, "%u ", unknownRfid);                                                                                 
    strcat(logLine, rfidBuffer);
    strcat(logLine, "\n");

    // write the line to the log file and close the file
    fputs(logLine, fp);
    fclose(fp);

#ifdef debug
    printf("%s\n\r", logLine);
#endif

}

/*************************************************************************************************/
/* Housekeeping - flag clearing, cancelling, check time, update user table  */
/*************************************************************************************************/
/*
 *  Reset all user initiated flags. Useful after sequences of events where flags may have 
 *  piled up from misc inputs and you don't want the system to act seemingly sporadically.
 */
void clearAllUserInitiatedFlagsH() {
    flagButtonOneH = 0;
    flagButtonTwoH = 0;
    flagButtonThreeH = 0;
    flagButtonFourH = 0;
    flagBatteryRfidH = 0;
    flagPoshoRfidH = 0;
    flagIncubatorRfidH = 0;
}

/*
 * User presses the button corresponding to an exit. Returns to home screen
 * after logging the action.
 * @param userIndex - int representing index of user in user table 
 */
void cancelPressedH(int userIndex) {
    clearAllUserInitiatedFlagsH();
    currentScreenH = initialScanRfid;
    logActionWithUserInfoH(hubAction_Exit, userIndex);
}

/**
 *  Do if time check flag is set 
 *  reads the unix time, converts into human readable format, and displays on PC
 */
void doTimeCheckH()
{
    flagTimeCheckH = 0;
    time_t seconds = time(NULL);
    printf("%s\n\r", ctime(&seconds));
}

/**
 *  Do if update user table flag is set 
 */
void doUpdateUserTableH()
{
    flagUpdateUserTableH = 0;
    // Write to a users.txt file
    spiSD();
    char * name = "/sd/users.txt";
    FILE *fp;
    fp = fopen(name, "w");

    // output the header for the users.txt file
    fputs("rfid accountCredit locker batterySubscription batteriesOut podID\n", fp);
    
    // output buffer
    char logLine[180];

    for (int i = 0; i < userCountH; i++) {
        // get all the needed information in strings
        sprintf(logLine, "%u ", allUsersH[i].rfid);
        char accountCreditBuffer[20];
        char* lockerBuffer;
        char batterySubscriptionBuffer[20];
        char batteriesOutBuffer[20];
        char podIdBuffer[20];

        sprintf(accountCreditBuffer, "%d ", allUsersH[i].accountCredit);
        switch (allUsersH[i].lockerID) {
            case lockerOne:
                lockerBuffer = "1 ";
                break;
            case lockerTwo:
                lockerBuffer = "2 ";
                break;
            case lockerThree:
                lockerBuffer = "3 ";
                break;
            case lockerFour:
                lockerBuffer = "4 ";
                break;
            case lockerUnassigned:
            default:
                lockerBuffer = "0 ";
                break;
        }
        sprintf(batterySubscriptionBuffer, "%d ", allUsersH[i].batterySubscription);
        sprintf(batteriesOutBuffer, "%d ", allUsersH[i].batteriesOut);
        sprintf(podIdBuffer, "%d\n", allUsersH[i].podID);

        // concatenate all the strings
        strcat(logLine, accountCreditBuffer);
        strcat(logLine, lockerBuffer);
        strcat(logLine, batterySubscriptionBuffer);
        strcat(logLine, batteriesOutBuffer);
        strcat(logLine, podIdBuffer);

        // write the line to the log file
        fputs(logLine, fp);

#ifdef debug
    printf("%s\n\r", logLine);
#endif
    }
    fclose(fp);
}

/*************************************************************************************************/
/* Battery Services */
/*************************************************************************************************/
/**
 *  Do if Battery RFID flag is set. Read RFID tag and check user status. 
 *  If there's a valid battery subscription, display drop off and/or pick up. 
 *  Otherwise, returns to initial screen.
 */
void doBatteryRfidH()
{
    flagBatteryRfidH = 0;

    // is there a new readable card?
    if (!BatteryRfChipH.PICC_IsNewCardPresent()) {
        return;
    }

    if (!BatteryRfChipH.PICC_ReadCardSerial()) {
#ifdef debug
        printf("Card not readable\n\r");
#endif
        return;
    }
    
    // get the id of the scanned tag 
    uint8_t tempReadingRfid[4]; 
    for(uint8_t i = 0; i < BatteryRfChipH.uid.size; i++) {
        tempReadingRfid[i] = BatteryRfChipH.uid.uidByte[i];
    }
    
    // concatenate the 4 bytes into a single integer via bit shifting
    uint32_t actualRfid = ((((uint32_t)tempReadingRfid[0] << 24) | 
                              ((uint32_t)tempReadingRfid[1] << 16)) | 
                             (((uint32_t)tempReadingRfid[2] << 8) | 
                              ((uint32_t)tempReadingRfid[3] << 0)));

    // find the user info
    char foundUserFlag = 0;
    int foundUserIndex;
    for (int i = 0; i < userCountH; i++) {
        if (allUsersH[i].rfid == actualRfid) {
            currentUserH = i;
            foundUserIndex = i;
            foundUserFlag = 1;
            break;
        }
    }

    // if the user is found, set the global variable for current user interacting with the system
    // if the user isn't found, log that an rfid without a user was used and display
    if (!foundUserFlag) {
#ifdef debug
        printf("User not found\n\r");
        printf("ID:%u\n\r", actualRfid);
#endif
        // log the error interaction
        logErrorUnknownRfidScannedH(actualRfid, source_battery);

        // let user know tag wasn't found
        TFT_H.cls();
        TFT_H.locate(0,0);
        TFT_H.printf("User not found\n\r");
        TFT_H.printf("ID:%u\n\r", actualRfid);
        wait(1);
        currentScreenH = initialScanRfid;
        return;
    }
    
    // log user scan
    logActionWithUserInfoH(hubAction_BatteryRfidScanned, foundUserIndex);

    // Display info about the user 
    char authorizationFlag = 0;
    TFT_H.cls();
    TFT_H.locate(0,0);
    TFT_H.printf("UserID: %u\n\r", actualRfid);
    TFT_H.printf("Balance:%d\n\r", allUsersH[foundUserIndex].accountCredit);
    if (allUsersH[foundUserIndex].batterySubscription > 0) {
        authorizationFlag = 1;
    }
    TFT_H.printf("Authorization:%s\n\r", (authorizationFlag)? "YES":"NO");

#ifdef debug
    printf("User:");
    for(uint8_t i = 0; i < BatteryRfChipH.uid.size; i++)
    {
        uint8_t uidByte = BatteryRfChipH.uid.uidByte[i];
        printf(" %d", uidByte);
    }
    printf("\n\rUserID: %u\n\r", actualRfid);
#endif

    // if not authorized for batteries, return to main screen
    wait(1);
    if (!authorizationFlag) {
        currentScreenH = initialScanRfid;
        return;
    }

    // otherwise, ask user if the user wants to pick up or drop off batteries?
    TFT_H.locate(0,160);
    TFT_H.printf("Battery Action?\n\r");
    TFT_H.locate(0,200);

    uint8_t maxBatteries = allUsersH[foundUserIndex].batterySubscription;
    uint8_t outBatteries = allUsersH[foundUserIndex].batteriesOut;

    if ((maxBatteries - outBatteries) == 0) {   // can only drop off
        TFT_H.printf(" drop           exit");
    } else if (outBatteries == 0) {             // can only pick up 
        TFT_H.printf(" pick           exit");
    } else {                                    // can pickup or dropoff                 
        TFT_H.printf(" pick     drop  exit");
    }

    // go to action selecting screen and clear buttons beforehand
    currentScreenH = batterySelectAction;
    clearAllUserInitiatedFlagsH();
    return;
}

/**
 *  Do if user selects to pickup a battery. Determines how many batteries
 *  a user can pickup based off user info and displays corresponding text
 *  @param userIndex - int representing index of user in user table
 */
void batteryPickUpH(int userIndex) {
    TFT_H.cls();
    TFT_H.locate(0,0);
    TFT_H.printf("UserID: %u\n\r\n\r", allUsersH[userIndex].rfid);

    TFT_H.printf("Action: PICK UP\n\r");
    TFT_H.locate(0,160);
    TFT_H.printf("How many batteries?\n\r");
    TFT_H.locate(0,200);
    switch (allUsersH[userIndex].batterySubscription - allUsersH[userIndex].batteriesOut) {
        case 1: {
            TFT_H.printf("  1            exit");
            break;
        }
        case 2: {
            TFT_H.printf("  1    2       exit");
            break;
        }
        case 3: {
            TFT_H.printf("  1    2    3  exit");
            break;
        }
    }

    // go to wait for selection input and reset button flags
    currentScreenH = batterySelectNumberForPickup;
    clearAllUserInitiatedFlagsH();
    return;
}

/**
 *  Do if user selects to dropoff a battery. Determines how many batteries
 *  a user can pickup based off user info and displays corresponding text
 *  @param userIndex - int representing index of user in user table
 */
void batteryDropOffH(int userIndex) {
    TFT_H.cls();
    TFT_H.locate(0,0);
    TFT_H.printf("UserID: %u\n\r\n\r", allUsersH[userIndex].rfid);

    TFT_H.printf("Action: DROP OFF\n\r");
    TFT_H.locate(0,160);
    TFT_H.printf("How many batteries?\n\r");
    TFT_H.locate(0,200);
    switch (allUsersH[userIndex].batteriesOut) {
        case 1: {
            TFT_H.printf("  1            exit");
            break;
        }
        case 2: {
            TFT_H.printf("  1    2       exit");
            break;
        }
        case 3: {
            TFT_H.printf("  1    2    3  exit");
            break;
        }
    }
    
    // go to wait for selection input and reset button flags
    currentScreenH = batterySelectNumberForDropoff;
    clearAllUserInitiatedFlagsH();
    return;
}

/**
 *  Do after use selects number of batteries to drop off. 
 *  Logs the action, changes user info, transmits instructions to other systems
 *  @param numBatteries - int for number of batteries selected to drop off
 *  @param userIndex - int representing index of user in user table
 */
void batteryDropOffH(int numBatteries, int userIndex) {
    switch(numBatteries) {
        case 1:
            logActionWithUserInfoH(hubAction_OneBatteryDropped, userIndex);
            txBatteryDropOff(1);
            allUsersH[userIndex].batteriesOut--;
            break;
        case 2:
            logActionWithUserInfoH(hubAction_TwoBatteryDropped, userIndex);
            txBatteryDropOff(2);
            allUsersH[userIndex].batteriesOut -= 2;
            break;
        case 3:
            logActionWithUserInfoH(hubAction_ThreeBatteryDropped, userIndex);
            txBatteryDropOff(3);
            allUsersH[userIndex].batteriesOut -= 3;
            break;
    }

    currentScreenH = initialScanRfid;
    return;
}

/**
 *  Do after use selects number of batteries to pick up. 
 *  Logs the action, changes user info, transmits instructions to other systems
 *  @param numBatteries - int for number of batteries selected to pick up
 *  @param userIndex - int representing index of user in user table
 */
void batteryPickUpH(int numBatteries, int userIndex) {
    switch(numBatteries) {
        case 1:
            logActionWithUserInfoH(hubAction_OneBatteryPicked, userIndex);
            txBatteryPickUp(1);
            allUsersH[userIndex].batteriesOut++;
            break;
        case 2:
            logActionWithUserInfoH(hubAction_TwoBatteryPicked, userIndex);
            txBatteryPickUp(2);
            allUsersH[userIndex].batteriesOut += 2;
            break;
        case 3:
            logActionWithUserInfoH(hubAction_ThreeBatteryPicked, userIndex);
            txBatteryPickUp(3);
            allUsersH[userIndex].batteriesOut += 3;
            break;
    }

    currentScreenH = initialScanRfid;
    return;
}

/*************************************************************************************************/
/* Misc Services */
/*************************************************************************************************/
/**
 *  Do if Posho RFID flag is set. Reads rfid and checks user table for a positive balance. 
 */
void doPoshoRfidH()
{
    flagPoshoRfidH = 0;

    // is there a new readable card?
    if (!PoshoRfChipH.PICC_IsNewCardPresent()) {
        return;
    }

    if (!PoshoRfChipH.PICC_ReadCardSerial()) {
#ifdef debug
        printf("Card not readable\n\r");
#endif
        return;
    }
    
    // is the posho already in use?
    if (flagPoshoAlreadyInUseH) {
        TFT_H.cls();
        TFT_H.locate(0,0);
        TFT_H.printf("Posho in use\n\r");
        wait(1);
        currentScreenH = initialScanRfid;
        return;
    }

    // get the id of the scanned tag 
    uint8_t tempReadingRfid[4]; 
    for(uint8_t i = 0; i < PoshoRfChipH.uid.size; i++) {
        tempReadingRfid[i] = PoshoRfChipH.uid.uidByte[i];
    }
    
    // concatenate the 4 bytes into a single integer via bit shifting
    uint32_t actualRfid = ((((uint32_t)tempReadingRfid[0] << 24) | 
                              ((uint32_t)tempReadingRfid[1] << 16)) | 
                             (((uint32_t)tempReadingRfid[2] << 8) | 
                              ((uint32_t)tempReadingRfid[3] << 0)));

    // find the user info
    char foundUserFlag = 0;
    int foundUserIndex;
    for (int i = 0; i < userCountH; i++) {
        if (allUsersH[i].rfid == actualRfid) {
            currentUserH = i;
            foundUserIndex = i;
            foundUserFlag = 1;
            break;
        }
    }

    // if the user is found, set the global variable for current user interacting with the system
    // if the user isn't found, log that an rfid without a user was used and display
    if (!foundUserFlag) {
#ifdef debug
        printf("User not found\n\r");
        printf("ID:%u\n\r", actualRfid);
#endif
        // log the error interaction
        logErrorUnknownRfidScannedH(actualRfid, source_posho);

        // let user know tag wasn't found
        TFT_H.cls();
        TFT_H.locate(0,0);
        TFT_H.printf("User not found\n\r");
        TFT_H.printf("ID:%u\n\r", actualRfid);
        wait(1);
        currentScreenH = initialScanRfid;
        return;
    }
    
    // log user scan
    logActionWithUserInfoH(hubAction_PoshoRfidScanned, foundUserIndex);

    // Display info about the user 
    char authorizationFlag = 0;
    TFT_H.cls();
    TFT_H.locate(0,0);
    TFT_H.printf("UserID: %u\n\r", actualRfid);
    int userAccountBalance = allUsersH[foundUserIndex].accountCredit;
    TFT_H.printf("Balance:%d\n\r", userAccountBalance);
    if (userAccountBalance > 1*poshoPricePerKgH) {
        authorizationFlag = 1;
    }
    TFT_H.printf("Authorization:%s\n\r", (authorizationFlag)? "YES":"NO");


    // if not authorized for batteries, return to main screen
    wait(1);
    if (!authorizationFlag) {
        currentScreenH = initialScanRfid;
        return;
    }

    // otherwise, ask user how may kg of material to process?
    TFT_H.cls();
    TFT_H.locate(0,0);
    TFT_H.printf("UserID: %u\n\r\n\r", actualRfid);

    TFT_H.printf("Action: Posho\n\r");
    TFT_H.locate(0,160);
    TFT_H.printf("How many kilos?\n\r");
    TFT_H.locate(0,200);
    if (userAccountBalance > 3*poshoPricePerKgH) {
        TFT_H.printf("  1    2    3  exit");
    } else if (userAccountBalance > 2*poshoPricePerKgH) {
        TFT_H.printf("  1    2       exit");
    } else {
        TFT_H.printf("  1            exit");
    }

    // go to wait for selection input and reset button flags
    currentScreenH = poshoSelectKilograms;
    clearAllUserInitiatedFlagsH();
    return;
}

/**
 *  Prepare to active the posho and husker for serial use. Change user balance as necessary
 *  and send corresponding signal.
 *  @param numKilograms - int for number of kilograms to be processed
 *  @param userIndex - int representing index of user in user table
 */
void poshoSerialUse(int numKilograms, int userIndex) {
    flagPoshoAlreadyInUseH = 1;
    switch (numKilograms) {
        case 1:
            tickPoshoInUseClearH.attach(&interruptPoshoInUseClearH, (float)timeForOneKgH);
            logActionWithUserInfoH(hubAction_OneKiloPosho, userIndex);
            break;
        case 2:
            tickPoshoInUseClearH.attach(&interruptPoshoInUseClearH, (float)timeForTwoKgH);
            logActionWithUserInfoH(hubAction_TwoKiloPosho, userIndex);
            break;
        case 3:
            tickPoshoInUseClearH.attach(&interruptPoshoInUseClearH, (float)timeForThreeKgH);
            logActionWithUserInfoH(hubAction_ThreeKiloPosho, userIndex);
            break;
    } 
    allUsersH[userIndex].accountCredit -= numKilograms*poshoPricePerKgH;
    TFT_H.cls();
    TFT_H.locate(0,0);
    TFT_H.printf("UserID: %u\n\r\n\r", allUsersH[userIndex].rfid);
    TFT_H.printf("New balance: %d\n\r", allUsersH[userIndex].accountCredit);
    txPoshoSerialUse(numKilograms);
    wait(1);
    currentScreenH = initialScanRfid;
    return;
}

/**
 *  Do if Incubator RFID flag is set. Add incubator functionality later.
 */
void doIncubatorRfidH()
{
    flagIncubatorRfidH = 0;
}

/*************************************************************************************************/
/* Public Methods */
/*************************************************************************************************/
/**
* Initialise for a hub
* fp is the config file if additonal information is needed.
*/
void initialiseHub(FILE *fp){
#ifdef debug
    printf("Initializing Hub\n\r");
#endif

    // Read in hub address and channel
    if (fscanf (fp,"%x %*c %*s",&channel )!=1) writeError("Hub config: cannot read channel");
    if (fscanf (fp,"%llx %*c %*s",&addrLcker )!=1) writeError("Hub config: cannot read hub address");

#ifdef debug
    printf("  Channel:%x, Hub Address %llx \n\r",channel, addrLcker);
#endif

    // Setup nrf
#ifdef debug
    printf("Steup doNrf \n\r");
#endif
    spiNrf();
    nrf1.quickTxSetup(channel, addrLcker);
#ifdef debug
    nrf1.printDetails();
    nrf1.checkStatus();
    printf("Setup doNrf complete [nrf:%s]\n\r",nrf1.statusString());
#endif

    // Other initialization routines
    initializeTimeH();
    initializeInterruptsH();
    initializeRfidReadersH();
    initializeLCD_H();
    initializeUsersFromSD_H();
    initializePoshoFunctionality();
}

void loopHub(){
    while (true) {
        // put interrupts here that should supercede anything else
        if (flag1sH) { do1sH(); }

        // put interrupts here that may be active depending on screen stage
        switch(currentScreenH) {
            case initialScanRfid: {
                    TFT_H.cls();
                    TFT_H.locate(0,0);
                    TFT_H.printf("Please scan your ID");
                    currentScreenH = waitForRfid;
                    clearAllUserInitiatedFlagsH();
            }
            case waitForRfid: {
                if (flagTimeCheckH) doTimeCheckH();
                if (flagBatteryRfidH) doBatteryRfidH();
                if (flagPoshoRfidH) doPoshoRfidH();
                if (flagIncubatorRfidH) doIncubatorRfidH();
                if (flagUpdateUserTableH) doUpdateUserTableH();
                break;
            }
            case batterySelectAction: {
                uint8_t maxBatteries = allUsersH[currentUserH].batterySubscription;
                uint8_t outBatteries = allUsersH[currentUserH].batteriesOut;

                if ((maxBatteries - outBatteries) == 0) {
                    if (flagButtonOneH) batteryDropOffH(currentUserH);
                    if (flagButtonFourH) cancelPressedH(currentUserH);
                } else if (outBatteries == 0) { 
                    if (flagButtonOneH) batteryPickUpH(currentUserH);
                    if (flagButtonFourH) cancelPressedH(currentUserH);
                } else {                                                     
                    if (flagButtonOneH) batteryPickUpH(currentUserH);
                    if (flagButtonThreeH) batteryDropOffH(currentUserH);
                    if (flagButtonFourH) cancelPressedH(currentUserH);
                }

                break;
            }
            case batterySelectNumberForDropoff: {
                switch (allUsersH[currentUserH].batteriesOut) {
                    case 1: {
                        if (flagButtonOneH) batteryDropOffH(1, currentUserH);
                        if (flagButtonFourH) cancelPressedH(currentUserH);
                        break;
                    }
                    case 2: {
                        if (flagButtonOneH) batteryDropOffH(1, currentUserH);
                        if (flagButtonTwoH) batteryDropOffH(2, currentUserH);
                        if (flagButtonFourH) cancelPressedH(currentUserH);
                        break;
                    }
                    case 3: {
                        if (flagButtonOneH) batteryDropOffH(1, currentUserH);
                        if (flagButtonTwoH) batteryDropOffH(2, currentUserH);
                        if (flagButtonThreeH) batteryDropOffH(3, currentUserH);
                        if (flagButtonFourH) cancelPressedH(currentUserH);
                        break;
                    }
                }
                break;
            }
            case batterySelectNumberForPickup: {
                switch (allUsersH[currentUserH].batterySubscription - allUsersH[currentUserH].batteriesOut) {
                    case 1: {
                        if (flagButtonOneH) batteryPickUpH(1, currentUserH);
                        if (flagButtonFourH) cancelPressedH(currentUserH);
                        break;
                    }
                    case 2: {
                        if (flagButtonOneH) batteryPickUpH(1, currentUserH);
                        if (flagButtonTwoH) batteryPickUpH(2, currentUserH);
                        if (flagButtonFourH) cancelPressedH(currentUserH);
                        break;
                    }
                    case 3: {
                        if (flagButtonOneH) batteryPickUpH(1, currentUserH);
                        if (flagButtonTwoH) batteryPickUpH(2, currentUserH);
                        if (flagButtonThreeH) batteryPickUpH(3, currentUserH);
                        if (flagButtonFourH) cancelPressedH(currentUserH);
                        break;
                    }
                }
                break;
            }
            case poshoSelectKilograms: {
                if (allUsersH[currentUserH].accountCredit > 3*poshoPricePerKgH) {
                    if (flagButtonOneH) poshoSerialUse(1, currentUserH);
                    if (flagButtonTwoH) poshoSerialUse(2, currentUserH);
                    if (flagButtonThreeH) poshoSerialUse(3, currentUserH);
                    if (flagButtonFourH) cancelPressedH(currentUserH);
                } else if (allUsersH[currentUserH].accountCredit > 2*poshoPricePerKgH) {
                    if (flagButtonOneH) poshoSerialUse(1, currentUserH);
                    if (flagButtonTwoH) poshoSerialUse(2, currentUserH);
                    if (flagButtonFourH) cancelPressedH(currentUserH);
                } else {
                    if (flagButtonOneH) poshoSerialUse(1, currentUserH);
                    if (flagButtonFourH) cancelPressedH(currentUserH);
                }
            }
        }
    }
}