From 519e15036fb8a4f587d03811367b9412d689b3fc Mon Sep 17 00:00:00 2001 From: jenoack Date: Tue, 25 Feb 2025 19:33:56 +0100 Subject: [PATCH] adopted .gitignore file --- .gitignore | 4 +- firmware/include/epaperdisplay.h | 16 ++- firmware/include/lnurlutil.h | 22 ++++ firmware/include/main.h | 96 --------------- firmware/platformio.ini | 2 +- firmware/src/epaperdisplay.cpp | 89 +++++++++----- firmware/src/lnurlutil.cpp | 89 ++++++++++++++ firmware/src/main.cpp | 195 ++----------------------------- 8 files changed, 196 insertions(+), 317 deletions(-) create mode 100644 firmware/include/lnurlutil.h create mode 100644 firmware/src/lnurlutil.cpp diff --git a/.gitignore b/.gitignore index 456016a..c11c412 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -.vscode/ -.pio/ +firmware/.vscode/ +firmware/.pio/ telegram_notification_bot/atmbot_rust/target/ \ No newline at end of file diff --git a/firmware/include/epaperdisplay.h b/firmware/include/epaperdisplay.h index 6e6bde5..b4d6062 100644 --- a/firmware/include/epaperdisplay.h +++ b/firmware/include/epaperdisplay.h @@ -14,8 +14,19 @@ class EpaperDisplay private: GxEPD2_DISPLAY_CLASS *epDisplay; + typedef struct s_qrdata + { + uint16_t current_y; + uint16_t current_x; + uint16_t start_x; + uint16_t start_y; + uint16_t module_size; + uint16_t qr_size; + } t_qrdata; + public: - + const uint8_t QR_VERSION = 15; // 20 is standard. 6 for simpler QR code, but does not always work. + EpaperDisplay(uint8_t DSPLY_PIN_CS, uint8_t DSPLY_PIN_DC, uint8_t DSPLY_PIN_RST, uint8_t DSPLY_PIN_BUSY); ~EpaperDisplay(); void init(); @@ -25,10 +36,13 @@ class EpaperDisplay void updateInsertedAmount(const char*); void cleanScreen(); + uint16_t xCenterText(const char* text); + void drawXCenteredText(const char* text, uint8_t textSize, uint16_t y, uint16_t fgColor, uint16_t bgColor ); void drawText(const char* text, uint8_t textSize, uint16_t x, uint16_t y, uint16_t fgColor, uint16_t bgColor ); void drawText(const char* text, uint16_t x, uint16_t y); void updateText(const char* text, uint16_t x, uint16_t y); void updateText(const char* text, uint8_t textSize, uint16_t x, uint16_t y, uint16_t fgColor, uint16_t bgColor); + void qrCode(const char* content); }; diff --git a/firmware/include/lnurlutil.h b/firmware/include/lnurlutil.h new file mode 100644 index 0000000..aab1477 --- /dev/null +++ b/firmware/include/lnurlutil.h @@ -0,0 +1,22 @@ +#ifndef BLESKOMAT_UTIL_H +#define BLESKOMAT_UTIL_H + +// Lnurluril.h/cpp code basically taken form Bleskomat util code: https://github.com/bleskomat/bleskomat-diy and simplyfied to our needs. +// Thank you! + +#include +#include +#include +#include +#include + +namespace lnurlutil { + std::string createQrContent (const double accumulatedValue); + std::string createSignedLnurlWithdraw(const double &amount); + std::string lnurlEncode(const std::string &text); + std::string toUpperCase(std::string s); + std::string urlEncode(const std::string &value); + std::string floatToStringWithPrecision(const float &value, const unsigned short &precision = 6); +} + +#endif diff --git a/firmware/include/main.h b/firmware/include/main.h index c67ef41..ebfa484 100644 --- a/firmware/include/main.h +++ b/firmware/include/main.h @@ -1,100 +1,4 @@ #ifndef MAIN_H #define MAIN_H -#include -#include -#include -#include "qrcode.h" -#include "Bitcoin.h" -#include -#include -#include -#include - -// ######################################## -// ########### USER ACTION ########### -// ######################################## -// Generate and copy in LNbits with the LNURLDevice extension the string for the ATM and paste it here: -const char* lnurlDeviceString = "https://legend.lnbits.com/lnurldevice/api/v1/lnurl/idexample,keyexample,EUR"; -// #################### EXAMPLE: https://legend.lnbits.com/lnurldevice/api/v1/lnurl/idexample,keyexample,EUR -// ######################################## -// ######################################## -// ######################################## - -// Activate for debugging over Serial (1), deactivate in production use (0) -#define DEBUG_MODE 1 - -#define COIN_PIN 17 - -#define PULSE_TIMEOUT 200 -const uint8_t LED_BUTTON_PIN = 21; // old: 13 | new: 21 -const uint8_t BUTTON_PIN = 32; -#define MOSFET_PIN 16 // old: 12 | new: 16 -#define QR_VERSION 6 // 20 is standard. 6 for simpler QR code, but does not always work. - -const uint8_t DSPLY_PIN_CS = 26; -const uint8_t DSPLY_PIN_DC = 25; -const uint8_t DSPLY_PIN_RST = 33; -const uint8_t DSPLY_PIN_BUSY = 27; - -constexpr unsigned int COINS[] = { 1, 2, 5, 10, 20, 50, 100, 200}; -constexpr size_t COINS_COUNT = sizeof(COINS) / sizeof(COINS[0]); -const unsigned long COIN_PULSE_WIDTH_MS = 45; - -typedef struct s_qrdata -{ - uint8_t current_y; - uint8_t current_x; - uint8_t start_x; - uint8_t start_y; - uint8_t module_size; - uint8_t qr_size; -} t_qrdata; - -extern String baseURLATM; -extern String secretATM; -extern String currencyATM; - - -// put function declarations here: -void clean_screen(); -void initialize_display(); -void to_upper(char* arr); -void qr_withdrawl_screen(const char* qr_content); -char* makeLNURL(float total); -int xor_encrypt(uint8_t* output, size_t outlen, uint8_t* key, size_t keylen, uint8_t* nonce, size_t nonce_len, uint64_t pin, uint64_t amount_in_cents); -void show_inserted_amount(int amount_in_cents); -String get_amount_string(int amount_in_cents); -unsigned int detect_coin(); -void home_screen(); -void IRAM_ATTR button_pressed_itr(); -void wait_for_user_to_scan(); -String getValue(String data, char separator, int index); -void display_sleep(); -void test_macro(); - -// Waveshare 1.54 inch e-ink display functions -void home_screen_waveshare_1_54(); -void show_inserted_amount_waveshare_1_54(String amount_in_euro); -void qr_withdrawl_screen_waveshare_1_54(const char* qr_content); -void clean_screen_waveshare_1_54(); - -// Waveshare 2.7 inch e-ink display functions -void home_screen_waveshare_2_7(); -void show_inserted_amount_waveshare_2_7(String amount_in_euro); -void qr_withdrawl_screen_waveshare_2_7(const char* qr_content); -void clean_screen_waveshare_2_7(); - -// Waveshare 2.13 inch e-ink display (250x122) functions -void home_screen_waveshare_2_13(); -void show_inserted_amount_waveshare_2_13(String amount_in_euro); -void qr_withdrawl_screen_waveshare_2_13(const char* qr_content); -void clean_screen_waveshare_2_13(); - -// Waveshare 2.13 inch e-ink display (D) flex (yellow) (212x104) functions -void home_screen_waveshare_2_13_flex(); -void show_inserted_amount_waveshare_2_13_flex(String amount_in_euro); -void qr_withdrawl_screen_waveshare_2_13_flex(const char* qr_content); -void clean_screen_waveshare_2_13_flex(); - #endif diff --git a/firmware/platformio.ini b/firmware/platformio.ini index 48746ed..1d529a5 100644 --- a/firmware/platformio.ini +++ b/firmware/platformio.ini @@ -16,4 +16,4 @@ monitor_speed = 115200 lib_deps = zinggjm/GxEPD2@^1.5.2 ricmoo/QRCode@^0.0.1 - stepansnigirev/uBitcoin@^0.2.0 + chill1/lnurl@0.4.1 diff --git a/firmware/src/epaperdisplay.cpp b/firmware/src/epaperdisplay.cpp index 4718eec..33f57f5 100644 --- a/firmware/src/epaperdisplay.cpp +++ b/firmware/src/epaperdisplay.cpp @@ -1,4 +1,5 @@ #include "epaperdisplay.h" +#include "qrcode.h" // *** for Waveshare ESP32 Driver board *** // #if defined(ESP32) && defined(USE_HSPI_FOR_EPD) @@ -28,10 +29,12 @@ void EpaperDisplay::init() hspi.begin(13, 12, 14, 15); // remap hspi for EPD (swap pins) epDisplay>epd2.selectSPI(hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0)); #endif + epDisplay->setRotation(1); } + void EpaperDisplay::cleanScreen() { epDisplay->firstPage(); @@ -42,6 +45,20 @@ void EpaperDisplay::cleanScreen() epDisplay->hibernate(); } + +uint16_t EpaperDisplay::xCenterText(const char* text) +{ + int16_t xul, yul; + uint16_t textWidth, textHeight; + epDisplay->getTextBounds(text, 0, 0, &xul, &yul, &textWidth, &textHeight); + xul = (epDisplay->width() - textWidth)/2; + if(xul < 0 ) + { + xul = 0; + } + return xul; +} + void EpaperDisplay::drawText(const char* text, uint16_t x, uint16_t y) { epDisplay->setCursor(x, y); @@ -55,6 +72,12 @@ void EpaperDisplay::drawText(const char* text, uint8_t textSize, uint16_t x, uin drawText(text, x, y); } +void EpaperDisplay::drawXCenteredText(const char* text, uint8_t textSize, uint16_t y, uint16_t fgColor, uint16_t bgColor ) +{ + epDisplay->setTextSize(textSize); + epDisplay->setTextColor(fgColor, bgColor); + drawText(text, xCenterText(text), y); +} void EpaperDisplay::updateText(const char* text, uint16_t x, uint16_t y) { @@ -108,47 +131,53 @@ void EpaperDisplay::updateInsertedAmount(const char* amount_in_euro) updateText(amount_in_euro, 3, 20, 75, GxEPD_BLACK, GxEPD_WHITE); } -#if 0 - -void qr_withdrawl_screen_waveshare_2_7(const char* qr_content) +void EpaperDisplay::qrCode(const char* content) { QRCode qrcoded; uint8_t qrcodeData[qrcode_getBufferSize(QR_VERSION)]; // 20 is "qr version" t_qrdata qr; + uint16_t color = GxEPD_BLACK; - qrcode_initText(&qrcoded, qrcodeData, QR_VERSION, 0, qr_content); - qr.qr_size = qrcoded.size * 3; - qr.start_x = (264 - qr.qr_size) / 2; - qr.start_y = (176 - qr.qr_size) / 2; + qrcode_initText(&qrcoded, qrcodeData, QR_VERSION, 0, content); qr.module_size = 3; + qr.qr_size = qrcoded.size * qr.module_size; + Serial.printf("Qrcode size: %d, qrcoded.size %d\n", qr.qr_size, qrcoded.size); + qr.start_x = (epDisplay->width() - qr.qr_size) / 2; + qr.start_y = (epDisplay->height() - qr.qr_size) / 2; + + epDisplay->setFullWindow(); + epDisplay->firstPage(); - display.setRotation(1); - display.setFullWindow(); - display.firstPage(); - for (qr.current_y = 0; qr.current_y < qrcoded.size; qr.current_y++) + do { - for (qr.current_x = 0; qr.current_x < qrcoded.size; qr.current_x++) + + for (qr.current_y = 0; qr.current_y < qrcoded.size; qr.current_y++) { - if (qrcode_getModule(&qrcoded, qr.current_x, qr.current_y)) - display.fillRect(qr.start_x + qr.module_size * qr.current_x, - qr.start_y + qr.module_size * qr.current_y, qr.module_size, qr.module_size, GxEPD_BLACK); - else - display.fillRect(qr.start_x + qr.module_size * qr.current_x, - qr.start_y + qr.module_size * qr.current_y, qr.module_size, qr.module_size, GxEPD_WHITE); + for (qr.current_x = 0; qr.current_x < qrcoded.size; qr.current_x++) + { + if (qrcode_getModule(&qrcoded, qr.current_x, qr.current_y)) + { + color = GxEPD_BLACK; + } + else + { + color = GxEPD_WHITE; + } + + epDisplay->fillRect(qr.start_x + (qr.module_size * qr.current_x), + qr.start_y + (qr.module_size * qr.current_y), qr.module_size, qr.module_size, color); + } } - } - display.setCursor(11, 5); - display.setTextSize(2); - display.setTextColor(GxEPD_BLACK, GxEPD_WHITE); - display.println("Please scan QR code:"); // top message - display.setCursor(11, 155); - display.setTextSize(2); - display.setTextColor(GxEPD_BLACK, GxEPD_WHITE); - display.println("Reset - press button"); // bottom message + uint16_t radius_px = 5; + epDisplay->drawRoundRect(qr.start_x - radius_px , qr.start_y - radius_px, qr.qr_size+(2*radius_px), qr.qr_size+(2*radius_px), radius_px, GxEPD_BLACK); - display.nextPage(); - display.hibernate(); + drawXCenteredText("Please scan the QR code!\n", 2, 10, GxEPD_BLACK, GxEPD_WHITE ); + drawXCenteredText("This will transfer the paid value to your wallet.\n", 1, 30, GxEPD_BLACK, GxEPD_WHITE ); + drawXCenteredText("Reset - press button\n", 2, epDisplay->height() - 20, GxEPD_BLACK, GxEPD_WHITE ); + + /* code */ + } while (epDisplay->nextPage()); + epDisplay->hibernate(); } -#endif \ No newline at end of file diff --git a/firmware/src/lnurlutil.cpp b/firmware/src/lnurlutil.cpp new file mode 100644 index 0000000..0dad936 --- /dev/null +++ b/firmware/src/lnurlutil.cpp @@ -0,0 +1,89 @@ +#include "lnurlutil.h" +#include "config.h" + +namespace { + +const uint8_t FIATPRECESION = 2; +const char* FIATCURRENCY = "EUR"; + + Lnurl::SignerConfig getLnurlSignerConfig() { + struct Lnurl::SignerConfig lnurl; + lnurl.apiKey.id = "86f6d045d26e4e39"; + lnurl.apiKey.key = "b2ff678c2c634868b70bf678948e92c4382b1e8f7c651bd648b0caefd5cca613"; + lnurl.apiKey.encoding = "hex"; + lnurl.callbackUrl = "https://lnbits.makerlab-murnau.de/bleskomat/u"; + lnurl.shorten = true; + return lnurl; + } + + std::string generate_nonce() { + std::ostringstream ss; + uint32_t entropy = esp_random(); + // Random numbers generated by esp_random are probably sufficient. + // But just to be safe let's append the current time in microseconds as well. + auto extraEntropyFromTime = std::ceil((std::chrono::steady_clock::now().time_since_epoch().count() / 1000) % (UINT32_MAX / 10)); + entropy += extraEntropyFromTime; + ss << entropy; + return ss.str(); + } +} + +namespace lnurlutil { + + std::string createQrContent (const double accumulatedValue) { + const std::string signedUrl = createSignedLnurlWithdraw(accumulatedValue); + const std::string encoded = lnurlEncode(signedUrl); + std::string qrcodeData = ""; + qrcodeData += toUpperCase(encoded); + return qrcodeData; + } + + std::string createSignedLnurlWithdraw(const double &t_amount) { + Lnurl::Signer signer(getLnurlSignerConfig()); + std::string nonce = generate_nonce(); + Lnurl::WithdrawParams params; + std::string amount = floatToStringWithPrecision(t_amount, FIATPRECESION); + params.minWithdrawable = amount; + params.maxWithdrawable = amount; + params.defaultDescription = ""; + params.custom["f"] = FIATCURRENCY; + return signer.create_url(params, nonce); + } + + std::string lnurlEncode(const std::string &text) { + return Lnurl::encode(text); + } + + std::string toUpperCase(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return std::toupper(c); }); + return s; + } + + std::string urlEncode(const std::string &value) { + std::ostringstream escaped; + escaped.fill('0'); + escaped << std::hex; + for (std::string::const_iterator i = value.begin(), n = value.end(); i != n; ++i) { + std::string::value_type c = (*i); + // Keep alphanumeric and other accepted characters intact + if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { + escaped << c; + continue; + } + // Any other characters are percent-encoded + escaped << std::uppercase; + escaped << '%' << std::setw(2) << int((unsigned char) c); + escaped << std::nouppercase; + } + return escaped.str(); + } + + std::string floatToStringWithPrecision(const float &value, const unsigned short &precision) { + std::ostringstream out; + out.precision(precision); + out << std::fixed << value; + return out.str(); + } +} + + diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index b48b84a..5b04ec5 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -1,24 +1,26 @@ - - #include "main.h" +#include #include "epaperdisplay.h" #include "button.h" #include "coinacceptor.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); -/*------------*/ - -String baseURLATM, secretATM, currencyATM; - void setup() { Serial.begin(115200); cacc.begin(); button.begin(); epDisp.init(); + std::string lnurl = lnurlutil::createQrContent(0.05); + epDisp.qrCode(lnurl.c_str()); + while(0==0) + {delay(1000);} + epDisp.homeScreen(); while(0 == 0) @@ -31,192 +33,11 @@ void setup() delay(1000); } - baseURLATM = getValue(lnurlDeviceString, ',', 0); // setup wallet data from string - secretATM = getValue(lnurlDeviceString, ',', 1); - currencyATM = getValue(lnurlDeviceString, ',', 2); - while(0==0) {delay(1000);} } void loop() { -#if 0 - pulses = 0; - pulses = detect_coin(); // detect_coin() is a loop to detect the input of coins, will return the amount of pulses - if (pulses >= 2 && pulses <= 9) - { - digitalWrite(MOSFET_PIN, HIGH); - digitalWrite(LED_BUTTON_PIN, LOW); - inserted_cents += COINS[pulses]; - show_inserted_amount(inserted_cents); - } - else if (button_pressed && inserted_cents > 0) - { - digitalWrite(MOSFET_PIN, HIGH); - button_pressed = false; - char* lnurl = makeLNURL(inserted_cents); - qr_withdrawl_screen(lnurl); - free(lnurl); - wait_for_user_to_scan(); - digitalWrite(LED_BUTTON_PIN, HIGH); - home_screen(); - digitalWrite(MOSFET_PIN, LOW); - inserted_cents = 0; - } - else if (button_pressed && !pulses && !inserted_cents) // to clean the screen (for storage), press the button several times - { - int press_counter = 0; - - button_pressed = false; - time_last_press = millis(); - while ((millis() - time_last_press) < 4000 && press_counter < 6) - { - if (button_pressed) - { - if (DEBUG_MODE) - Serial.println("Button pressed"); - time_last_press = millis(); - button_pressed = false; - press_counter++; - delay(500); - } - } - if (press_counter > 5) - { - if (DEBUG_MODE) - Serial.println("Button pressed over 5 times, will clean screen..."); - digitalWrite(LED_BUTTON_PIN, LOW); - clean_screen(); - display_sleep(); - delay(30000); - home_screen(); - } - } -#endif } - - - -//////////////////////////////////////////// -///////////////LNURL STUFF////////////////// -////USING STEPAN SNIGREVS GREAT CRYTPO////// -////////////THANK YOU STEPAN//////////////// -//////////////////////////////////////////// - -int xor_encrypt(uint8_t* output, size_t outlen, uint8_t* key, size_t keylen, uint8_t* nonce, size_t nonce_len, uint64_t pin, uint64_t amount_in_cents) -{ - // check we have space for all the data: - // - if (outlen < 2 + nonce_len + 1 + lenVarInt(pin) + 1 + lenVarInt(amount_in_cents) + 8) - { - return 0; - } - - int cur = 0; - output[cur] = 1; // variant: XOR encryption - cur++; - - // nonce_len | nonce - output[cur] = nonce_len; - cur++; - memcpy(output + cur, nonce, nonce_len); - cur += nonce_len; - - // payload, unxored first - - int payload_len = lenVarInt(pin) + 1 + lenVarInt(amount_in_cents); - output[cur] = (uint8_t)payload_len; - cur++; - uint8_t* payload = output + cur; // pointer to the start of the payload - cur += writeVarInt(pin, output + cur, outlen - cur); // pin code - cur += writeVarInt(amount_in_cents, output + cur, outlen - cur); // amount - cur++; - - // xor it with round key - uint8_t hmacresult[32]; - SHA256 h; - h.beginHMAC(key, keylen); - h.write((uint8_t*)"Round secret:", 13); - h.write(nonce, nonce_len); - h.endHMAC(hmacresult); - for (int i = 0; i < payload_len; i++) - { - payload[i] = payload[i] ^ hmacresult[i]; - } - - // add hmac to authenticate - h.beginHMAC(key, keylen); - h.write((uint8_t*)"Data:", 5); - h.write(output, cur); - h.endHMAC(hmacresult); - memcpy(output + cur, hmacresult, 8); - cur += 8; - - // return number of bytes written to the output - return cur; -} - -char* makeLNURL(float total) -{ - int randomPin = random(1000, 9999); - byte nonce[8]; - for (int i = 0; i < 8; i++) - { - nonce[i] = random(256); - } - byte payload[51]; // 51 bytes is max one can get with xor-encryption - size_t payload_len = xor_encrypt(payload, sizeof(payload), (uint8_t*)secretATM.c_str(), secretATM.length(), nonce, sizeof(nonce), randomPin, float(total)); - String preparedURL = baseURLATM + "?atm=1&p="; - preparedURL += toBase64(payload, payload_len, BASE64_URLSAFE | BASE64_NOPADDING); - if (DEBUG_MODE) - Serial.println(preparedURL); - char Buf[200]; - preparedURL.toCharArray(Buf, 200); - char* url = Buf; - byte* data = (byte*)calloc(strlen(url) * 2, sizeof(byte)); - if (!data) - return (NULL); - size_t len = 0; - int res = convert_bits(data, &len, 5, (byte*)url, strlen(url), 8, 1); - char* charLnurl = (char*)calloc(strlen(url) * 2, sizeof(byte)); - if (!charLnurl) - { - free(data); - return (NULL); - } - bech32_encode(charLnurl, "lnurl", data, len); - to_upper(charLnurl); - free(data); - return (charLnurl); -} - -void to_upper(char* arr) -{ - for (size_t i = 0; i < strlen(arr); i++) - { - if (arr[i] >= 'a' && arr[i] <= 'z') - { - arr[i] = arr[i] - 'a' + 'A'; - } - } -} - -// Function to seperate the LNURLDevice string in key, url and currency -String getValue(const String data, char separator, int index) -{ - int found = 0; - int strIndex[] = { 0, -1 }; - const 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]) : ""; -}