Compare commits

...

14 commits

24 changed files with 4352 additions and 538 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,10 @@
# AfRApay.MateCard
This is the software that runs on the MateCard terminal, powered by an ESP32.
## Setting up dev environment
This project uses PlatformIO. Quick setup guide:
- Install PlatformIO (macOS: `brew install platformio` / Arch Linux: `yay -S platformio` / Other: [platformio.org](https://platformio.org/install/cli))
- To compile, run `pio run`
- To compile and upload, run `pio run -t upload`
- To run clang-tidy, run `pio check`.
- Please run `uncrustify --no-backup -c .uncrustify.cfg src/*.cpp include/*.h` before commiting. To install it as a pre-commit hook, run `ln -s ../../hooks/pre-commit.hook ../.git/hooks/pre-commit` in this directory.

View file

@ -0,0 +1,28 @@
#pragma once
#include <Arduino.h>
#include <MFRC522.h>
#include <rdm6300.h>
class CardReader {
public:
virtual bool isNewCardPresent() = 0;
virtual String getCardUid() = 0;
};
class MFRC522CardReader : public CardReader {
private:
MFRC522* iReader;
public:
explicit MFRC522CardReader(MFRC522 *reader);
bool isNewCardPresent() override;
String getCardUid() override;
};
class RDM6300CardReader : public CardReader {
private:
Rdm6300* iReader;
public:
explicit RDM6300CardReader(Rdm6300 *reader);
bool isNewCardPresent() override;
String getCardUid() override;
};

View file

@ -1,155 +1,152 @@
#ifndef AFRAPAY_LOGOS_H
#define AFRAPAY_LOGOS_H
#pragma once
enum e_logo {
LOGO_AFRAPAY, LOGO_CONTACTLESS, LOGO_MATECARD, LOGO_PENDING, LOGO_SUCCESS, LOGO_FAILURE
LOGO_AFRAPAY, LOGO_CONTACTLESS, LOGO_MATECARD, LOGO_PENDING, LOGO_SUCCESS, LOGO_FAILURE
};
#define contactless_width 88
#define contactless_height 52
static unsigned char contactless_bits[] = {
0x00, 0x00, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xE0, 0x07, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C,
0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00,
0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00,
0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00,
0x30, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0,
0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x80, 0x01,
0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x03, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x06, 0x00, 0x00,
0x60, 0x00, 0x00, 0x00, 0x06, 0x0F, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x20,
0x00, 0x00, 0x00, 0x0E, 0x1E, 0xE0, 0x00, 0x18, 0x00, 0x00, 0x30, 0x00,
0x00, 0x00, 0x1E, 0x1E, 0xB0, 0x3F, 0x10, 0x00, 0x00, 0x18, 0x00, 0x00,
0x00, 0x1E, 0x1E, 0x10, 0xE0, 0x33, 0x00, 0x00, 0x08, 0x00, 0x00, 0x1C,
0x3C, 0x3C, 0x18, 0x00, 0x66, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x3C, 0x3C,
0x3C, 0x08, 0x00, 0x46, 0x00, 0x00, 0x04, 0x00, 0x00, 0x3C, 0x3C, 0x3C,
0x08, 0x06, 0xFE, 0x01, 0x00, 0x06, 0x00, 0x30, 0x78, 0x78, 0x38, 0x88,
0x1F, 0xFE, 0x07, 0x00, 0x02, 0x00, 0x78, 0x78, 0x78, 0x78, 0x88, 0x70,
0x02, 0x0C, 0x00, 0x03, 0x00, 0xF0, 0x78, 0x78, 0x78, 0x8C, 0xC0, 0x03,
0x18, 0x00, 0x03, 0x00, 0xF0, 0xF0, 0x70, 0x78, 0x84, 0x00, 0x07, 0x10,
0x00, 0x03, 0x00, 0xE0, 0xF0, 0x70, 0x78, 0x84, 0x01, 0x0C, 0x20, 0x00,
0x03, 0x00, 0xE0, 0xF1, 0x70, 0x78, 0x04, 0x03, 0x30, 0x60, 0x00, 0x03,
0x00, 0xE0, 0xF1, 0x70, 0x78, 0x0C, 0x04, 0xC0, 0x40, 0x00, 0x03, 0x00,
0xE0, 0xF1, 0x70, 0x78, 0xF8, 0x1D, 0x80, 0x81, 0x00, 0x03, 0x00, 0xE0,
0xF1, 0x70, 0x78, 0x00, 0x3F, 0x00, 0x80, 0x01, 0x03, 0x00, 0xE0, 0xF0,
0x70, 0x78, 0x00, 0x68, 0x00, 0x00, 0x01, 0x03, 0x00, 0xF0, 0xF0, 0x70,
0x78, 0x00, 0xC4, 0x00, 0x00, 0x02, 0x03, 0x00, 0xF0, 0x70, 0x78, 0x78,
0x00, 0x0C, 0x03, 0x00, 0x06, 0x02, 0x00, 0x78, 0x78, 0x78, 0x78, 0x00,
0x18, 0x06, 0x00, 0x04, 0x06, 0x00, 0x30, 0x78, 0x78, 0x38, 0x00, 0x30,
0x0C, 0x00, 0x08, 0x04, 0x00, 0x00, 0x3C, 0x3C, 0x3C, 0x00, 0x60, 0x18,
0x00, 0x18, 0x0C, 0x00, 0x00, 0x3C, 0x3C, 0x3C, 0x00, 0x90, 0x31, 0x00,
0x10, 0x0C, 0x00, 0x00, 0x1C, 0x3C, 0x3C, 0x00, 0x10, 0x3F, 0x00, 0x20,
0x18, 0x00, 0x00, 0x00, 0x1E, 0x1E, 0x00, 0x10, 0x60, 0x00, 0x60, 0x30,
0x00, 0x00, 0x00, 0x1E, 0x1E, 0x00, 0x60, 0x40, 0x00, 0x40, 0x30, 0x00,
0x00, 0x00, 0x0E, 0x1E, 0x00, 0x80, 0xF3, 0x00, 0x80, 0x60, 0x00, 0x00,
0x00, 0x06, 0x0F, 0x00, 0x00, 0x9E, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00,
0x00, 0x0F, 0x00, 0x00, 0x06, 0x01, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80,
0x07, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07,
0x00, 0x80, 0x01, 0x1C, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00,
0xE0, 0x00, 0x30, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00,
0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xE0, 0x07, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00,};
0x00, 0x00, 0x00, 0xE0, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xE0, 0x07, 0x00, 0xC0, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C,
0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00,
0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00,
0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00,
0x30, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0,
0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x80, 0x01,
0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x07, 0x00, 0x00, 0x03, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x06, 0x00, 0x00,
0x60, 0x00, 0x00, 0x00, 0x06, 0x0F, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x20,
0x00, 0x00, 0x00, 0x0E, 0x1E, 0xE0, 0x00, 0x18, 0x00, 0x00, 0x30, 0x00,
0x00, 0x00, 0x1E, 0x1E, 0xB0, 0x3F, 0x10, 0x00, 0x00, 0x18, 0x00, 0x00,
0x00, 0x1E, 0x1E, 0x10, 0xE0, 0x33, 0x00, 0x00, 0x08, 0x00, 0x00, 0x1C,
0x3C, 0x3C, 0x18, 0x00, 0x66, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x3C, 0x3C,
0x3C, 0x08, 0x00, 0x46, 0x00, 0x00, 0x04, 0x00, 0x00, 0x3C, 0x3C, 0x3C,
0x08, 0x06, 0xFE, 0x01, 0x00, 0x06, 0x00, 0x30, 0x78, 0x78, 0x38, 0x88,
0x1F, 0xFE, 0x07, 0x00, 0x02, 0x00, 0x78, 0x78, 0x78, 0x78, 0x88, 0x70,
0x02, 0x0C, 0x00, 0x03, 0x00, 0xF0, 0x78, 0x78, 0x78, 0x8C, 0xC0, 0x03,
0x18, 0x00, 0x03, 0x00, 0xF0, 0xF0, 0x70, 0x78, 0x84, 0x00, 0x07, 0x10,
0x00, 0x03, 0x00, 0xE0, 0xF0, 0x70, 0x78, 0x84, 0x01, 0x0C, 0x20, 0x00,
0x03, 0x00, 0xE0, 0xF1, 0x70, 0x78, 0x04, 0x03, 0x30, 0x60, 0x00, 0x03,
0x00, 0xE0, 0xF1, 0x70, 0x78, 0x0C, 0x04, 0xC0, 0x40, 0x00, 0x03, 0x00,
0xE0, 0xF1, 0x70, 0x78, 0xF8, 0x1D, 0x80, 0x81, 0x00, 0x03, 0x00, 0xE0,
0xF1, 0x70, 0x78, 0x00, 0x3F, 0x00, 0x80, 0x01, 0x03, 0x00, 0xE0, 0xF0,
0x70, 0x78, 0x00, 0x68, 0x00, 0x00, 0x01, 0x03, 0x00, 0xF0, 0xF0, 0x70,
0x78, 0x00, 0xC4, 0x00, 0x00, 0x02, 0x03, 0x00, 0xF0, 0x70, 0x78, 0x78,
0x00, 0x0C, 0x03, 0x00, 0x06, 0x02, 0x00, 0x78, 0x78, 0x78, 0x78, 0x00,
0x18, 0x06, 0x00, 0x04, 0x06, 0x00, 0x30, 0x78, 0x78, 0x38, 0x00, 0x30,
0x0C, 0x00, 0x08, 0x04, 0x00, 0x00, 0x3C, 0x3C, 0x3C, 0x00, 0x60, 0x18,
0x00, 0x18, 0x0C, 0x00, 0x00, 0x3C, 0x3C, 0x3C, 0x00, 0x90, 0x31, 0x00,
0x10, 0x0C, 0x00, 0x00, 0x1C, 0x3C, 0x3C, 0x00, 0x10, 0x3F, 0x00, 0x20,
0x18, 0x00, 0x00, 0x00, 0x1E, 0x1E, 0x00, 0x10, 0x60, 0x00, 0x60, 0x30,
0x00, 0x00, 0x00, 0x1E, 0x1E, 0x00, 0x60, 0x40, 0x00, 0x40, 0x30, 0x00,
0x00, 0x00, 0x0E, 0x1E, 0x00, 0x80, 0xF3, 0x00, 0x80, 0x60, 0x00, 0x00,
0x00, 0x06, 0x0F, 0x00, 0x00, 0x9E, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00,
0x00, 0x0F, 0x00, 0x00, 0x06, 0x01, 0x00, 0x80, 0x01, 0x00, 0x00, 0x80,
0x07, 0x00, 0x00, 0x03, 0x03, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07,
0x00, 0x80, 0x01, 0x1C, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00,
0xE0, 0x00, 0x30, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x00,
0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xE0, 0x07, 0x00, 0x80, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00,};
#define failure_width 40
#define failure_height 40
static unsigned char failure_bits[] = {
0x00, 0x80, 0xFF, 0x01, 0x00, 0x00, 0xF0, 0xFF, 0x0F, 0x00, 0x00, 0xFC,
0xFF, 0x3F, 0x00, 0x00, 0xFE, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0xFF, 0xFF,
0x01, 0xC0, 0xFF, 0xFF, 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0x07, 0xF0,
0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF,
0xFF, 0x1F, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F,
0xFE, 0x7F, 0xFF, 0xFE, 0x7F, 0xFE, 0x3F, 0x7E, 0xFC, 0x7F, 0xFE, 0x1F,
0x3C, 0xF8, 0x7F, 0xFF, 0x0F, 0x18, 0xF0, 0xFF, 0xFF, 0x1F, 0x00, 0xF8,
0xFF, 0xFF, 0x3F, 0x00, 0xFC, 0xFF, 0xFF, 0x7F, 0x00, 0xFE, 0xFF, 0xFF,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x7F, 0x00,
0xFE, 0xFF, 0xFF, 0x3F, 0x00, 0xFC, 0xFF, 0xFF, 0x1F, 0x00, 0xF8, 0xFF,
0xFF, 0x0F, 0x18, 0xF0, 0xFF, 0xFE, 0x1F, 0x3C, 0xF8, 0x7F, 0xFE, 0x3F,
0x7E, 0xFC, 0x7F, 0xFE, 0x7F, 0xFF, 0xFE, 0x7F, 0xFC, 0xFF, 0xFF, 0xFF,
0x3F, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F, 0xF8, 0xFF, 0xFF, 0xFF, 0x1F, 0xF0,
0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x0F, 0xE0, 0xFF, 0xFF,
0xFF, 0x07, 0xC0, 0xFF, 0xFF, 0xFF, 0x03, 0x80, 0xFF, 0xFF, 0xFF, 0x01,
0x00, 0xFE, 0xFF, 0x7F, 0x00, 0x00, 0xFC, 0xFF, 0x3F, 0x00, 0x00, 0xF0,
0xFF, 0x0F, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00,};
0x00, 0x80, 0xFF, 0x01, 0x00, 0x00, 0xF0, 0xFF, 0x0F, 0x00, 0x00, 0xFC,
0xFF, 0x3F, 0x00, 0x00, 0xFE, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0xFF, 0xFF,
0x01, 0xC0, 0xFF, 0xFF, 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0x07, 0xF0,
0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF,
0xFF, 0x1F, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F,
0xFE, 0x7F, 0xFF, 0xFE, 0x7F, 0xFE, 0x3F, 0x7E, 0xFC, 0x7F, 0xFE, 0x1F,
0x3C, 0xF8, 0x7F, 0xFF, 0x0F, 0x18, 0xF0, 0xFF, 0xFF, 0x1F, 0x00, 0xF8,
0xFF, 0xFF, 0x3F, 0x00, 0xFC, 0xFF, 0xFF, 0x7F, 0x00, 0xFE, 0xFF, 0xFF,
0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0x7F, 0x00,
0xFE, 0xFF, 0xFF, 0x3F, 0x00, 0xFC, 0xFF, 0xFF, 0x1F, 0x00, 0xF8, 0xFF,
0xFF, 0x0F, 0x18, 0xF0, 0xFF, 0xFE, 0x1F, 0x3C, 0xF8, 0x7F, 0xFE, 0x3F,
0x7E, 0xFC, 0x7F, 0xFE, 0x7F, 0xFF, 0xFE, 0x7F, 0xFC, 0xFF, 0xFF, 0xFF,
0x3F, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F, 0xF8, 0xFF, 0xFF, 0xFF, 0x1F, 0xF0,
0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x0F, 0xE0, 0xFF, 0xFF,
0xFF, 0x07, 0xC0, 0xFF, 0xFF, 0xFF, 0x03, 0x80, 0xFF, 0xFF, 0xFF, 0x01,
0x00, 0xFE, 0xFF, 0x7F, 0x00, 0x00, 0xFC, 0xFF, 0x3F, 0x00, 0x00, 0xF0,
0xFF, 0x0F, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00,};
#define pending_width 40
#define pending_height 40
static unsigned char pending_bits[] = {
0x00, 0x80, 0xFF, 0x01, 0x00, 0x00, 0xF0, 0xFF, 0x0F, 0x00, 0x00, 0xFC,
0xFF, 0x3F, 0x00, 0x00, 0xFE, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0xFF, 0xFF,
0x01, 0xC0, 0xFF, 0xFF, 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0x07, 0xF0,
0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF,
0xFF, 0x1F, 0xFC, 0xFF, 0xE3, 0xFF, 0x3F, 0xFC, 0xFF, 0xC3, 0xFF, 0x3F,
0xFE, 0xFF, 0xC3, 0xFF, 0x7F, 0xFE, 0xFF, 0xC3, 0xFF, 0x7F, 0xFE, 0xFF,
0xC3, 0xFF, 0x7F, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF,
0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFF,
0xFF, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0x03,
0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFC, 0xFF, 0xFF, 0xFF, 0x03, 0xF0, 0xFF,
0xFF, 0xFF, 0x0F, 0xE0, 0xFF, 0xFE, 0xFF, 0x3F, 0xE0, 0x7F, 0xFE, 0xFF,
0x7F, 0xF0, 0x7F, 0xFE, 0xFF, 0xFF, 0xF1, 0x7F, 0xFC, 0xFF, 0xFF, 0xFF,
0x3F, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F, 0xF8, 0xFF, 0xFF, 0xFF, 0x1F, 0xF0,
0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x0F, 0xE0, 0xFF, 0xFF,
0xFF, 0x07, 0xC0, 0xFF, 0xFF, 0xFF, 0x03, 0x80, 0xFF, 0xFF, 0xFF, 0x01,
0x00, 0xFE, 0xFF, 0x7F, 0x00, 0x00, 0xFC, 0xFF, 0x3F, 0x00, 0x00, 0xF0,
0xFF, 0x0F, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00,};
0x00, 0x80, 0xFF, 0x01, 0x00, 0x00, 0xF0, 0xFF, 0x0F, 0x00, 0x00, 0xFC,
0xFF, 0x3F, 0x00, 0x00, 0xFE, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0xFF, 0xFF,
0x01, 0xC0, 0xFF, 0xFF, 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0x07, 0xF0,
0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF,
0xFF, 0x1F, 0xFC, 0xFF, 0xE3, 0xFF, 0x3F, 0xFC, 0xFF, 0xC3, 0xFF, 0x3F,
0xFE, 0xFF, 0xC3, 0xFF, 0x7F, 0xFE, 0xFF, 0xC3, 0xFF, 0x7F, 0xFE, 0xFF,
0xC3, 0xFF, 0x7F, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF,
0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFF,
0xFF, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0x03,
0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xFC, 0xFF, 0xFF, 0xFF, 0x03, 0xF0, 0xFF,
0xFF, 0xFF, 0x0F, 0xE0, 0xFF, 0xFE, 0xFF, 0x3F, 0xE0, 0x7F, 0xFE, 0xFF,
0x7F, 0xF0, 0x7F, 0xFE, 0xFF, 0xFF, 0xF1, 0x7F, 0xFC, 0xFF, 0xFF, 0xFF,
0x3F, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F, 0xF8, 0xFF, 0xFF, 0xFF, 0x1F, 0xF0,
0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x0F, 0xE0, 0xFF, 0xFF,
0xFF, 0x07, 0xC0, 0xFF, 0xFF, 0xFF, 0x03, 0x80, 0xFF, 0xFF, 0xFF, 0x01,
0x00, 0xFE, 0xFF, 0x7F, 0x00, 0x00, 0xFC, 0xFF, 0x3F, 0x00, 0x00, 0xF0,
0xFF, 0x0F, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00,};
#define success_width 40
#define success_height 40
static unsigned char success_bits[] = {
0x00, 0x80, 0xFF, 0x01, 0x00, 0x00, 0xF0, 0xFF, 0x0F, 0x00, 0x00, 0xFC,
0xFF, 0x3F, 0x00, 0x00, 0xFE, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0xFF, 0xFF,
0x01, 0xC0, 0xFF, 0xFF, 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0x07, 0xF0,
0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF,
0xFF, 0x1F, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F,
0xFE, 0xFF, 0xFF, 0xFF, 0x7F, 0xFE, 0xFF, 0xFF, 0xF3, 0x7F, 0xFE, 0xFF,
0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0x7F, 0xE0, 0xFF, 0xFF, 0xFF, 0x3F, 0xE0,
0xFF, 0xFF, 0xFF, 0x3F, 0xF0, 0xFF, 0xFF, 0xFF, 0x1F, 0xF8, 0xFF, 0xFF,
0x8F, 0x0F, 0xFC, 0xFF, 0xFF, 0x07, 0x07, 0xFE, 0xFF, 0xFF, 0x07, 0x00,
0xFF, 0xFF, 0xFF, 0x0F, 0x80, 0xFF, 0xFF, 0xFF, 0x1F, 0xC0, 0xFF, 0xFF,
0xFF, 0x3F, 0xE0, 0xFF, 0xFF, 0xFE, 0x7F, 0xF0, 0xFF, 0x7F, 0xFE, 0xFF,
0xF8, 0xFF, 0x7F, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F, 0xFC, 0xFF, 0xFF, 0xFF,
0x3F, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F, 0xF8, 0xFF, 0xFF, 0xFF, 0x1F, 0xF0,
0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x0F, 0xE0, 0xFF, 0xFF,
0xFF, 0x07, 0xC0, 0xFF, 0xFF, 0xFF, 0x03, 0x80, 0xFF, 0xFF, 0xFF, 0x01,
0x00, 0xFE, 0xFF, 0x7F, 0x00, 0x00, 0xFC, 0xFF, 0x3F, 0x00, 0x00, 0xF0,
0xFF, 0x0F, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00,};
0x00, 0x80, 0xFF, 0x01, 0x00, 0x00, 0xF0, 0xFF, 0x0F, 0x00, 0x00, 0xFC,
0xFF, 0x3F, 0x00, 0x00, 0xFE, 0xFF, 0x7F, 0x00, 0x80, 0xFF, 0xFF, 0xFF,
0x01, 0xC0, 0xFF, 0xFF, 0xFF, 0x03, 0xE0, 0xFF, 0xFF, 0xFF, 0x07, 0xF0,
0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x0F, 0xF8, 0xFF, 0xFF,
0xFF, 0x1F, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F,
0xFE, 0xFF, 0xFF, 0xFF, 0x7F, 0xFE, 0xFF, 0xFF, 0xF3, 0x7F, 0xFE, 0xFF,
0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0x7F, 0xE0, 0xFF, 0xFF, 0xFF, 0x3F, 0xE0,
0xFF, 0xFF, 0xFF, 0x3F, 0xF0, 0xFF, 0xFF, 0xFF, 0x1F, 0xF8, 0xFF, 0xFF,
0x8F, 0x0F, 0xFC, 0xFF, 0xFF, 0x07, 0x07, 0xFE, 0xFF, 0xFF, 0x07, 0x00,
0xFF, 0xFF, 0xFF, 0x0F, 0x80, 0xFF, 0xFF, 0xFF, 0x1F, 0xC0, 0xFF, 0xFF,
0xFF, 0x3F, 0xE0, 0xFF, 0xFF, 0xFE, 0x7F, 0xF0, 0xFF, 0x7F, 0xFE, 0xFF,
0xF8, 0xFF, 0x7F, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F, 0xFC, 0xFF, 0xFF, 0xFF,
0x3F, 0xFC, 0xFF, 0xFF, 0xFF, 0x3F, 0xF8, 0xFF, 0xFF, 0xFF, 0x1F, 0xF0,
0xFF, 0xFF, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFF, 0x0F, 0xE0, 0xFF, 0xFF,
0xFF, 0x07, 0xC0, 0xFF, 0xFF, 0xFF, 0x03, 0x80, 0xFF, 0xFF, 0xFF, 0x01,
0x00, 0xFE, 0xFF, 0x7F, 0x00, 0x00, 0xFC, 0xFF, 0x3F, 0x00, 0x00, 0xF0,
0xFF, 0x0F, 0x00, 0x00, 0x80, 0xFF, 0x01, 0x00,};
#define matecard_width 65
#define matecard_height 40
static unsigned char matecard_bits[] = {
0x00, 0x80, 0xBE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0xEB,
0x0F, 0x00, 0x55, 0x05, 0x00, 0x00, 0x00, 0xB8, 0x5E, 0x1B, 0x20, 0x88,
0x10, 0x00, 0x00, 0x00, 0xEE, 0xF7, 0xF6, 0x90, 0x22, 0x44, 0x00, 0x00,
0x00, 0xBB, 0xDA, 0xAF, 0x4A, 0x94, 0x12, 0x01, 0x00, 0x80, 0xDF, 0xFF,
0xFA, 0x12, 0x42, 0x44, 0x00, 0x00, 0xE0, 0x7A, 0x55, 0x57, 0x45, 0x09,
0x91, 0x02, 0x00, 0xA0, 0xAF, 0xFF, 0xBD, 0x2A, 0xA2, 0x24, 0x08, 0x00,
0xF0, 0xFA, 0xAA, 0x57, 0x95, 0x08, 0x92, 0x02, 0x00, 0x78, 0x5F, 0xFF,
0xAD, 0x2A, 0xA4, 0x44, 0x14, 0x00, 0xA8, 0xF5, 0x55, 0x57, 0x95, 0x12,
0x11, 0x21, 0x00, 0xFC, 0xBF, 0xFE, 0xAB, 0xAA, 0x40, 0x88, 0x08, 0x00,
0x54, 0xD5, 0xAB, 0x56, 0x55, 0x8A, 0x22, 0x25, 0x00, 0xFE, 0xFF, 0xFD,
0xAB, 0x2A, 0x51, 0x48, 0x48, 0x00, 0xAA, 0xAA, 0x56, 0xD5, 0x56, 0x82,
0x12, 0x21, 0x00, 0xFE, 0xFF, 0xFF, 0xAB, 0xAA, 0x54, 0x44, 0x8A, 0x00,
0x55, 0x55, 0x55, 0x55, 0xB5, 0x80, 0x88, 0x10, 0x00, 0xFE, 0xFF, 0xFF,
0xAD, 0xAA, 0x2A, 0x52, 0xA4, 0x00, 0xAB, 0xAA, 0xAA, 0x55, 0x55, 0x44,
0x81, 0x12, 0x01, 0xFE, 0xFF, 0x7F, 0xAB, 0xAA, 0x22, 0x28, 0x44, 0x00,
0xAB, 0xAA, 0xEA, 0x6A, 0x55, 0x49, 0x45, 0x11, 0x01, 0xFD, 0xFF, 0xBF,
0xAB, 0xAA, 0x10, 0x20, 0x44, 0x00, 0x57, 0x55, 0xF5, 0xAA, 0x6D, 0xA5,
0x8A, 0x28, 0x01, 0xFA, 0xFF, 0xAF, 0x55, 0x95, 0x08, 0x21, 0x05, 0x00,
0xAE, 0xAA, 0x7E, 0xAB, 0x6A, 0x22, 0x4A, 0xA8, 0x00, 0xF6, 0xFF, 0xD5,
0x56, 0x55, 0x91, 0x10, 0x05, 0x00, 0x5A, 0x55, 0xFF, 0x55, 0x55, 0x44,
0x22, 0xA8, 0x00, 0xFC, 0xFF, 0x57, 0xAF, 0x2A, 0x29, 0x49, 0x05, 0x00,
0x54, 0x55, 0xFD, 0x6A, 0x55, 0x84, 0x04, 0x28, 0x00, 0xF8, 0xFF, 0x57,
0xAF, 0xAA, 0x22, 0x51, 0x05, 0x00, 0xA8, 0xAA, 0xFE, 0x55, 0x15, 0x88,
0x84, 0x10, 0x00, 0xF0, 0xFF, 0xAB, 0xBE, 0x96, 0x52, 0x28, 0x0A, 0x00,
0xA0, 0xAA, 0xFE, 0xAB, 0x4A, 0x04, 0x41, 0x00, 0x00, 0xC0, 0xFF, 0x57,
0xDD, 0x12, 0xA2, 0x94, 0x0A, 0x00, 0x80, 0xAA, 0xFE, 0x77, 0x45, 0x09,
0x21, 0x00, 0x00, 0x00, 0xFF, 0xAB, 0xBA, 0x21, 0x50, 0x8A, 0x02, 0x00,
0x00, 0xAA, 0xFE, 0x6F, 0x94, 0x82, 0x10, 0x00, 0x00, 0x00, 0xF8, 0xAB,
0x1A, 0x80, 0x28, 0x4A, 0x00, 0x00, 0x00, 0x40, 0xFF, 0x0B, 0x20, 0xA2,
0x00, 0x00, 0x00, 0x00, 0x80, 0xAA, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
0x00, 0x80, 0xBE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0xEB,
0x0F, 0x00, 0x55, 0x05, 0x00, 0x00, 0x00, 0xB8, 0x5E, 0x1B, 0x20, 0x88,
0x10, 0x00, 0x00, 0x00, 0xEE, 0xF7, 0xF6, 0x90, 0x22, 0x44, 0x00, 0x00,
0x00, 0xBB, 0xDA, 0xAF, 0x4A, 0x94, 0x12, 0x01, 0x00, 0x80, 0xDF, 0xFF,
0xFA, 0x12, 0x42, 0x44, 0x00, 0x00, 0xE0, 0x7A, 0x55, 0x57, 0x45, 0x09,
0x91, 0x02, 0x00, 0xA0, 0xAF, 0xFF, 0xBD, 0x2A, 0xA2, 0x24, 0x08, 0x00,
0xF0, 0xFA, 0xAA, 0x57, 0x95, 0x08, 0x92, 0x02, 0x00, 0x78, 0x5F, 0xFF,
0xAD, 0x2A, 0xA4, 0x44, 0x14, 0x00, 0xA8, 0xF5, 0x55, 0x57, 0x95, 0x12,
0x11, 0x21, 0x00, 0xFC, 0xBF, 0xFE, 0xAB, 0xAA, 0x40, 0x88, 0x08, 0x00,
0x54, 0xD5, 0xAB, 0x56, 0x55, 0x8A, 0x22, 0x25, 0x00, 0xFE, 0xFF, 0xFD,
0xAB, 0x2A, 0x51, 0x48, 0x48, 0x00, 0xAA, 0xAA, 0x56, 0xD5, 0x56, 0x82,
0x12, 0x21, 0x00, 0xFE, 0xFF, 0xFF, 0xAB, 0xAA, 0x54, 0x44, 0x8A, 0x00,
0x55, 0x55, 0x55, 0x55, 0xB5, 0x80, 0x88, 0x10, 0x00, 0xFE, 0xFF, 0xFF,
0xAD, 0xAA, 0x2A, 0x52, 0xA4, 0x00, 0xAB, 0xAA, 0xAA, 0x55, 0x55, 0x44,
0x81, 0x12, 0x01, 0xFE, 0xFF, 0x7F, 0xAB, 0xAA, 0x22, 0x28, 0x44, 0x00,
0xAB, 0xAA, 0xEA, 0x6A, 0x55, 0x49, 0x45, 0x11, 0x01, 0xFD, 0xFF, 0xBF,
0xAB, 0xAA, 0x10, 0x20, 0x44, 0x00, 0x57, 0x55, 0xF5, 0xAA, 0x6D, 0xA5,
0x8A, 0x28, 0x01, 0xFA, 0xFF, 0xAF, 0x55, 0x95, 0x08, 0x21, 0x05, 0x00,
0xAE, 0xAA, 0x7E, 0xAB, 0x6A, 0x22, 0x4A, 0xA8, 0x00, 0xF6, 0xFF, 0xD5,
0x56, 0x55, 0x91, 0x10, 0x05, 0x00, 0x5A, 0x55, 0xFF, 0x55, 0x55, 0x44,
0x22, 0xA8, 0x00, 0xFC, 0xFF, 0x57, 0xAF, 0x2A, 0x29, 0x49, 0x05, 0x00,
0x54, 0x55, 0xFD, 0x6A, 0x55, 0x84, 0x04, 0x28, 0x00, 0xF8, 0xFF, 0x57,
0xAF, 0xAA, 0x22, 0x51, 0x05, 0x00, 0xA8, 0xAA, 0xFE, 0x55, 0x15, 0x88,
0x84, 0x10, 0x00, 0xF0, 0xFF, 0xAB, 0xBE, 0x96, 0x52, 0x28, 0x0A, 0x00,
0xA0, 0xAA, 0xFE, 0xAB, 0x4A, 0x04, 0x41, 0x00, 0x00, 0xC0, 0xFF, 0x57,
0xDD, 0x12, 0xA2, 0x94, 0x0A, 0x00, 0x80, 0xAA, 0xFE, 0x77, 0x45, 0x09,
0x21, 0x00, 0x00, 0x00, 0xFF, 0xAB, 0xBA, 0x21, 0x50, 0x8A, 0x02, 0x00,
0x00, 0xAA, 0xFE, 0x6F, 0x94, 0x82, 0x10, 0x00, 0x00, 0x00, 0xF8, 0xAB,
0x1A, 0x80, 0x28, 0x4A, 0x00, 0x00, 0x00, 0x40, 0xFF, 0x0B, 0x20, 0xA2,
0x00, 0x00, 0x00, 0x00, 0x80, 0xAA, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
};
#endif

