#include #include #include #include #include #include #include #include #include #include #include #include #include "wifiFix.h" #include "pitches.h" #include "utils.h" #include "oled.h" #include "cardReader.h" #include "vars.h" U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); MFRC522 mfrc522(PIN_HSPI_SS, PIN_HSPI_RST); WiFiClient *wifi = new WiFiClientFixed(); HTTPClient *http = new HTTPClient(); Rdm6300 rdm6300; auto mfrc522reader = MFRC522CardReader(&mfrc522); auto rdm6300reader = RDM6300CardReader(&rdm6300); CardReader* readers[2] = {&mfrc522reader, &rdm6300reader}; e_state state = STATE_IDLE; String apiUrl = ""; const int gTimeout = 5000; const int scanTimeout = 15000; String scannedCardId = ""; String lastStatusText = ""; volatile bool interruptFired = false; unsigned long timer = 0; void IRAM_ATTR TransactInterruptHandler() { if (interruptFired || state != STATE_IDLE || digitalRead(PIN_INTERRUPT_TRANSACT)) { return; } interruptFired = true; state = STATE_TRANSACT_CARDSCAN; } void IRAM_ATTR LinkInterruptHandler() { if (interruptFired || state != STATE_IDLE || digitalRead(PIN_INTERRUPT_LINK)) { return; } interruptFired = true; state = STATE_LINK_CARDSCAN; } void IRAM_ATTR BalanceInterruptHandler() { if (interruptFired || state != STATE_IDLE || digitalRead(PIN_INTERRUPT_BALANCE)) { return; } interruptFired = true; state = STATE_BALANCE_CARDSCAN; } void IRAM_ATTR CancelInterruptHandler() { if (digitalRead(PIN_INTERRUPT_CANCEL)) return; state = STATE_IDLE; } bool cooldownCheck(long timeout) { if (millis() - timer > timeout) { state = STATE_IDLE; updateOLED(u8g2, state); return true; } return false; } void setup() { Serial.begin(115200); 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_TRANSACT, INPUT_PULLUP); pinMode(PIN_INTERRUPT_BALANCE, INPUT_PULLUP); pinMode(PIN_INTERRUPT_CANCEL, INPUT_PULLUP); pinMode(PIN_INTERRUPT_LINK, INPUT_PULLUP); u8g2.begin(); drawLogo(u8g2, LOGO_MATECARD); u8g2.sendBuffer(); 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(); rdm6300.begin(PIN_HWSERIAL_RX); rdm6300.set_tag_timeout(65); mfrc522.PCD_Init(); mfrc522.PCD_DumpVersionToSerial(); DateTime.setTimeZone(TZ_Europe_Berlin); DateTime.begin(); updateOLED(u8g2, state); attachInterrupt(PIN_INTERRUPT_TRANSACT, TransactInterruptHandler, FALLING); attachInterrupt(PIN_INTERRUPT_BALANCE, BalanceInterruptHandler, FALLING); attachInterrupt(PIN_INTERRUPT_CANCEL, CancelInterruptHandler, FALLING); attachInterrupt(PIN_INTERRUPT_LINK, LinkInterruptHandler, FALLING); } void loop() { if (WiFiClass::status() != WL_CONNECTED) { updateOLED(u8g2, state, "WiFi disconnected :("); state = STATE_IDLE; WiFi.reconnect(); delay(50); return; } if (interruptFired) { timer = millis(); interruptFired = false; } switch (state) { case STATE_IDLE: updateOLED(u8g2, state); break; case STATE_TRANSACT_CARDSCAN: for (CardReader *reader : readers) { if (reader->isNewCardPresent()) { scannedCardId = reader->getCardUid(); lastStatusText = "Card #" + scannedCardId; state = STATE_TRANSACT_VERIFY; return; } } if (!cooldownCheck(scanTimeout)) { updateOLED(u8g2, state, String("1.50€"), String(cooldownSecondsRemaining(scanTimeout, timer))); } break; case STATE_TRANSACT_VERIFY: updateOLED(u8g2, state, lastStatusText); tone(PIN_BUZZER, NOTE_A5, 25); tone(PIN_BUZZER, NOTE_NONE, 150); lastStatusText = cardTransaction(wifi, http, apiUrl, scannedCardId, "-150"); if (lastStatusText.startsWith("S:")) { tone(PIN_BUZZER, NOTE_C7, 650); state = STATE_RESULT_SUCCESS; } else { tone(PIN_BUZZER, NOTE_CS5, 100); tone(PIN_BUZZER, NOTE_NONE, 25); tone(PIN_BUZZER, NOTE_CS5, 400); state = STATE_RESULT_FAILURE; } lastStatusText = lastStatusText.substring(2); timer = millis(); return; case STATE_LINK_CARDSCAN: for (CardReader *reader : readers) { if (reader->isNewCardPresent()) { scannedCardId = reader->getCardUid(); lastStatusText = "Card #" + scannedCardId; state = STATE_LINK_VERIFY; return; } } if (!cooldownCheck(scanTimeout)) { updateOLED(u8g2, state, "Link", String(cooldownSecondsRemaining(scanTimeout, timer))); } break; case STATE_LINK_VERIFY: updateOLED(u8g2, state, lastStatusText); tone(PIN_BUZZER, NOTE_A5, 25); tone(PIN_BUZZER, NOTE_NONE, 150); lastStatusText = cardLink(wifi, http, apiUrl, scannedCardId); if (lastStatusText.startsWith("S:")) { tone(PIN_BUZZER, NOTE_C6, 100); tone(PIN_BUZZER, NOTE_NONE, 10); tone(PIN_BUZZER, NOTE_C6, 100); state = STATE_RESULT_SUCCESS; } else { tone(PIN_BUZZER, NOTE_CS5, 100); tone(PIN_BUZZER, NOTE_NONE, 25); tone(PIN_BUZZER, NOTE_CS5, 400); state = STATE_RESULT_FAILURE; } lastStatusText = lastStatusText.substring(2); timer = millis(); return; case STATE_BALANCE_CARDSCAN: for (CardReader *reader : readers) { if (reader->isNewCardPresent()) { scannedCardId = reader->getCardUid(); lastStatusText = "Card #" + scannedCardId; state = STATE_BALANCE_VERIFY; return; } } if (!cooldownCheck(scanTimeout)) { updateOLED(u8g2, state, "Balance", String(cooldownSecondsRemaining(scanTimeout, timer))); } break; case STATE_BALANCE_VERIFY: updateOLED(u8g2, state, lastStatusText); tone(PIN_BUZZER, NOTE_A5, 25); tone(PIN_BUZZER, NOTE_NONE, 150); lastStatusText = cardBalance(wifi, http, apiUrl, scannedCardId); if (lastStatusText.startsWith("S:")) { lastStatusText = lastStatusText + ":Card #" + scannedCardId; state = STATE_RESULT_DISPLAY; } else { tone(PIN_BUZZER, NOTE_CS5, 100); tone(PIN_BUZZER, NOTE_NONE, 25); tone(PIN_BUZZER, NOTE_CS5, 400); state = STATE_RESULT_FAILURE; } lastStatusText = lastStatusText.substring(2); timer = millis(); case STATE_RESULT_SUCCESS: if (cooldownSecondsRemaining(gTimeout, timer) <= 4) { lastStatusText = lastStatusText + ":Card #" + scannedCardId; state = STATE_RESULT_DISPLAY; } case STATE_RESULT_FAILURE: case STATE_RESULT_DISPLAY: if (!cooldownCheck(gTimeout)) { updateOLED(u8g2, state, lastStatusText, String(cooldownSecondsRemaining(gTimeout, timer))); } break; } delay(50); } #pragma clang diagnostic pop