#include #include #include #include #include #include #include #include #include #include #include "wifiFix.h" #include "pitches.h" #include "utils.h" #include "oled.h" #include "readers.h" #include "vars.h" OLED oled(U8G2_SH1106_128X64_NONAME_F_HW_I2C(U8G2_R0, U8X8_PIN_NONE)); WiFiClient* wifi = new WiFiClientFixed(); HTTPClient* http = new HTTPClient(); Reader* readers[] = { //new MFRC522CardReader(PIN_HSPI_SS, PIN_HSPI_RST), new PN532Reader(PIN_HSPI_CS0), new RDM6300Reader(PIN_HWSERIAL_RX), }; e_state state = STATE_IDLE; e_scanstate scanstate = SCANSTATE_IDLE; String apiUrl = ""; const int gTimeout = 5000; const int scanTimeout = 15000; Reader* scannedCardReader; String scannedCardId = ""; String lastStatusText = ""; bool deposit = false; int transactionAmountIndex = 0; int transactionAmountsDebit[] = {-150, -200}; int transactionAmountsCredit[] = {+100, +200, +500, +1000}; volatile bool toggleTransactionAmountDebit = false; volatile bool toggleTransactionAmountCredit = false; volatile bool interruptFired = false; unsigned long timer = 0; void IRAM_ATTR PN532_IRQ() { PN532Reader::irq++; } void IRAM_ATTR FeliCaInterruptHandler() { if (PN532Reader::toggleMode || scanstate != SCANSTATE_ACTIVE || digitalRead(PIN_INTERRUPT_FELICA)) { return; } PN532Reader::toggleMode = true; } void IRAM_ATTR TransactInterruptHandler() { if (interruptFired || digitalRead(PIN_INTERRUPT_TRANSACT)) { return; } if (state == STATE_IDLE) { interruptFired = true; state = STATE_TRANSACT_CARDSCAN; } else if (state == STATE_TRANSACT_CARDSCAN) { PN532Reader::toggleMode = true; } else { return; } } void IRAM_ATTR LinkInterruptHandler() { if (interruptFired || digitalRead(PIN_INTERRUPT_LINK)) { return; } if (state == STATE_IDLE) { interruptFired = true; state = STATE_LINK_CARD_SCAN; } else if (state == STATE_LINK_CARD_SCAN) { PN532Reader::toggleMode = true; } else if (state == STATE_TRANSACT_CARDSCAN) { toggleTransactionAmountCredit = true; } else { return; } } void IRAM_ATTR BalanceInterruptHandler() { if (interruptFired || digitalRead(PIN_INTERRUPT_BALANCE)) { return; } if (state == STATE_IDLE) { interruptFired = true; state = STATE_BALANCE_CARDSCAN; } else if (state == STATE_BALANCE_CARDSCAN) { PN532Reader::toggleMode = true; } else if (state == STATE_TRANSACT_CARDSCAN) { toggleTransactionAmountDebit = true; } else { return; } } void IRAM_ATTR CancelInterruptHandler() { if (digitalRead(PIN_INTERRUPT_CANCEL)) return; state = STATE_IDLE; } int GetTransactionAmount() { if (deposit) return transactionAmountsCredit[transactionAmountIndex]; else return transactionAmountsDebit[transactionAmountIndex]; } String GetDisplayTransactionAmount() { String result = ""; int total = abs(GetTransactionAmount()); int cents = total % 100; if (PN532Reader::mode == PN532_MIFARE_ISO14443A) { if (deposit) result += "Deposit: "; result += total / 100; result += "."; result += cents; if (cents < 10) result += "0"; result += "€"; } else { // TOKYO MODE if (deposit) result += "Deposit: "; result += "¥"; result += total; } return result; } bool cooldownCheck(long timeout) { if (millis() - timer > timeout) { state = STATE_IDLE; oled.updateOLED(state); return true; } return false; } void updateCardscan() { switch (state) { case STATE_TRANSACT_CARDSCAN: case STATE_LINK_CARD_SCAN: case STATE_LINK_CARD_RESCAN: case STATE_BALANCE_CARDSCAN: if (scanstate == SCANSTATE_ACTIVE) return; for (Reader* reader : readers) { reader->begin(); } scanstate = SCANSTATE_ACTIVE; break; case STATE_IDLE: case STATE_TRANSACT_VERIFY: case STATE_LINK_VERIFY: case STATE_BALANCE_VERIFY: case STATE_RESULT_SUCCESS: case STATE_RESULT_FAILURE: case STATE_RESULT_DISPLAY: if (scanstate == SCANSTATE_IDLE) return; for (Reader* reader : readers) { reader->end(); } scanstate = SCANSTATE_IDLE; break; } } void setup() { Serial.begin(115200); oled.begin(); Serial2.begin(115200, SERIAL_8N1, PIN_HWSERIAL_RX, PIN_HWSERIAL_TX); SPI.begin(PIN_HSPI_SCLK, PIN_HSPI_MISO, PIN_HSPI_MOSI); ledcSetup(0, 5000, 12); pinMode(PIN_INTERRUPT_IRQ_PN532, INPUT_PULLUP); pinMode(PIN_INTERRUPT_TRANSACT, INPUT_PULLUP); pinMode(PIN_INTERRUPT_BALANCE, INPUT_PULLUP); pinMode(PIN_INTERRUPT_FELICA, INPUT_PULLUP); pinMode(PIN_INTERRUPT_CANCEL, INPUT_PULLUP); pinMode(PIN_INTERRUPT_LINK, INPUT_PULLUP); SPIFFS.begin(true); WiFiSettings.hostname = "afrapay-"; apiUrl = WiFiSettings.string("AfRApay.Web API", "http://192.168.50.170:5296"); if (!digitalRead(PIN_INTERRUPT_CANCEL)) WiFiSettings.portal(); else WiFiSettings.connect(); for (Reader* reader : readers) { reader->init(); } DateTime.setTimeZone(TZ_Europe_Berlin); DateTime.begin(); oled.updateOLED(state); attachInterrupt(PIN_INTERRUPT_IRQ_PN532, PN532_IRQ, FALLING); attachInterrupt(PIN_INTERRUPT_TRANSACT, TransactInterruptHandler, FALLING); attachInterrupt(PIN_INTERRUPT_BALANCE, BalanceInterruptHandler, FALLING); attachInterrupt(PIN_INTERRUPT_FELICA, FeliCaInterruptHandler, FALLING); attachInterrupt(PIN_INTERRUPT_CANCEL, CancelInterruptHandler, FALLING); attachInterrupt(PIN_INTERRUPT_LINK, LinkInterruptHandler, FALLING); } void loop() { if (WiFiClass::status() != WL_CONNECTED) { oled.updateOLED(state, "WiFi disconnected :("); state = STATE_IDLE; WiFi.reconnect(); delay(50); return; } updateCardscan(); if (PN532Reader::toggleMode) timer = millis(); if (interruptFired) { timer = millis(); interruptFired = false; } switch (state) { case STATE_IDLE: if (PN532Reader::mode != PN532_MIFARE_ISO14443A) PN532Reader::mode = PN532_MIFARE_ISO14443A; if (deposit == true) deposit = false; if (transactionAmountIndex != 0) transactionAmountIndex = 0; oled.updateOLED(state); break; case STATE_TRANSACT_CARDSCAN: if (toggleTransactionAmountDebit) { if (millis() - timer > 250) { if (deposit) { deposit = false; transactionAmountIndex = -1; } transactionAmountIndex = ++transactionAmountIndex % 2; timer = millis(); } toggleTransactionAmountDebit = false; } if (toggleTransactionAmountCredit) { if (millis() - timer > 250) { if (!deposit) { deposit = true; transactionAmountIndex = -1; } transactionAmountIndex = ++transactionAmountIndex % 4; timer = millis(); } toggleTransactionAmountCredit = false; } for (Reader* reader : readers) { if (reader->isNewCardPresent()) { scannedCardId = reader->getCardUid(); lastStatusText = cardIdDisplay(scannedCardId); scannedCardReader = reader; state = STATE_TRANSACT_VERIFY; return; } } if (!cooldownCheck(scanTimeout)) oled.updateOLED(state, GetDisplayTransactionAmount(), String(cooldownSecondsRemaining(scanTimeout, timer))); break; case STATE_TRANSACT_VERIFY: oled.updateOLED(state, lastStatusText); #ifndef DISABLE_BUZZER if (PN532Reader::mode == PN532_MIFARE_ISO14443A) { tone(PIN_BUZZER, NOTE_A5, 25); tone(PIN_BUZZER, NOTE_NONE, 150); } else { tone(PIN_BUZZER, NOTE_FS7, 100); tone(PIN_BUZZER, NOTE_NONE, 150); } #endif lastStatusText = cardTransaction(wifi, http, apiUrl, scannedCardId, String(GetTransactionAmount()), scannedCardReader->getReaderName()); if (lastStatusText.startsWith("S:")) { #ifndef DISABLE_BUZZER tone(PIN_BUZZER, NOTE_C7, 650); #endif state = STATE_RESULT_SUCCESS; } else { #ifndef DISABLE_BUZZER tone(PIN_BUZZER, NOTE_CS5, 100); tone(PIN_BUZZER, NOTE_NONE, 25); tone(PIN_BUZZER, NOTE_CS5, 400); #endif state = STATE_RESULT_FAILURE; } lastStatusText = lastStatusText.substring(2); timer = millis(); return; case STATE_LINK_CARD_SCAN: for (Reader* reader : readers) { if (reader->isNewCardPresent()) { scannedCardReader = reader; scannedCardId = reader->getCardUid(); if (reader->canHaveUnstableIdentifier()) { reader->reset(); timer = millis(); state = STATE_LINK_CARD_RESCAN; return; } else { lastStatusText = cardIdDisplay(scannedCardId); state = STATE_LINK_VERIFY; return; } } } if (!cooldownCheck(scanTimeout)) { if (PN532Reader::mode == PN532_MIFARE_ISO14443A) oled.updateOLED(state, "Link", String(cooldownSecondsRemaining(scanTimeout, timer))); else oled.updateOLED(state, "リンク", String(cooldownSecondsRemaining(scanTimeout, timer)), true); } break; case STATE_LINK_CARD_RESCAN: if (scannedCardReader->isNewCardPresent()) { if (scannedCardId == scannedCardReader->getCardUid()) { lastStatusText = cardIdDisplay(scannedCardId); state = STATE_LINK_VERIFY; return; } else { lastStatusText = "Unstable identifier"; state = STATE_RESULT_FAILURE; timer = millis(); return; } } if (!cooldownCheck(scanTimeout)) { if (PN532Reader::mode == PN532_MIFARE_ISO14443A) oled.updateOLED(state, "Link - rescan ", String(cooldownSecondsRemaining(scanTimeout, timer))); else oled.updateOLED(state, "リンク - rescan ", String(cooldownSecondsRemaining(scanTimeout, timer)), true); } break; case STATE_LINK_VERIFY: oled.updateOLED(state, lastStatusText); #ifndef DISABLE_BUZZER if (PN532Reader::mode == PN532_MIFARE_ISO14443A) { tone(PIN_BUZZER, NOTE_A5, 25); tone(PIN_BUZZER, NOTE_NONE, 150); } else { tone(PIN_BUZZER, NOTE_FS7, 100); tone(PIN_BUZZER, NOTE_NONE, 150); } #endif lastStatusText = cardLink(wifi, http, apiUrl, scannedCardId, scannedCardReader->getReaderName()); if (lastStatusText.startsWith("S:")) { #ifndef DISABLE_BUZZER tone(PIN_BUZZER, NOTE_C6, 100); tone(PIN_BUZZER, NOTE_NONE, 10); tone(PIN_BUZZER, NOTE_C6, 100); #endif state = STATE_RESULT_SUCCESS; } else { #ifndef DISABLE_BUZZER tone(PIN_BUZZER, NOTE_CS5, 100); tone(PIN_BUZZER, NOTE_NONE, 25); tone(PIN_BUZZER, NOTE_CS5, 400); #endif state = STATE_RESULT_FAILURE; } lastStatusText = lastStatusText.substring(2); timer = millis(); return; case STATE_BALANCE_CARDSCAN: for (Reader* reader : readers) { if (reader->isNewCardPresent()) { scannedCardId = reader->getCardUid(); scannedCardReader = reader; lastStatusText = cardIdDisplay(scannedCardId); state = STATE_BALANCE_VERIFY; return; } } if (!cooldownCheck(scanTimeout)) { if (PN532Reader::mode == PN532_MIFARE_ISO14443A) oled.updateOLED(state, "Balance", String(cooldownSecondsRemaining(scanTimeout, timer))); else oled.updateOLED(state, "残高", String(cooldownSecondsRemaining(scanTimeout, timer)), true); } break; case STATE_BALANCE_VERIFY: oled.updateOLED(state, lastStatusText); #ifndef DISABLE_BUZZER if (PN532Reader::mode == PN532_MIFARE_ISO14443A) { tone(PIN_BUZZER, NOTE_A5, 25); tone(PIN_BUZZER, NOTE_NONE, 150); } else { tone(PIN_BUZZER, NOTE_FS7, 100); tone(PIN_BUZZER, NOTE_NONE, 150); } #endif lastStatusText = cardBalance(wifi, http, apiUrl, scannedCardId, scannedCardReader->getReaderName()); if (lastStatusText.startsWith("S:")) { lastStatusText = lastStatusText + ":" + cardIdDisplay(scannedCardId); state = STATE_RESULT_DISPLAY; } else { #ifndef DISABLE_BUZZER tone(PIN_BUZZER, NOTE_CS5, 100); tone(PIN_BUZZER, NOTE_NONE, 25); tone(PIN_BUZZER, NOTE_CS5, 400); #endif state = STATE_RESULT_FAILURE; } lastStatusText = lastStatusText.substring(2); timer = millis(); case STATE_RESULT_SUCCESS: if (cooldownSecondsRemaining(gTimeout, timer) <= 4) { lastStatusText = lastStatusText + ":" + cardIdDisplay(scannedCardId); state = STATE_RESULT_DISPLAY; } case STATE_RESULT_FAILURE: case STATE_RESULT_DISPLAY: if (!cooldownCheck(gTimeout)) { if (PN532Reader::mode == PN532_MIFARE_ISO14443A) oled.updateOLED(state, lastStatusText, String(cooldownSecondsRemaining(gTimeout, timer))); else oled.updateOLED(state, lastStatusText, String(cooldownSecondsRemaining(gTimeout, timer)), true); } break; } delay(50); } #pragma clang diagnostic pop