View file

@ -1,15 +1,11 @@
#pragma once
#include "Arduino.h"
#include <U8g2lib.h>
#include "logos.h"
#include "utils.h"
#ifndef AFRAPAY_OLED_H
#define AFRAPAY_OLED_H
void drawLogo(U8G2 u8g2, e_logo logo);
void drawStatusText(U8G2 u8g2, String status, String statusRightAligned = "");
void updateOLED(U8G2 u8g2, e_state state, String statusText, String statusTextRightAligned = "");
void updateOLED(U8G2 u8g2, e_state state);
#endif

View file

@ -1,5 +1,4 @@
#ifndef AFRAPAY_PITCHES_H
#define AFRAPAY_PITCHES_H
#pragma once
#define NOTE_NONE 0
#define NOTE_B0 31
@ -91,5 +90,3 @@
#define NOTE_CS8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978
#endif

View file

@ -1,26 +1,24 @@
#pragma once
#include <Arduino.h>
#include <HTTPClient.h>
#ifndef AFRAPAY_UTILS_H
#define AFRAPAY_UTILS_H
unsigned long cooldownSecondsRemaining(unsigned long timeout, unsigned long timer);
String byteArrayAsHexString(byte *buffer, byte bufferSize);
String uint32AsHexString(uint32_t input);
String cardLink(WiFiClient* wifi, HTTPClient* http, String apiUrl, String cardId);
String cardBalance(WiFiClient* wifi, HTTPClient* http, String apiUrl, String cardId);
String cardTransaction(WiFiClient* wifi, HTTPClient* http, String apiUrl, String cardId, String amount);
String splitString(String data, char separator, int index);
enum e_state {
STATE_IDLE,
STATE_TRANSACT_CARDSCAN,
STATE_TRANSACT_VERIFY,
STATE_LINK_CARDSCAN,
STATE_LINK_VERIFY,
STATE_BALANCE_CARDSCAN,
STATE_BALANCE_VERIFY,
STATE_RESULT_SUCCESS,
STATE_RESULT_FAILURE,
STATE_RESULT_DISPLAY,
STATE_IDLE,
STATE_TRANSACT_CARDSCAN,
STATE_TRANSACT_VERIFY,
STATE_LINK_CARDSCAN,
STATE_LINK_VERIFY,
STATE_BALANCE_CARDSCAN,
STATE_BALANCE_VERIFY,
STATE_RESULT_SUCCESS,
STATE_RESULT_FAILURE,
STATE_RESULT_DISPLAY,
};
#endif

