#include <Pushbutton.h>
#include <TroykaTextLCD.h>
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_PN532.h>
#include "mlt_core.h"
TroykaTextLCD lcd;
//pins
#define PIN_BUTTON_UP 4
#define PIN_BUTTON_DOWN 5
#define PIN_BUTTON_BUY 6
#define PIN_BUZZ 10
#define PIN_IR_SENDER 11
#define PIN_IR_RECEIVER 13 //not used yet
#define PIN_PN532_IRQ 12
Adafruit_PN532 nfc(1, 1);
uint8_t nfcData[16];
uint8_t nfcUID[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID
uint8_t nfcUIDLength; // Length of the UID (4 or 7 bytes depending on card type)
uint8_t lastnfcUID[] = { 0, 0, 0, 0, 0, 0, 0 }; // Previous NFC UID
uint8_t lastnfcUIDLength; // Length of the UID (4 or 7 bytes depending on card type)
int irqPrev = HIGH;
//NFC storage format as follows
// 7E 4B- always these 2 bytes
// playerID playerClass playerBalance
// 00 00 00 reserved
// next bytes - shop levels
//UI modes
#define UIMODE_WAITING 0 //basic screen, wait for card entry
#define UIMODE_SHOP_DISPLAY 1 //shop initial screen with the current balance and ability to flash the config to the IR
#define UIMODE_SHOP 2 //shop screen, allowing scrolling and buying
#define UIMODE_WRITE_ERROR 3 //write error, returns in 5 seconds to the shop screen
#define UIMODE_READ_ERROR 4 //read error, returns in 5 seconds to the waiting screen
#define UIMODE_COMPLETE 10 //card write and flash successful (tbd: allow to refresh)
#define UIMODE_NFC_FORMAT 99 //format NFC card and make balance 1000
#define SHOP_POSITIONS 3 //shop options: damage, hp, bullets in magazine
#define SHOP_DEPTH 4 //every shop position has this number of levels. TBD: will make depth variable
#define IR_PRESET 0xA809E8 //starting position of the preset
#define IR_TRAILING_DELAY 500
#define TIMEOUT_SHOPPING 30000 //if no buttons pressed in the shop
#define TIMEOUT_ERROR 5000 //if no buttons pressed elsewere
#define BUZZ_PRESSED_DURATION 100
#define BUZZ_PRESSED_TONE 55
#define BUZZ_SUCCESS_DURATION 300
#define BUZZ_SUCCESS_TONE 110
#define BUZZ_ERROR_DURATION 500
#define BUZZ_ERROR_TONE 220
unsigned long lastUIActionMillis=0; //millis since last action, to support timeouts
int uiMode = UIMODE_WAITING; //UI mode (one of above)
int offset=0; //current item displayed in the shop
int balance=1000; //current player's balance, to be read from the card
int playerID;
int playerClass;
uint8_t shopModified[SHOP_POSITIONS];
uint8_t shopCurrent[SHOP_POSITIONS]={0,0,0}; //player's levels in each category
int shopValues[SHOP_POSITIONS][SHOP_DEPTH]={ {20,25,35,50},{100,125,150,200},{30,40,50,60}}; //values of respective categories
int shopPrices[SHOP_POSITIONS][SHOP_DEPTH-1]={ {100,300,800},{100,300,800},{100,300,800}}; //cost to level up in respective categories
char lines[SHOP_POSITIONS][11] = {
"Dam",
"HP",
"Mag"
};
byte symDown[8] = {
0b00000,
0b00000,
0b00000,
0b10001,
0b01010,
0b00100,
0b00000,
0b00000
};
byte symUp[8] = {
0b00000,
0b00000,
0b00000,
0b00100,
0b01010,
0b10001,
0b00000,
0b00000
};
Pushbutton buttonUp(PIN_BUTTON_UP);
Pushbutton buttonDown(PIN_BUTTON_DOWN);
Pushbutton buttonBuy(PIN_BUTTON_BUY);
void setup() {
Serial.begin(9600);
while (!Serial);
//delay(10000);
//Serial.println("Starting up");
drawDisplay();
lcd.begin(16, 2);
lcd.setContrast(27);
lcd.setBrightness(255);
lcd.createChar(0, symUp);
lcd.createChar(1, symDown);
//drawDisplay();
setupNFC();
mltSetup(PIN_IR_SENDER, PIN_IR_RECEIVER);
setUIMode(UIMODE_WAITING);
}
void setupNFC(){
Serial.println("Starting NFC setup...");
// RFID init
nfc.begin();
int versiondata = nfc.getFirmwareVersion();
if (!versiondata) {
Serial.print("Didn't find RFID/NFC reader");
} else {
Serial.println("Found RFID/NFC reader");
// module setup
nfc.SAMConfig();
Serial.println("Waiting for a card ...");
}
}
void drawDisplay(){
lcd.clear();
lcd.setCursor(0,0);
//Serial.println("@@@@@@@@@@@@@@@@@@@@@@@@");//ground the glitch
switch (uiMode) {
case UIMODE_WRITE_ERROR:
case UIMODE_READ_ERROR:
lcd.print("Error!");
lcd.setCursor(0,1);
lcd.print("Please try again");
break;
case UIMODE_COMPLETE:
lcd.print("Card update");
lcd.setCursor(0,1);
lcd.print("successful!");
break;
case UIMODE_WAITING:
Serial.println("Touch the reader"); //THIS ONE
lcd.print("Touch the reader");
lcd.setCursor(0,1);
//Serial.println("to start");
lcd.print("to start");
delay(1000);
break;
case UIMODE_NFC_FORMAT:
lcd.print("Format NFC card");
lcd.setCursor(0,1);
lcd.print("Touch the reader");
break;
case UIMODE_SHOP_DISPLAY:
char buf[32];
sprintf(buf, "Balance: $%d",balance);
Serial.println(buf);
lcd.print(buf);
lcd.setCursor(0,1);
lcd.write(1);
lcd.setCursor(7,1);
lcd.write(0);
lcd.setCursor(15,1);
lcd.print("*");
break;
case UIMODE_SHOP:
bool isFinalOffer = (shopCurrent[offset]==SHOP_DEPTH-1);
bool canAfford = isFinalOffer ? false : balance >= shopPrices[offset][shopCurrent[offset]];
if (isFinalOffer) { //print category and level->level+1
sprintf(buf, "%s %d",lines[offset],shopValues[offset][shopCurrent[offset]]);
} else {
sprintf(buf, "%s %d%c%d",
lines[offset],
shopValues[offset][shopCurrent[offset]],
126, // -> as one symbol
shopValues[offset][shopCurrent[offset]+1]);
}
Serial.println(buf);
lcd.print(buf);
if (!isFinalOffer) { //print price to buy the next level
sprintf(buf,"$%d",shopPrices[offset][shopCurrent[offset]]);
lcd.setCursor(16-strlen(buf),0);
lcd.print(buf);
}
lcd.setCursor(9,1);
sprintf(buf,"$%d",balance); //print current balance
lcd.print(buf);
lcd.setCursor(0,1);
lcd.write(1);
lcd.setCursor(7,1);
lcd.write(0);
if (canAfford){
lcd.setCursor(15,1);
lcd.print("+");
};
break;
}
}
bool buyCurrentPosition(){
bool isFinalOffer = (shopCurrent[offset]==SHOP_DEPTH-1);
bool canAfford = isFinalOffer ? false : balance >= shopPrices[offset][shopCurrent[offset]];
if (canAfford) {
balance-=shopPrices[offset][shopCurrent[offset]];
shopCurrent[offset]++;
shopModified[offset]=true;
drawDisplay();
}
return canAfford;
}
void handleShopping(){
bool changed=false;
if (buttonUp.getSingleDebouncedPress()) {
Serial.println("Up pressed");
tone(PIN_BUZZ,BUZZ_PRESSED_TONE,BUZZ_PRESSED_DURATION);
offset+=SHOP_POSITIONS-1;
offset %= SHOP_POSITIONS;
changed=true;
}
if (buttonDown.getSingleDebouncedPress()) {
Serial.println("Down pressed");
tone(PIN_BUZZ,BUZZ_PRESSED_TONE,BUZZ_PRESSED_DURATION);
offset++;
offset %= SHOP_POSITIONS;
changed=true;
}
if (buttonBuy.getSingleDebouncedPress()) {
Serial.println("Buy pressed");
if (buyCurrentPosition()) {
tone(PIN_BUZZ,BUZZ_PRESSED_TONE,BUZZ_PRESSED_DURATION);
changed=true;
}
}
if (changed) {
drawDisplay();
lastUIActionMillis=millis();
}
if (millis()-lastUIActionMillis>TIMEOUT_SHOPPING){
setUIMode(UIMODE_WAITING);
offset=0;
}
}
void setUIMode(int newMode){
Serial.print("Setting UI mode to ");
Serial.println(newMode);
uiMode=newMode;
drawDisplay();
lastUIActionMillis=millis();
if (newMode==UIMODE_WAITING || newMode==UIMODE_SHOP || newMode==UIMODE_NFC_FORMAT) {
nfc.startPassiveTargetIDDetection(PN532_MIFARE_ISO14443A);
}
}
void handleShopDisplay() {
if (buttonUp.getSingleDebouncedPress() ||
buttonDown.getSingleDebouncedPress()) {
tone(PIN_BUZZ,BUZZ_PRESSED_TONE,BUZZ_PRESSED_DURATION);
setUIMode(UIMODE_SHOP);
}
if (buttonBuy.getSingleDebouncedPress()) {
flashPresets(true);
tone(PIN_BUZZ,BUZZ_SUCCESS_TONE,BUZZ_SUCCESS_DURATION);
lastUIActionMillis=millis();
}
if (millis()-lastUIActionMillis>TIMEOUT_SHOPPING){
setUIMode(UIMODE_WAITING);
}
}
void handleWaiting() {
if (buttonBuy.getSingleDebouncedPress()) {
setUIMode(UIMODE_NFC_FORMAT);
tone(PIN_BUZZ,BUZZ_PRESSED_TONE,BUZZ_PRESSED_DURATION);
}
handleWaitingNFC();
}
void writeNFCCard(uint8_t playerID, uint8_t playerClass, uint16_t balance, uint8_t shopLevels[]) {
uint8_t data[] = {
0x7E, 0x4B, playerID, playerClass, (uint8_t) (balance >> 8), (uint8_t) (balance & 0xFF), 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
uint8_t success;
for (int i=0; i<SHOP_POSITIONS; i++) {
data[i+8]=shopLevels[i];
}
success = nfc.mifareclassic_WriteDataBlock(4, data);
if (success) {
Serial.println("Data written to block 4:");
for (uint8_t i = 0; i < 16; i++) {
Serial.print(data[i], HEX);
Serial.print(" ");
}
Serial.println();
tone(PIN_BUZZ,BUZZ_SUCCESS_TONE,BUZZ_SUCCESS_DURATION);
setUIMode(UIMODE_COMPLETE);
} else {
Serial.println("Failed to write data to block 4");
tone(PIN_BUZZ,BUZZ_ERROR_TONE,BUZZ_ERROR_DURATION);
setUIMode(UIMODE_WRITE_ERROR);
}
}
uint8_t readNFCCard(bool matchUID){
int irqCurr = digitalRead(PIN_PN532_IRQ);
if (irqCurr == LOW && irqPrev == HIGH) {
uint8_t success;
// Wait for an NFC card to be present
success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, nfcUID, &nfcUIDLength);
if (success) {
// Found a card!
Serial.println("Found an NFC card!");
lastUIActionMillis=millis();
Serial.print("UID Length: ");
Serial.print(nfcUIDLength, DEC);
Serial.println(" bytes");
Serial.print("UID Value: ");
for (uint8_t i = 0; i < nfcUIDLength; i++) {
Serial.print(" 0x");Serial.print(nfcUID[i], HEX);
if (matchUID) { //when we apply a card second time for write, checking that same card was applied
if (lastnfcUID[i]!=nfcUID[i]) {
Serial.println(" ... Wrong card ID!");
setUIMode(UIMODE_READ_ERROR);
tone(PIN_BUZZ,BUZZ_ERROR_TONE,BUZZ_ERROR_DURATION);
return false;
}
} else {
lastnfcUID[i]=nfcUID[i];
}
}
Serial.println("");
lastnfcUIDLength=nfcUIDLength;
// Authenticate the card for reading or writing (Key A or B)
uint8_t keya[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; // Mifare default key for authentication
success = nfc.mifareclassic_AuthenticateBlock(nfcUID, nfcUIDLength, 4, 0, keya);
if (success) {
Serial.println("Authenticated block 4");
// Read data from block 4
success = nfc.mifareclassic_ReadDataBlock(4, nfcData);
if (success) {
Serial.println("Data in block 4:");
for (uint8_t i = 0; i < 16; i++) {
Serial.print(nfcData[i], HEX);
Serial.print(" ");
}
Serial.println();
tone(PIN_BUZZ,BUZZ_SUCCESS_TONE,BUZZ_SUCCESS_DURATION);
return true;
} else {
Serial.println("Failed to read data from block 4");
tone(PIN_BUZZ,BUZZ_ERROR_TONE,BUZZ_ERROR_DURATION);
setUIMode(UIMODE_READ_ERROR);
return false;
}
} else {
Serial.println("Authentication failed");
tone(PIN_BUZZ,BUZZ_ERROR_TONE,BUZZ_ERROR_DURATION);
setUIMode(UIMODE_READ_ERROR);
return false;
}
} else {
tone(PIN_BUZZ,BUZZ_ERROR_TONE,BUZZ_ERROR_DURATION);
return false;
}
}
irqPrev=irqCurr;
return false;
}
void handleWaitingNFC() {
if (readNFCCard(false)) {
balance = (((uint16_t)(nfcData[4]))<<8)+(nfcData[5]);
playerID = nfcData[2];
playerClass = nfcData[3];
for (int i=0;i<SHOP_POSITIONS;i++) {
shopCurrent[i]=nfcData[i+8];
shopModified[i]=false;
}
setUIMode(UIMODE_SHOP_DISPLAY);
}
}
void handleShopAction(){
handleShopping();
handleShopNFCUpdate();
}
void handleShopNFCUpdate() {
if (readNFCCard(true)) {
writeNFCCard(playerID,playerClass,balance,shopCurrent);
if (uiMode==UIMODE_COMPLETE) { //if there was no error
flashPresets(false);
}
}
}
void handleNFCFormat() {
if (readNFCCard(false)) {
uint8_t levels[]={0,0,0};
writeNFCCard(21,0,1000,levels);
}
if (millis()-lastUIActionMillis>TIMEOUT_SHOPPING||
buttonBuy.getSingleDebouncedPress() ||
buttonUp.getSingleDebouncedPress() ||
buttonDown.getSingleDebouncedPress()){
setUIMode(UIMODE_WAITING);
}
}
void flashSinglePreset(int position, int level) {
uint32_t command = IR_PRESET + ((position*SHOP_DEPTH+level) << 8);
Serial.print("Flashing ");
Serial.print(position);
Serial.print(" ");
Serial.print(level);
Serial.print(" ");
Serial.println(command,HEX);
sendRawCommand(command);
delay(IR_TRAILING_DELAY);
}
void flashPresets(bool flashAll) {
for (int i=0; i<SHOP_POSITIONS;i++) {
if (shopModified[i] || flashAll) {
flashSinglePreset(i, shopCurrent[i]);
}
}
}
void loop() {
//Serial.println(digitalRead(PIN_PN532_IRQ));
switch (uiMode) {
case UIMODE_WAITING:
handleWaiting();
break;
case UIMODE_SHOP:
handleShopAction();
break;
case UIMODE_SHOP_DISPLAY:
handleShopDisplay();
break;
case UIMODE_NFC_FORMAT:
handleNFCFormat();
break;
case UIMODE_WRITE_ERROR:
case UIMODE_READ_ERROR:
case UIMODE_COMPLETE:
if (millis()-lastUIActionMillis>TIMEOUT_ERROR){
setUIMode(UIMODE_WAITING);
};
break;
}
}