diff --git a/Logos/Sicherungskopie_von_logosATM.cdr b/Logos/Sicherungskopie_von_logosATM.cdr new file mode 100644 index 0000000..9748908 Binary files /dev/null and b/Logos/Sicherungskopie_von_logosATM.cdr differ diff --git a/Logos/logosATM.cdr b/Logos/logosATM.cdr index 9748908..5d73bb7 100644 Binary files a/Logos/logosATM.cdr and b/Logos/logosATM.cdr differ diff --git a/firmware/include/button.h b/firmware/include/button.h index e341f9d..9e8f909 100644 --- a/firmware/include/button.h +++ b/firmware/include/button.h @@ -12,6 +12,7 @@ private: volatile unsigned long last_pressed_ms = 0; volatile unsigned long pressed_at_ms = 0; volatile unsigned long pressed_after_ms = 0; + volatile unsigned long pressed_count = 0; bool activeWaiting = false; unsigned long startWaitTime = 0; @@ -26,6 +27,8 @@ public: AtmButton(uint8_t, uint8_t ); void begin(); + void reset(); + void softPress(); bool wasPressed(); void ledOn(); void ledOff(); diff --git a/firmware/include/coinacceptor.h b/firmware/include/coinacceptor.h index 8a42150..06ba76d 100644 --- a/firmware/include/coinacceptor.h +++ b/firmware/include/coinacceptor.h @@ -2,6 +2,8 @@ #define COINACCEPTOR_H #include "Arduino.h" +#include +#include class CoinAcceptor { private: @@ -9,26 +11,38 @@ private: uint8_t accState = ACC_DISABLED; volatile uint16_t coinPulses = 0; + volatile uint16_t allPulses = 0; uint16_t coinValue = 0; + uint16_t sumValue = 0; + std::string sumValueStr = ""; volatile bool coinDetected = false; + volatile unsigned long currentMs = 0; volatile unsigned long lastPulseAtMs = 0; - static void IRAM_ATTR pulseIsrHandler(void* arg); + void IRAM_ATTR pulseIsrHandler(); public: const uint8_t ACC_DISABLED = LOW; const uint8_t ACC_ENABLED = HIGH; const unsigned int* pulseValues; + unsigned int* coinNumbers; const size_t maxPulses = 0; const uint8_t pulseWidthMs = 0; + const uint16_t maxAllowedCentToInsert = 0; uint8_t detectionError = 0; - CoinAcceptor(uint8_t pulsePin, uint8_t enablePin, const unsigned int* pulseValues, const size_t maxPulses, const uint8_t pulseWidthMs ); + CoinAcceptor(uint8_t pulsePin, uint8_t enablePin, const unsigned int* pulseValues, const size_t maxPulses, const uint8_t pulseWidthMs, const uint16_t maxAllowedCentToInsert); + ~CoinAcceptor(); void begin(); void disable(); void enable(); uint8_t state(); bool detectCoin(); uint16_t getCoinValue(); + uint16_t getSumValue(); + void resetSumValue(); + std::string getSumValueStr(); + std::string getCoinsNumStr() const; + std::string getCoinsValStr() const; }; diff --git a/firmware/include/epaperdisplay.h b/firmware/include/epaperdisplay.h index 44e6e1e..9bfb064 100644 --- a/firmware/include/epaperdisplay.h +++ b/firmware/include/epaperdisplay.h @@ -38,7 +38,7 @@ class EpaperDisplay void sleep(); void initScreen(); void homeScreen(); - void showInsertedAmount(const char*); + void showInsertedAmount(const char* amount_in_euro, const char* coin_values, const char* coin_numbers, const char* max_ammount_in_euro, bool update); void updateInsertedAmount(const char*); void cleanScreen(); diff --git a/firmware/include/main.h b/firmware/include/main.h index d0e90e0..ebfa484 100644 --- a/firmware/include/main.h +++ b/firmware/include/main.h @@ -1,13 +1,4 @@ #ifndef MAIN_H #define MAIN_H -typedef enum { - INIT, - WAIT_FOR_COIN, - PROCESS_COIN, - GENERATE_QR, - PAYMENT_CONFIRMED, - ERROR_STATE -} State; - #endif diff --git a/firmware/include/state.h b/firmware/include/state.h new file mode 100644 index 0000000..a4cb796 --- /dev/null +++ b/firmware/include/state.h @@ -0,0 +1,36 @@ +#ifndef ATM_STATE_H +#define ATM_STATE_H + +#include +#include +#include +#include + +#include "button.h" +#include "coinacceptor.h" +#include "epaperdisplay.h" + +extern EpaperDisplay epDisp; +extern AtmButton ok_button; +extern AtmButton cancel_button; +extern CoinAcceptor cacc; + +namespace atm { + + typedef enum { + INIT, + WAIT_FOR_COIN, + WAIT_FOR_BUTTON, + PROCESS_COIN, + GENERATE_QR, + CANCEL_PAYMENT, + PAYMENT_CONFIRMED, + ERROR_STATE + } State; + + void stateMachine(void); + void reset(void); + +} + +#endif \ No newline at end of file diff --git a/firmware/src/button.cpp b/firmware/src/button.cpp index 88052b9..b052bcc 100644 --- a/firmware/src/button.cpp +++ b/firmware/src/button.cpp @@ -20,8 +20,8 @@ void IRAM_ATTR AtmButton::buttonIsrHandler(void* arg) { self->pressed_at_ms = current_ms; self->pressed_after_ms = current_ms - self->last_pressed_ms; self->last_pressed_ms = current_ms; + self->pressed_count++; } - self->pressed = true; } bool AtmButton::wasPressed() { @@ -32,6 +32,16 @@ bool AtmButton::wasPressed() { return false; } +void AtmButton::reset() { + pressed = false; // Reset des Status + pressed_count = 0; +} + +void AtmButton::softPress() { + pressed = true; // Reset des Status + pressed_count++; +} + void AtmButton::ledOn() { digitalWrite(ledPin, LEDON); ledState = LEDON; diff --git a/firmware/src/coinacceptor.cpp b/firmware/src/coinacceptor.cpp index 2478166..7f2cf40 100644 --- a/firmware/src/coinacceptor.cpp +++ b/firmware/src/coinacceptor.cpp @@ -1,7 +1,17 @@ #include "coinacceptor.h" +#include "lnurlutil.h" +#include -CoinAcceptor::CoinAcceptor(uint8_t pulsePin, uint8_t enablePin, const unsigned int* pulseValues, const size_t maxPulses, const uint8_t pulseWidthMs) : - pulsePin(pulsePin), enablePin(enablePin), pulseValues(pulseValues), maxPulses(maxPulses), pulseWidthMs(pulseWidthMs) {} +CoinAcceptor::CoinAcceptor(uint8_t pulsePin, uint8_t enablePin, const unsigned int* pulseValues, const size_t maxPulses, const uint8_t pulseWidthMs, const uint16_t maxAllowedCentToInsert) : + pulsePin(pulsePin), enablePin(enablePin), pulseValues(pulseValues), maxPulses(maxPulses), pulseWidthMs(pulseWidthMs), maxAllowedCentToInsert(maxAllowedCentToInsert) { + /* malloc the array to store the single given coins in*/ + coinNumbers = new unsigned int[maxPulses]; + resetSumValue(); +} + +CoinAcceptor::~CoinAcceptor() { + delete[] coinNumbers; +} // Initialisierung des Pins & Interrupts void CoinAcceptor::begin() { @@ -11,24 +21,24 @@ void CoinAcceptor::begin() { if(digitalRead(pulsePin) == HIGH) { Serial.println("Attaching interrupt to falling edge of coin acceptor pulse pin."); - attachInterruptArg(digitalPinToInterrupt(pulsePin), pulseIsrHandler, this, FALLING); + attachInterrupt(digitalPinToInterrupt(pulsePin), std::bind(&CoinAcceptor::pulseIsrHandler,this), FALLING); } else { Serial.println("Attaching interrupt to rising edge of coin acceptor pulse pin."); - attachInterruptArg(digitalPinToInterrupt(pulsePin), pulseIsrHandler, this, RISING); + attachInterrupt(digitalPinToInterrupt(pulsePin), std::bind(&CoinAcceptor::pulseIsrHandler,this), RISING); } } -void IRAM_ATTR CoinAcceptor::pulseIsrHandler(void* arg) { - CoinAcceptor* self = static_cast(arg); - unsigned long currentMs = micros()/1000; - if((self->lastPulseAtMs == 0) || ((currentMs - self->lastPulseAtMs) > 1.5*self->pulseWidthMs)) +void IRAM_ATTR CoinAcceptor::pulseIsrHandler() { + currentMs = millis(); + if((lastPulseAtMs == 0) || ((currentMs - lastPulseAtMs) > 1.5*pulseWidthMs)) { - self->coinDetected = true; - self->lastPulseAtMs = currentMs; - self->coinPulses++; + coinDetected = true; + lastPulseAtMs = currentMs; + coinPulses++; } + allPulses++; } void CoinAcceptor::disable() { @@ -37,13 +47,71 @@ void CoinAcceptor::disable() { } void CoinAcceptor::enable() { - accState = ACC_ENABLED; - coinDetected = false; - detectionError = 0; - coinPulses = 0; - coinValue = 0; - lastPulseAtMs = 0; - digitalWrite(enablePin, accState); + if(sumValue < maxAllowedCentToInsert) + { + accState = ACC_ENABLED; + coinDetected = false; + detectionError = 0; + coinPulses = 0; + allPulses = 0; + coinValue = 0; + lastPulseAtMs = 0; + digitalWrite(enablePin, accState); + } +} + +void CoinAcceptor::resetSumValue() +{ + sumValue = 0; + for (size_t i = 0; i < maxPulses; i++) { + coinNumbers[i] = 0; + } +} + +uint16_t CoinAcceptor::getSumValue() +{ + return sumValue; +} + +std::string CoinAcceptor::getSumValueStr() +{ + float value = sumValue/100.0; + return(lnurlutil::floatToStringWithPrecision(value,2)); +} + +std::string CoinAcceptor::getCoinsNumStr() const { + std::string result = ""; + + for (size_t i = 0; i < maxPulses; i++) { + if(pulseValues[i] > 0) + { + char temp[16]; + snprintf(temp, sizeof(temp), " %3d ", coinNumbers[i]); + result += temp; + } + } + return result; +} + +std::string CoinAcceptor::getCoinsValStr() const { + std::string result = ""; + + for (size_t i = 0; i < maxPulses; i++) { + if(pulseValues[i] > 0) + { + char temp[16]; + if(pulseValues[i] < 100) + { + snprintf(temp, sizeof(temp), " %2dct", pulseValues[i]); + } + else + { + snprintf(temp, sizeof(temp), " %1dEur", int(pulseValues[i]/100)); + } + result += temp; + } + } + return result; } uint8_t CoinAcceptor::state() { @@ -60,13 +128,15 @@ bool CoinAcceptor::detectCoin() { { lastPulseBeforeMs = millis() - lastPulseAtMs; } while ((lastPulseAtMs == 0) || (lastPulseBeforeMs < maxMsSinceLastPulse )); - Serial.printf("DONE --- Pulses: %d, Last pulse before: %d ms (max allowed: %d). \n", coinPulses, lastPulseBeforeMs, maxMsSinceLastPulse); + Serial.printf("DONE --- All pulses: %d Coin pulses: %d, Last pulse before: %d ms (max allowed: %d). \n", allPulses, coinPulses, lastPulseBeforeMs, maxMsSinceLastPulse); // there is one additial edge in the signal ... remove it coinPulses--; if((coinPulses > 1) && (coinPulses <= maxPulses)) { coinValue = pulseValues[coinPulses-1]; // coin array index is running from 0 to maxPulses-1 ... - Serial.printf(" --- Detected coin value: %d cents\n", coinValue); + coinNumbers[coinPulses-1]++; + sumValue = sumValue + coinValue; + Serial.printf(" --- Detected coin value: %d cents. Sum is now: %d cents\n", coinValue, sumValue); } else { diff --git a/firmware/src/epaperdisplay.cpp b/firmware/src/epaperdisplay.cpp index 98bab2b..3cce85a 100644 --- a/firmware/src/epaperdisplay.cpp +++ b/firmware/src/epaperdisplay.cpp @@ -222,19 +222,28 @@ void EpaperDisplay::homeScreen() } while (epDisplay->nextPage()); } -void EpaperDisplay::showInsertedAmount(const char* amount_in_euro) +void EpaperDisplay::showInsertedAmount(const char* amount_in_euro, const char* coin_values, const char* coin_numbers, const char* max_ammount_in_euro, bool update) { uint16_t y = 0; - epDisplay->setFullWindow(); + if(false == update) + { + epDisplay->setFullWindow(); + } + else{ + epDisplay->setPartialWindow(8,64,288,248); + } epDisplay->firstPage(); do { - y = 0; - epDisplay->fillRect(0,y,epDisplay->width(), 50, GxEPD_BLACK); - drawXCenteredText("Druecke den Taster, um die\n", 1, y+20, GxEPD_WHITE, GxEPD_BLACK); - drawXCenteredText("Einzahlung abzuschliessen!\n", 1, y+40, GxEPD_WHITE, GxEPD_BLACK); - + if(false == update) + { + y = 0; + epDisplay->fillRect(0,y,epDisplay->width(), 50, GxEPD_BLACK); + drawXCenteredText("Druecke den Taster, um die\n", 1, y+20, GxEPD_WHITE, GxEPD_BLACK); + drawXCenteredText("Einzahlung abzuschliessen!\n", 1, y+40, GxEPD_WHITE, GxEPD_BLACK); + } + y = 180; epDisplay->setFont(); epDisplay->setTextSize(1); @@ -249,15 +258,17 @@ void EpaperDisplay::showInsertedAmount(const char* amount_in_euro) y = 250; epDisplay->setFont(); epDisplay->setTextSize(1); - drawText("1ct 2ct 5ct 10ct 20ct 50ct 1Eur 2Eur\n", 0, 20, y, GxEPD_BLACK, GxEPD_WHITE); + drawText(coin_values, 0, 20, y, GxEPD_BLACK, GxEPD_WHITE); - drawText(" 0 0 0 0 0 0 0 0 \n", 0, 20, y+20, GxEPD_BLACK, GxEPD_WHITE); + drawText(coin_numbers, 0, 20, y+20, GxEPD_BLACK, GxEPD_WHITE); - y = 310; - epDisplay->fillRect(0,y,epDisplay->width(), epDisplay->height()-y, GxEPD_BLACK); - epDisplay->drawBitmap(1, y+10, epd_bitmap_makerlablogosATM_ML_logo_land, 248, 69, GxEPD_WHITE); - epDisplay->drawBitmap(220, y+10, epd_bitmap_makerlablogosATM_BC_LN, 73, 64, GxEPD_WHITE); - + if(false == update) + { + y = 310; + epDisplay->fillRect(0,y,epDisplay->width(), epDisplay->height()-y, GxEPD_BLACK); + epDisplay->drawBitmap(1, y+10, epd_bitmap_makerlablogosATM_ML_logo_land, 248, 69, GxEPD_WHITE); + epDisplay->drawBitmap(220, y+10, epd_bitmap_makerlablogosATM_BC_LN, 73, 64, GxEPD_WHITE); + } epDisplay->drawRoundRect(5, 55, epDisplay->width()-10, 250, 5, GxEPD_BLACK); } while (epDisplay->nextPage()); diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index d9f53f0..f8554a7 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -3,88 +3,51 @@ #include "epaperdisplay.h" #include "button.h" #include "coinacceptor.h" -#include "lnurlutil.h" +#include "state.h" +//#include "lnurlutil.h" #include "config.h" EpaperDisplay epDisp(DSPLY_PIN_CS, DSPLY_PIN_DC, DSPLY_PIN_RST, DSPLY_PIN_BUSY); -AtmButton button(BUTTON_PIN, LED_BUTTON_PIN); -CoinAcceptor cacc(COIN_PIN, MOSFET_PIN, COINS, COINS_COUNT, COIN_PULSE_WIDTH_MS); +AtmButton ok_button(OK_BUTTON_PIN, OK_LED_BUTTON_PIN); +AtmButton cancel_button(OK_BUTTON_PIN, OK_LED_BUTTON_PIN); +CoinAcceptor cacc(COIN_PIN, MOSFET_PIN, COINS, COINS_COUNT, COIN_PULSE_WIDTH_MS, MAX_ALLOWED_EUROCENT_VALUE); + -static State current_state = INIT; -static State next_state = INIT; -std::string lnurl_str = ""; -void init_atm(); void setup() { Serial.begin(115200); - epDisp.init(); // Init eInk display + epDisp.init(); // Init eInk display epDisp.initScreen(); - cacc.begin(); // Init Coin acceptor - cacc.disable(); // Disable coin detection - button.begin(); // Init button - button.wasPressed(); // Reset button - if already pressed + cacc.begin(); // Init Coin acceptor + cacc.disable(); // Disable coin detection + ok_button.begin(); // Init OK button + ok_button.reset(); // Reset Ok button - if already pressed + cancel_button.begin(); // Init Cancel button + cancel_button.reset(); // Reset cancel button - if already pressed + cancel_button.ledOff(); - while( true == button.iswaitingUntilPressed(/* maxTimeToWaitMs0= */ 0, /* blinkPeriodMs= */ 1000)) + /* activate both buttons and wait until at least one is pressed */ + while( + true == ok_button.iswaitingUntilPressed(/* maxTimeToWaitMs0= */ 0, /* blinkPeriodMs= */ 1000) && + true == cancel_button.iswaitingUntilPressed(/* maxTimeToWaitMs0= */ 0, /* blinkPeriodMs= */ 1000) + ) {} + cancel_button.softPress(); // just do a 'soft press' to both buttons to stop waiting and blink for the unpressed as well + ok_button.softPress(); // just do a 'soft press' to both buttons to stop waiting and blink for the unpressed as well + atm::reset(); // reset the ATM state machine and go to init state! } void loop() { - switch (current_state) { - case INIT: - init_atm(); - button.wasPressed(); - next_state = WAIT_FOR_COIN; - break; - case WAIT_FOR_COIN: - if(button.wasPressed()) - { - epDisp.showInsertedAmount("10,00"); - button.wasPressed(); - next_state = PROCESS_COIN; - } - break; - case PROCESS_COIN: - //update coin ammount until OK or CANCEL button is pressed - if(button.wasPressed()) - { - next_state = GENERATE_QR; - button.wasPressed(); - } - break; - - case GENERATE_QR: - lnurl_str = lnurlutil::createQrContent(0.05); - epDisp.qrCode(lnurl_str.c_str()); - next_state = PAYMENT_CONFIRMED; - button.wasPressed(); - break; - case PAYMENT_CONFIRMED: - if(button.wasPressed()) - { - next_state = INIT; - button.wasPressed(); - } - break; - - case ERROR_STATE: - next_state = WAIT_FOR_COIN; - break; - } - - current_state = next_state; + /* now start the ATM state machine in the endless loop*/ + atm::stateMachine(); } -void init_atm() -{ - button.ledOff(); - epDisp.homeScreen(); - cacc.enable(); -} + diff --git a/firmware/src/state.cpp b/firmware/src/state.cpp new file mode 100644 index 0000000..786ee24 --- /dev/null +++ b/firmware/src/state.cpp @@ -0,0 +1,88 @@ +#include "state.h" +#include "epaperdisplay.h" + +namespace { + std::string lnurl_str = ""; + bool update_value = false; + + static atm::State current_state = atm::State::INIT; + static atm::State next_state = atm::State::INIT; +} + +namespace atm { + void reset(void) + { + current_state = INIT; + next_state = INIT; + } + + void stateMachine(void) + { + switch (current_state) { + case INIT: + update_value = false; + ok_button.ledOff(); + ok_button.reset(); + cancel_button.ledOff(); + cancel_button.reset(); + epDisp.homeScreen(); + cacc.resetSumValue(); + cacc.enable(); + next_state = WAIT_FOR_COIN; + break; + case WAIT_FOR_BUTTON: + if(ok_button.wasPressed()) + { + next_state = GENERATE_QR; + ok_button.reset(); + } + if(cancel_button.wasPressed()) + { + next_state = CANCEL_PAYMENT; + cancel_button.reset(); + } + case WAIT_FOR_COIN: + if(true == cacc.detectCoin()) + { + next_state = PROCESS_COIN; + } + break; + case PROCESS_COIN: + Serial.printf("Summe: '%s' (cents %d) \n", cacc.getSumValueStr().c_str(), cacc.getSumValue()); + Serial.printf("%s\n", cacc.getCoinsNumStr().c_str()); + Serial.printf("%s\n", cacc.getCoinsValStr().c_str()); + epDisp.showInsertedAmount(cacc.getSumValueStr().c_str(),cacc.getCoinsValStr().c_str(), cacc.getCoinsNumStr().c_str(), "", update_value); + update_value = true; + ok_button.ledOn(); + ok_button.reset(); + cancel_button.ledOn(); + ok_button.reset(); + cacc.enable(); + next_state = WAIT_FOR_BUTTON; + break; + case GENERATE_QR: + //lnurl_str = lnurlutil::createQrContent(0.05); + epDisp.qrCode(lnurl_str.c_str()); + next_state = PAYMENT_CONFIRMED; + ok_button.wasPressed(); + break; + case CANCEL_PAYMENT: + break; + case PAYMENT_CONFIRMED: + if(ok_button.wasPressed()) + { + next_state = INIT; + ok_button.wasPressed(); + } + break; + + case ERROR_STATE: + next_state = WAIT_FOR_COIN; + break; + } + + current_state = next_state; + + } + +} \ No newline at end of file