View file

@ -1,9 +1,6 @@
#ifndef AFRAPAY_WIFIFIX_H
#define AFRAPAY_WIFIFIX_H
#pragma once
class WiFiClientFixed : public WiFiClient {
public:
void flush() override;
void flush() override;
};
#endif

View file

@ -20,3 +20,7 @@ lib_deps =
miguelbalboa/MFRC522@^1.4.10
arduino-libraries/NTPClient@^3.2.1
mcxiaoke/ESPDateTime@^1.0.4
arduino12/rdm6300@^2.0.0
check_tool = clangtidy
check_flags =
clangtidy: --checks=*,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-modernize-macro-to-enum

View file

@ -0,0 +1,32 @@
#include <Arduino.h>
#include <MFRC522.h>
#include <rdm6300.h>
#include "utils.h"
#include "cardReader.h"
MFRC522CardReader::MFRC522CardReader(MFRC522 *reader) {
iReader = reader;
}
bool MFRC522CardReader::isNewCardPresent() {
return iReader->PICC_IsNewCardPresent() && iReader->PICC_ReadCardSerial();
}
String MFRC522CardReader::getCardUid() {
return byteArrayAsHexString(iReader->uid.uidByte, iReader->uid.size);
}
RDM6300CardReader::RDM6300CardReader(Rdm6300 *reader) {
iReader = reader;
}
bool RDM6300CardReader::isNewCardPresent() {
return iReader->get_new_tag_id();
}
String RDM6300CardReader::getCardUid() {
auto uid = iReader->get_tag_id();
char buf[16];
sprintf(buf, "%010u", uid);
return {buf};
}

