AfRApay/AfRApay.MateCard/src/main.cpp

236 lines
6.4 KiB
C++

#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include <SPIFFS.h>
#include <WiFiSettings.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <SPI.h>
#include <MFRC522.h>
#include <ESPDateTime.h>
#include <DateTimeTZ.h>
#include <rdm6300.h>
#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);
u8g2.begin();
drawLogo(u8g2, LOGO_MATECARD);
u8g2.sendBuffer();
SPI.begin(PIN_HSPI_SCLK, PIN_HSPI_MISO, PIN_HSPI_MOSI);
SPIFFS.begin(true);
WiFiSettings.hostname = "afrapay-";
apiUrl = WiFiSettings.string("AfRApay.Web API", "http://192.168.50.170:5296");
WiFiSettings.connect();
rdm6300.begin(PIN_HWSERIAL_RX);
mfrc522.PCD_Init();
mfrc522.PCD_DumpVersionToSerial();
DateTime.setTimeZone(TZ_Europe_Berlin);
DateTime.begin();
updateOLED(u8g2, state);
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);
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