Added more details to the state machine and updating the inserted value

This commit is contained in:
Jens Noack 2025-03-01 00:19:00 +01:00
parent 90b894a307
commit 5b69468a88
12 changed files with 295 additions and 109 deletions

Binary file not shown.

Binary file not shown.

View file

@ -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();

View file

@ -2,6 +2,8 @@
#define COINACCEPTOR_H
#include "Arduino.h"
#include <cstring>
#include <string>
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;
};

View file

@ -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();

View file

@ -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

36
firmware/include/state.h Normal file
View file

@ -0,0 +1,36 @@
#ifndef ATM_STATE_H
#define ATM_STATE_H
#include <Arduino.h>
#include <lnurl.h>
#include <cstring>
#include <string>
#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

View file

@ -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;

View file

@ -1,7 +1,17 @@
#include "coinacceptor.h"
#include "lnurlutil.h"
#include <FunctionalInterrupt.h>
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<CoinAcceptor*>(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,14 +47,72 @@ void CoinAcceptor::disable() {
}
void CoinAcceptor::enable() {
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() {
return accState;
@ -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
{

View file

@ -222,18 +222,27 @@ 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;
if(false == update)
{
epDisplay->setFullWindow();
}
else{
epDisplay->setPartialWindow(8,64,288,248);
}
epDisplay->firstPage();
do
{
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();
@ -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);
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());

View file

@ -3,19 +3,18 @@
#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()
{
@ -26,65 +25,29 @@ void setup()
cacc.begin(); // Init Coin acceptor
cacc.disable(); // Disable coin detection
button.begin(); // Init button
button.wasPressed(); // Reset button - if already pressed
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;
/* now start the ATM state machine in the endless loop*/
atm::stateMachine();
}
current_state = next_state;
}
void init_atm()
{
button.ledOff();
epDisp.homeScreen();
cacc.enable();
}

88
firmware/src/state.cpp Normal file
View file

@ -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;
}
}