View file

@ -9,24 +9,44 @@
#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"
#pragma clang diagnostic push
#pragma ide diagnostic ignored "OCUnusedMacroInspection"
//MFRC522 pinout (front) 3V3 RST GND IRQ MISO MOSI SCK SDA
//MFRC522 pinout (rear) SDA SCK MOSI MISO IRQ GND RST 3V3
#define HSPI_RST 4
#define HSPI_MISO 12
#define HSPI_MOSI 13
#define HSPI_SCLK 14
#define HSPI_SS 15
#define HSPI_RST 16
#define BUZZER_PIN 26
#define PIN_HWSERIAL_RX 16
#define PIN_HWSERIAL_TX 17
#define PIN_BUZZER 19
#define PIN_OLED_SDA 21
#define PIN_OLED_SCL 22
#define PIN_INTERRUPT_TRANSACT 26
#define PIN_INTERRUPT_BALANCE 25
#define PIN_INTERRUPT_LINK 33
#define PIN_INTERRUPT_CANCEL 32
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
MFRC522 mfrc522(HSPI_SS, 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 = "";
@ -36,179 +56,190 @@ const int scanTimeout = 15000;
String scannedCardId = "";
String lastStatusText = "";
bool interruptFired = false;
unsigned long timer = 0;
void IRAM_ATTR TransactInterruptHandler() {
if (state != STATE_IDLE) {
return;
}
timer = millis();
state = STATE_TRANSACT_CARDSCAN;
if (interruptFired || state != STATE_IDLE) {
return;
}
interruptFired = true;
state = STATE_TRANSACT_CARDSCAN;
}
void IRAM_ATTR LinkInterruptHandler() {
if (state != STATE_IDLE) {
return;
}
timer = millis();
state = STATE_LINK_CARDSCAN;
if (interruptFired || state != STATE_IDLE) {
return;
}
interruptFired = true;
state = STATE_LINK_CARDSCAN;
}
void IRAM_ATTR BalanceInterruptHandler() {
if (state != STATE_IDLE) {
return;
}
timer = millis();
state = STATE_BALANCE_CARDSCAN;
if (interruptFired || state != STATE_IDLE) {
return;
}
interruptFired = true;
state = STATE_BALANCE_CARDSCAN;
}
bool cooldownCheck(long timeout) {
if (millis() - timer > timeout) {
state = STATE_IDLE;
updateOLED(u8g2, state);
return true;
}
if (millis() - timer > timeout) {
state = STATE_IDLE;
updateOLED(u8g2, state);
return true;
}
return false;
return false;
}
void setup() {
Serial.begin(115200);
u8g2.begin();
drawLogo(u8g2, LOGO_MATECARD);
u8g2.sendBuffer();
SPI.begin(HSPI_SCLK, HSPI_MISO, HSPI_MOSI);
SPIFFS.begin(true);
WiFiSettings.hostname = "afrapay-";
apiUrl = WiFiSettings.string("AfRApay.Web API", "http://192.168.50.170:5296");
WiFiSettings.connect();
mfrc522.PCD_Init();
DateTime.setTimeZone(TZ_Europe_Berlin);
DateTime.begin();
updateOLED(u8g2, state);
Serial.begin(115200);
Serial2.begin(115200, SERIAL_8N1, PIN_HWSERIAL_RX, PIN_HWSERIAL_TX);
u8g2.begin();
drawLogo(u8g2, LOGO_MATECARD);
u8g2.sendBuffer();
SPI.begin(HSPI_SCLK, HSPI_MISO, 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(25, INPUT_PULLUP);
pinMode(32, INPUT_PULLUP);
pinMode(33, INPUT_PULLUP);
attachInterrupt(25, BalanceInterruptHandler, FALLING);
attachInterrupt(32, LinkInterruptHandler, FALLING);
attachInterrupt(33, TransactInterruptHandler, FALLING);
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_LINK, LinkInterruptHandler, FALLING);
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
updateOLED(u8g2, state, "WiFi disconnected :(");
state = STATE_IDLE;
WiFi.reconnect();
delay(50);
return;
}
if (WiFi.status() != WL_CONNECTED) {
updateOLED(u8g2, state, "WiFi disconnected :(");
state = STATE_IDLE;
WiFi.reconnect();
delay(50);
return;
}
switch (state) {
case STATE_IDLE:
updateOLED(u8g2, state);
break;
case STATE_TRANSACT_CARDSCAN:
if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
scannedCardId = byteArrayAsHexString(mfrc522.uid.uidByte, mfrc522.uid.size);
mfrc522.PICC_HaltA();
lastStatusText = "Card #" + scannedCardId;
state = STATE_TRANSACT_VERIFY;
return;
} else {
if (!cooldownCheck(scanTimeout)) {
updateOLED(u8g2, state, String("1.50€"), String(cooldownSecondsRemaining(scanTimeout, timer)));
}
}
break;
case STATE_TRANSACT_VERIFY:
updateOLED(u8g2, state, lastStatusText);
tone(BUZZER_PIN, NOTE_A5, 25);
tone(BUZZER_PIN, NOTE_NONE, 150);
lastStatusText = cardTransaction(wifi, http, apiUrl, scannedCardId, "1.50");
if (lastStatusText.startsWith("S:")) {
tone(BUZZER_PIN, NOTE_C7, 650);
lastStatusText += "";
state = STATE_RESULT_SUCCESS;
} else {
tone(BUZZER_PIN, NOTE_CS5, 100);
tone(BUZZER_PIN, NOTE_NONE, 25);
tone(BUZZER_PIN, NOTE_CS5, 400);
state = STATE_RESULT_FAILURE;
}
lastStatusText = lastStatusText.substring(2);
timer = millis();
return;
case STATE_LINK_CARDSCAN:
if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
scannedCardId = byteArrayAsHexString(mfrc522.uid.uidByte, mfrc522.uid.size);
mfrc522.PICC_HaltA();
lastStatusText = "Card #" + scannedCardId;
state = STATE_LINK_VERIFY;
return;
} else {
if (!cooldownCheck(scanTimeout)) {
updateOLED(u8g2, state, "Link", String(cooldownSecondsRemaining(scanTimeout, timer)));
}
}
break;
case STATE_LINK_VERIFY:
updateOLED(u8g2, state, lastStatusText);
tone(BUZZER_PIN, NOTE_A5, 25);
tone(BUZZER_PIN, NOTE_NONE, 150);
lastStatusText = cardLink(wifi, http, apiUrl, scannedCardId);
if (lastStatusText.startsWith("S:")) {
tone(BUZZER_PIN, NOTE_C6, 100);
tone(BUZZER_PIN, NOTE_NONE, 10);
tone(BUZZER_PIN, NOTE_C6, 100);
state = STATE_RESULT_SUCCESS;
} else {
tone(BUZZER_PIN, NOTE_CS5, 100);
tone(BUZZER_PIN, NOTE_NONE, 25);
tone(BUZZER_PIN, NOTE_CS5, 400);
state = STATE_RESULT_FAILURE;
}
lastStatusText = lastStatusText.substring(2);
timer = millis();
return;
case STATE_BALANCE_CARDSCAN:
if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
scannedCardId = byteArrayAsHexString(mfrc522.uid.uidByte, mfrc522.uid.size);
mfrc522.PICC_HaltA();
lastStatusText = "Card #" + scannedCardId;
state = STATE_BALANCE_VERIFY;
return;
} else {
if (!cooldownCheck(scanTimeout)) {
updateOLED(u8g2, state, "Balance", String(cooldownSecondsRemaining(scanTimeout, timer)));
}
}
break;
case STATE_BALANCE_VERIFY:
updateOLED(u8g2, state, lastStatusText);
tone(BUZZER_PIN, NOTE_A5, 25);
tone(BUZZER_PIN, NOTE_NONE, 150);
lastStatusText = cardBalance(wifi, http, apiUrl, scannedCardId);
if (lastStatusText.startsWith("S:")) {
lastStatusText = lastStatusText + ":Card #" + scannedCardId;
state = STATE_RESULT_DISPLAY;
} else {
tone(BUZZER_PIN, NOTE_CS5, 100);
tone(BUZZER_PIN, NOTE_NONE, 25);
tone(BUZZER_PIN, NOTE_CS5, 400);
state = STATE_RESULT_FAILURE;
}
lastStatusText = lastStatusText.substring(2);
timer = millis();
case STATE_RESULT_SUCCESS:
case STATE_RESULT_FAILURE:
case STATE_RESULT_DISPLAY:
if (!cooldownCheck(gTimeout)) {
updateOLED(u8g2, state, lastStatusText, String(cooldownSecondsRemaining(gTimeout, timer)));
}
break;
}
if (interruptFired) {
timer = millis();
interruptFired = false;
}
delay(50);
}
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, "1.50");
if (lastStatusText.startsWith("S:")) {
tone(PIN_BUZZER, NOTE_C7, 650);
lastStatusText += "";
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:
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

View file

@ -6,125 +6,125 @@
#include "utils.h"
void drawCurvedLineV(U8G2 u8g2, int xStart, int yStart, int vert, int horiz) {
u8g2.drawVLine(xStart, yStart + 1, vert);
u8g2.drawVLine(xStart + 1, yStart, vert);
u8g2.drawVLine(xStart, yStart + 1, vert);
u8g2.drawVLine(xStart + 1, yStart, vert);
u8g2.drawHLine(xStart, yStart + vert, horiz);
u8g2.drawHLine(xStart + 1, yStart + vert + 1, horiz);
u8g2.drawHLine(xStart, yStart + vert, horiz);
u8g2.drawHLine(xStart + 1, yStart + vert + 1, horiz);
}
void drawCurvedLine(U8G2 u8g2, int xStart, int yStart, int horiz, int vert) {
u8g2.drawHLine(xStart, yStart, horiz);
u8g2.drawHLine(xStart, yStart + 1, horiz);
u8g2.drawHLine(xStart, yStart, horiz);
u8g2.drawHLine(xStart, yStart + 1, horiz);
u8g2.drawVLine(xStart + horiz, yStart, vert);
u8g2.drawVLine(xStart + horiz + 1, yStart + 1, vert);
u8g2.drawVLine(xStart + horiz, yStart, vert);
u8g2.drawVLine(xStart + horiz + 1, yStart + 1, vert);
}
void drawLogoAfraPay(U8G2 u8g2) {
u8g2.setFontMode(1);
u8g2.setFontDirection(0);
u8g2.setFontMode(1);
u8g2.setFontDirection(0);
int xOffset = 12;
int yOffset = 23;
int xOffset = 12;
int yOffset = 23;
u8g2.setFont(u8g2_font_maniac_tr);
u8g2.drawStr(xOffset, yOffset, "AfRA");
u8g2.setFont(u8g2_font_maniac_tr);
u8g2.drawStr(xOffset, yOffset, "AfRA");
u8g2.setFont(u8g2_font_tenthinguys_tr);
u8g2.drawStr(xOffset + 73, yOffset + 3, "pay");
u8g2.setFont(u8g2_font_tenthinguys_tr);
u8g2.drawStr(xOffset + 73, yOffset + 3, "pay");
// uncomment to verify distance to screen gap is equal on both sides
//u8g2.drawHLine(0, yOffset-12, xOffset);
//u8g2.drawHLine(127-xOffset-1, yOffset, xOffset);
// uncomment to verify distance to screen gap is equal on both sides
//u8g2.drawHLine(0, yOffset-12, xOffset);
//u8g2.drawHLine(127-xOffset-1, yOffset, xOffset);
drawCurvedLineV(u8g2, xOffset + 69, yOffset, 7, 32);
drawCurvedLine(u8g2, xOffset + 73, yOffset - 8, 28, 15);
u8g2.drawPixel(xOffset + 101, yOffset + 7);
drawCurvedLineV(u8g2, xOffset + 69, yOffset, 7, 32);
drawCurvedLine(u8g2, xOffset + 73, yOffset - 8, 28, 15);
u8g2.drawPixel(xOffset + 101, yOffset + 7);
}
void drawLogoBitmap(U8G2 u8g2, int width, int height, unsigned char bits[], int yOffset = 0) {
int offset = (128 - width) / 2;
u8g2.drawXBM(offset, yOffset, width, height, bits);
int offset = (128 - width) / 2;
u8g2.drawXBM(offset, yOffset, width, height, bits);
}
void drawLogo(U8G2 u8g2, e_logo logo) {
switch (logo) {
case LOGO_AFRAPAY:
drawLogoAfraPay(u8g2);
break;
case LOGO_CONTACTLESS:
drawLogoBitmap(u8g2, contactless_width, contactless_height, contactless_bits);
break;
case LOGO_MATECARD:
drawLogoBitmap(u8g2, matecard_width, matecard_height, matecard_bits, 5);
u8g2.setFont(u8g2_font_bpixeldouble_tr);
u8g2.drawStr((126 - u8g2.getStrWidth("matecard")) / 2, 57, "matecard");
break;
case LOGO_PENDING:
drawLogoBitmap(u8g2, pending_width, pending_height, pending_bits, 5);
break;
case LOGO_SUCCESS:
drawLogoBitmap(u8g2, success_width, success_height, success_bits, 5);
break;
case LOGO_FAILURE:
drawLogoBitmap(u8g2, failure_width, failure_height, failure_bits, 5);
break;
}
switch (logo) {
case LOGO_AFRAPAY:
drawLogoAfraPay(u8g2);
break;
case LOGO_CONTACTLESS:
drawLogoBitmap(u8g2, contactless_width, contactless_height, contactless_bits);
break;
case LOGO_MATECARD:
drawLogoBitmap(u8g2, matecard_width, matecard_height, matecard_bits, 5);
u8g2.setFont(u8g2_font_bpixeldouble_tr);
u8g2.drawStr((126 - u8g2.getStrWidth("matecard")) / 2, 57, "matecard");
break;
case LOGO_PENDING:
drawLogoBitmap(u8g2, pending_width, pending_height, pending_bits, 5);
break;
case LOGO_SUCCESS:
drawLogoBitmap(u8g2, success_width, success_height, success_bits, 5);
break;
case LOGO_FAILURE:
drawLogoBitmap(u8g2, failure_width, failure_height, failure_bits, 5);
break;
}
}
void drawStatusText(U8G2 u8g2, String status, String statusRightAligned = "") {
u8g2.setFont(u8g2_font_bpixel_te);
u8g2.drawUTF8(0, 61, status.c_str());
u8g2.setFont(u8g2_font_bpixel_te);
u8g2.drawUTF8(0, 61, status.c_str());
if (!statusRightAligned.isEmpty()) {
int textWidth = u8g2.getUTF8Width(statusRightAligned.c_str());
u8g2.drawUTF8(126 - textWidth, 61, statusRightAligned.c_str());
}
if (!statusRightAligned.isEmpty()) {
int textWidth = u8g2.getUTF8Width(statusRightAligned.c_str());
u8g2.drawUTF8(126 - textWidth, 61, statusRightAligned.c_str());
}
}
void drawFullScreenText(U8G2 u8g2, String textTop, String textBottom) {
u8g2.setFont(u8g2_font_chargen_92_tr);
u8g2.drawUTF8((127-u8g2.getUTF8Width(textTop.c_str()))/2, 13, textTop.c_str());
u8g2.setFont(u8g2_font_maniac_te);
u8g2.drawUTF8((127-u8g2.getUTF8Width(textBottom.c_str()))/2, 45, textBottom.c_str());
u8g2.setFont(u8g2_font_chargen_92_tr);
u8g2.drawUTF8((127-u8g2.getUTF8Width(textTop.c_str()))/2, 13, textTop.c_str());
u8g2.setFont(u8g2_font_maniac_te);
u8g2.drawUTF8((127-u8g2.getUTF8Width(textBottom.c_str()))/2, 45, textBottom.c_str());
}
void updateOLED(U8G2 u8g2, e_state state, String statusText, String statusTextRightAligned = "") {
u8g2.clearBuffer();
u8g2.clearBuffer();
switch (state) {
case STATE_IDLE:
drawLogo(u8g2, LOGO_AFRAPAY);
break;
case STATE_TRANSACT_CARDSCAN:
case STATE_BALANCE_CARDSCAN:
case STATE_LINK_CARDSCAN:
drawLogo(u8g2, LOGO_CONTACTLESS);
break;
case STATE_RESULT_SUCCESS:
drawLogo(u8g2, LOGO_SUCCESS);
break;
case STATE_RESULT_FAILURE:
drawLogo(u8g2, LOGO_FAILURE);
break;
case STATE_RESULT_DISPLAY:
drawFullScreenText(u8g2, splitString(statusText, ':', 0), splitString(statusText, ':', 1) + "");
drawStatusText(u8g2, splitString(statusText, ':', 2), statusTextRightAligned);
u8g2.sendBuffer();
return;
case STATE_TRANSACT_VERIFY:
case STATE_BALANCE_VERIFY:
case STATE_LINK_VERIFY:
drawLogo(u8g2, LOGO_PENDING);
break;
}
switch (state) {
case STATE_IDLE:
drawLogo(u8g2, LOGO_AFRAPAY);
break;
case STATE_TRANSACT_CARDSCAN:
case STATE_BALANCE_CARDSCAN:
case STATE_LINK_CARDSCAN:
drawLogo(u8g2, LOGO_CONTACTLESS);
break;
case STATE_RESULT_SUCCESS:
drawLogo(u8g2, LOGO_SUCCESS);
break;
case STATE_RESULT_FAILURE:
drawLogo(u8g2, LOGO_FAILURE);
break;
case STATE_RESULT_DISPLAY:
drawFullScreenText(u8g2, splitString(statusText, ':', 0), splitString(statusText, ':', 1) + "");
drawStatusText(u8g2, splitString(statusText, ':', 2), statusTextRightAligned);
u8g2.sendBuffer();
return;
case STATE_TRANSACT_VERIFY:
case STATE_BALANCE_VERIFY:
case STATE_LINK_VERIFY:
drawLogo(u8g2, LOGO_PENDING);
break;
}
drawStatusText(u8g2, statusText, statusTextRightAligned);
u8g2.sendBuffer();
drawStatusText(u8g2, statusText, statusTextRightAligned);
u8g2.sendBuffer();
}
void updateOLED(U8G2 u8g2, e_state state) {
String time = DateTime.format(DateFormatter::TIME_ONLY);
updateOLED(u8g2, state, time, WiFi.localIP().toString());
}
String time = DateTime.format(DateFormatter::TIME_ONLY);
updateOLED(u8g2, state, time, WiFi.localIP().toString());
}

View file

@ -3,78 +3,82 @@
#include <HTTPClient.h>
String byteArrayAsHexString(byte *buffer, byte bufferSize) {
String s = "";
for (byte i = 0; i < bufferSize; i++) {
s += (buffer[i] < 0x10 ? "0" : "");
s += String(buffer[i], HEX);
}
return s;
String s = "";
for (byte i = 0; i < bufferSize; i++) {
s += (buffer[i] < 0x10 ? "0" : "");
s += String(buffer[i], HEX);
}
return s;
}
String uint32AsHexString(uint32_t input) {
return byteArrayAsHexString(reinterpret_cast<byte *>(&input), sizeof input);
}
unsigned long cooldownSecondsRemaining(unsigned long timeout, unsigned long timer) {
return (timeout - (millis() - timer)) / 1000 + 1;
return (timeout - (millis() - timer)) / 1000 + 1;
}
String splitString(String data, char separator, int index) {
int found = 0;
int strIndex[] = {0, -1};
int maxIndex = data.length() - 1;
int found = 0;
int strIndex[] = {0, -1};
int maxIndex = data.length() - 1;
for (int i = 0; i <= maxIndex && found <= index; i++) {
if (data.charAt(i) == separator || i == maxIndex) {
found++;
strIndex[0] = strIndex[1] + 1;
strIndex[1] = (i == maxIndex) ? i + 1 : i;
}
}
return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
for (int i = 0; i <= maxIndex && found <= index; i++) {
if (data.charAt(i) == separator || i == maxIndex) {
found++;
strIndex[0] = strIndex[1] + 1;
strIndex[1] = (i == maxIndex) ? i + 1 : i;
}
}
return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
}
String cardLink(WiFiClient *wifi, HTTPClient *http, String apiUrl, String cardId) {
String finalRequestUrl = apiUrl + "/api/card/link?card=" + cardId;
http->begin(*wifi, finalRequestUrl.c_str());
int httpResponseCode = http->GET();
if (httpResponseCode == 200) {
String payload = http->getString();
http->end();
return payload;
}
http->end();
if (httpResponseCode > 0) {
return String("E:HTTP Error ") + httpResponseCode;
}
return String("E:Internal Error ") + httpResponseCode;
String finalRequestUrl = apiUrl + "/api/card/link?card=" + cardId;
http->begin(*wifi, finalRequestUrl.c_str());
int httpResponseCode = http->GET();
if (httpResponseCode == 200) {
String payload = http->getString();
http->end();
return payload;
}
http->end();
if (httpResponseCode > 0) {
return String("E:HTTP Error ") + httpResponseCode;
}
return String("E:Internal Error ") + httpResponseCode;
}
String cardBalance(WiFiClient *wifi, HTTPClient *http, String apiUrl, String cardId) {
String finalRequestUrl = apiUrl + "/api/card/balance?card=" + cardId;
http->begin(*wifi, finalRequestUrl.c_str());
int httpResponseCode = http->GET();
if (httpResponseCode == 200) {
String payload = http->getString();
http->end();
return payload;
}
http->end();
if (httpResponseCode > 0) {
return String("E:HTTP Error ") + httpResponseCode;
}
return String("E:Internal Error ") + httpResponseCode;
String finalRequestUrl = apiUrl + "/api/card/balance?card=" + cardId;
http->begin(*wifi, finalRequestUrl.c_str());
int httpResponseCode = http->GET();
if (httpResponseCode == 200) {
String payload = http->getString();
http->end();
return payload;
}
http->end();
if (httpResponseCode > 0) {
return String("E:HTTP Error ") + httpResponseCode;
}
return String("E:Internal Error ") + httpResponseCode;
}
String cardTransaction(WiFiClient *wifi, HTTPClient *http, String apiUrl, String cardId, String amount) {
String finalRequestUrl = apiUrl + "/api/card/transaction?card=" + cardId + "&amount=" + amount;
http->begin(*wifi, finalRequestUrl.c_str());
int httpResponseCode = http->GET();
if (httpResponseCode == 200) {
String payload = http->getString();
http->end();
return payload;
}
http->end();
if (httpResponseCode > 0) {
return String("E:HTTP Error ") + httpResponseCode;
}
return String("E:Internal Error ") + httpResponseCode;
}
String finalRequestUrl = apiUrl + "/api/card/transaction?card=" + cardId + "&amount=" + amount;
http->begin(*wifi, finalRequestUrl.c_str());
int httpResponseCode = http->GET();
if (httpResponseCode == 200) {
String payload = http->getString();
http->end();
return payload;
}
http->end();
if (httpResponseCode > 0) {
return String("E:HTTP Error ") + httpResponseCode;
}
return String("E:Internal Error ") + httpResponseCode;
}

View file

@ -1,34 +1,29 @@
#include <WiFi.h>
#include <lwip/sockets.h>
#include "wifiFix.h"
#define WIFI_CLIENT_FLUSH_BUFFER_SIZE (1024)
class WiFiClientFixed : public WiFiClient {
public:
void flush() override;
};
void WiFiClientFixed::flush() {
int res;
size_t a = available(), toRead = 0;
if (!a) {
return;//nothing to flush
}
uint8_t *buf = (uint8_t *) malloc(WIFI_CLIENT_FLUSH_BUFFER_SIZE);
if (!buf) {
return;//memory error
}
while (a) {
toRead = (a > WIFI_CLIENT_FLUSH_BUFFER_SIZE) ? WIFI_CLIENT_FLUSH_BUFFER_SIZE : a;
// override broken WiFiClient flush method, ref https://github.com/espressif/arduino-esp32/issues/6129#issuecomment-1237417915
//res = recv(fd(), buf, toRead, MSG_DONTWAIT);
res = read(buf, a);
if (res < 0) {
log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno));
stop();
break;
}
a -= res;
}
free(buf);
}
int res;
size_t a = available();
if (!a) {
return;//nothing to flush
}
auto *buf = (uint8_t *) malloc(WIFI_CLIENT_FLUSH_BUFFER_SIZE);
if (!buf) {
return;//memory error
}
while (a) {
// override broken WiFiClient flush method, ref https://github.com/espressif/arduino-esp32/issues/6129#issuecomment-1237417915
//res = recv(fd(), buf, toRead, MSG_DONTWAIT);
res = read(buf, a);
if (res < 0) {
log_e("fail on fd %d, errno: %d, \"%s\"", fd(), errno, strerror(errno));
stop();
break;
}
a -= res;
}
free(buf);
}

View file

@ -4,11 +4,15 @@
<TargetFramework>net7.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="linq2db" Version="4.4.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="7.0.2" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.6" />
<PackageReference Include="System.Data.SQLite" Version="1.0.115" />
<PackageReference Include="System.Data.SQLite.Core.osx.arm64" Version="1.0.117" />
</ItemGroup>

View file

@ -1,3 +1,4 @@
using System.Reflection;
using AfRApay.Web.Backend;
using LinqToDB.Data;
@ -6,8 +7,11 @@ Migrations.RunMigrations();
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddSwaggerGen(options => {
var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
});
#if (DEBUG)
builder.Services.AddControllers().AddRazorRuntimeCompilation();
@ -18,14 +22,13 @@ builder.Services.AddControllers();
var app = builder.Build();
if (!app.Environment.IsDevelopment()) {
app.UseExceptionHandler("/Error");
app.UseHsts();
if (app.Environment.IsDevelopment()) {
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseStatusCodePagesWithReExecute("/error");
app.UseStatusCodePagesWithReExecute("/Error");
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

View file

@ -0,0 +1,15 @@
using System.ComponentModel;
using AfRApay.Web.Backend.Tables;
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace AfRApay.Web.Controllers.Schema;
public class ErrorResponse {
[J("status")] [DefaultValue("error")] public string Status { get; set; }
[J("message")] public string Message { get; set; }
public ErrorResponse(string message) {
Status = "error";
Message = message;
}
}

View file

@ -0,0 +1,15 @@
using System.ComponentModel;
using AfRApay.Web.Backend.Tables;
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
namespace AfRApay.Web.Controllers.Schema;
public class UserResponse {
[J("data")] public User? Data { get; set; }
[J("status")] [DefaultValue("success")] public string Status { get; set; } = "success";
[J("message")] [DefaultValue("")] public string Message { get; set; } = "";
public UserResponse(User data) {
Data = data;
}
}

View file

@ -0,0 +1,37 @@
using AfRApay.Web.Backend;
using AfRApay.Web.Controllers.Schema;
using LinqToDB;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Filters;
namespace AfRApay.Web.Controllers;
[ApiController]
public class UserController : Controller {
/// <summary>
/// Renames the specified user.
/// </summary>
/// <param name="uid">The ID of the user to be renamed</param>
/// <param name="newName">The new name of the user</param>
/// <response code="200">Returns 200 if user was renamed successfully</response>
/// <response code="400">Returns 400 if newName isn't unique</response>
/// <response code="404">Returns 404 if no user matching the UID was found</response>
[HttpPut]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UserResponse))]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorResponse))]
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(ErrorResponse))]
//[SwaggerResponseExample(400, typeof(new )]
[Route("/api/user/{uid:int}/name")]
public async Task<IActionResult> Rename(int uid, [FromQuery] string newName) {
var db = new Database.DbConn();
if (db.Users.Any(p => p.Id == uid)) {
var user = db.Users.First(p => p.Id == uid);
user.Nickname = newName;
await db.UpdateAsync(user);
return new OkObjectResult(new UserResponse(user));
}
return NotFound(new ErrorResponse("Unknown user"));
}
}

View file

@ -23,8 +23,8 @@
<label for="nickname" class="form-label">Nickname</label>
<input type="text" maxlength="10" class="form-control" id="nickname" name="nickname" value="@user.Nickname" required>
</div>
<button type="submit" class="btn btn-primary" name="action" value="save">Save</button>
<button type="button" class="btn btn-danger" name="action" data-bs-toggle="modal" data-bs-target="#deleteConfirmModal">Delete</button> <!-- Calls the below modal dialog to confirm user deletetion -->
<button type="submit" class="btn btn-lg btn-primary" name="action" value="save">Save</button>
<button type="button" class="btn btn-lg btn-danger" name="action" data-bs-toggle="modal" data-bs-target="#deleteConfirmModal">Delete</button> <!-- Calls the below modal dialog to confirm user deletetion -->
</form>
<!-- Modal dialog to confirm user deletion -->
@ -39,8 +39,8 @@
Are you sure you want to delete this user?<br>This cannot be undone.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" form="change_user" class="btn btn-danger" name="action" value="delete">Delete User</button>
<button type="button" class="btn btn-lg btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" form="change_user" class="btn btn-lg btn-danger" name="action" value="delete">Delete User</button>
</div>
</div>
</div>
@ -48,18 +48,13 @@
<br/>
<h3 class="text-center">Cards</h3>
@if (!cards.Any()) {
<div>
No linked cards found.
</div>
}
else {
@{ <!-- We add all the cards here! -->
<div class="row justify-content-center">
@foreach (var card in cards) {
<div class="fake-card-wrapper m-2">
<div class="fake-card-wrapper blur-true m-2">
<div class="fake-card">
<div class="hover-overlay"></div>
<button type="button" class="btn btn-danger btn-lg hover-button">Delete</button>
<a class="btn btn-danger btn-lg hover-button">Delete</a>
<div class="face-card front">
<div class="chip">
<div>
@ -87,5 +82,35 @@ else {
</div>
</div>
}
<!-- Here we display a faded placholder card, with the add button at its center -->
<div class="fake-card-wrapper m-2">
<div class="fake-card">
<a class="btn btn-success btn-lg add-card-button" href="/LinkCard/@user.Id">Add Card</a>
<div class="face-card ghost-card front">
<div class="chip">
<div>
<div>
<div>
</div>
</div>
</div>
</div>
<h2 class="card-number">Awooo!</h2>
<h3 class="card-holder">@user.Nickname</h3>
<span class="validity">
<span class="small">VALID<br>THRU</span>
<span>01/2038</span>
</span>
<div class="fake-card-logo-top">
<img src="/img/afra.png" class="fake-card-logo-inner" alt="">
</div>
<div class="fake-card-logo-bottom">
<img src="/img/matecard.png" class="fake-card-logo-inner" alt="">
</div>
</div>
</div>
</div>
</div>
}

View file

@ -67,15 +67,14 @@
<td> <!-- Displayed when in big layout -->
<div class="d-none d-md-flex btn-group btn-group-lg" role="group">
<!-- Make sure these buttons match the small/mobile layout ones below -->
<a class="btn px-auto btn-danger" href="/UpdateBalance/@user.Id/-1.50">-1.50&euro;</a>
<a class="btn px-auto btn-success" href="/UpdateBalance/@user.Id/5">+5&euro;</a>
<a class="btn px-auto btn-success" href="/UpdateBalance/@user.Id/10">+10&euro;</a>
<a class="btn px-2 btn-secondary text-nowrap" href="/LinkCard/@user.Id">Link card</a>
<a class="btn px-3 btn-danger" href="/UpdateBalance/@user.Id/-1.50">-1.50&euro;</a>
<a class="btn px-3 btn-success" href="/UpdateBalance/@user.Id/5">+5&euro;</a>
<a class="btn px-3 btn-success" href="/UpdateBalance/@user.Id/10">+10&euro;</a>
<a class="btn px-2 btn-primary" href="/EditUser/@user.Id">Edit</a>
</div>
<!-- Displayed when in compact/phone layout -->
<div class="dropdown d-md-none">
<a class="btn btn-secondary dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-bs-toggle="dropdown" aria-expanded="false">
<a class="btn btn-lg px-1 btn-secondary dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-bs-toggle="dropdown" aria-expanded="false">
Actions
</a>
<div class="dropdown-menu p-1" aria-labelledby="dropdownMenuLink" style="min-width: max-content;"> <!-- Inline CSS, sets minimum width of drop down to maxium size of content -->
@ -84,7 +83,6 @@
<a class="btn btn-lg btn-danger" href="/UpdateBalance/@user.Id/-1.50">-1.50&euro;</a>
<a class="btn btn-lg btn-success" href="/UpdateBalance/@user.Id/5">+5&euro;</a>
<a class="btn btn-lg btn-success" href="/UpdateBalance/@user.Id/10">+10&euro;</a>
<a class="btn btn-lg btn-secondary" href="/LinkCard/@user.Id">Link card</a>
<a class="btn btn-lg btn-primary" href="/EditUser/@user.Id">Edit</a>
</div>
</div>

View file

@ -8,15 +8,6 @@
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://0.0.0.0:7115;http://localhost:5296",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View file

@ -258,6 +258,17 @@ button.accept-policy {
margin-bottom: 60px;
}
.ghost-card {
filter:grayscale(100%) blur(4px);
}
.add-card-button {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
filter: grayscale(0%)
}
.hover-button {
position: absolute;
top: 50%;
@ -268,20 +279,13 @@ button.accept-policy {
transition: .5s ease;
z-index: 2;
}
.hover-overlay {
position: absolute;
height: 100%;
width: 100%;
opacity: 0;
border-radius: 15px;
transition: .5s ease;
background-color: #808080;
z-index: 1;
}
.fake-card-wrapper:hover .hover-button {
opacity: 1;
visibility: visible;
}
.fake-card-wrapper:hover .hover-overlay {
opacity: 0.75;
.fake-card-wrapper:hover.blur-true .face-card{
transition: 0.3s ease;
filter:blur(4px) grayscale(100%);
}

40
hooks/pre-commit.hook Executable file
View file

@ -0,0 +1,40 @@
#!/bin/bash
# Regexp for grep to only choose some file extensions for formatting
exts="\.\(cpp\|h\)$"
# The formatter to use
formatter=`which uncrustify`
# Check availability of the formatter
if [ -z "$formatter" ]
then
1>&2 echo "$formatter not found. Pre-commit formatting will not be done."
exit 0
fi
# Format staged files
for file in `git diff --cached --name-only --diff-filter=ACMR | grep $exts`
do
echo "Formatting $file"
# get tmpfile
tmpfile="${file//.cpp/.tmp.cpp}"
tmpfile="${file//.h/.tmp.h}"
# Get the file from index
git show ":$file" > "$tmpfile"
# Format it
"$formatter" --no-backup -c AfRApay.MateCard/.uncrustify.cfg "$tmpfile"
# Create a blob object from the formatted file
hash=`git hash-object -w "$tmpfile"`
# Add it back to index
git update-index --add --cacheinfo 100644 "$hash" "$file"
# Remove the tmp file
rm "$tmpfile"
done
# If no files left in index after formatting - fail
ret=0
if [ ! "`git diff --cached --name-only`" ]; then
1>&2 echo "No files left after formatting"
exit 1
fi