diff --git a/app.py b/app.py index 006da15..08f5bff 100755 --- a/app.py +++ b/app.py @@ -27,11 +27,15 @@ def softreset(): """Displays startup screen and deletes fiat amount """ global led + # Inform about coin, bill and sat amounts + if config.COINCOUNT > 0: + logger.info("Last payment:") + logger.info("%s Bill(s), %s Sats", config.COINCOUNT, config.SATS) + config.SATS = 0 config.FIAT = 0 - if config.COINCOUNT > 0: - logger.info("%s Coin(s) and XX Bill(s) added", config.COINCOUNT) config.COINCOUNT = 0 + config.PUSHES = 0 # Turn off button LED GPIO.output(13, GPIO.LOW) led = "off" @@ -57,21 +61,127 @@ def coin_event(channel): def button_pushed(): """Actions button pushes by number """ + logger.info("Button pushed %s time(s).", str(config.PUSHES)) if config.PUSHES == 1: """If no coins inserted, update the screen. If coins inserted, scan a qr code for the exchange amount """ - if config.FIAT == 0: - display.update_nocoin_screen() - time.sleep(3) - display.update_startup_screen() if not config.conf["atm"]["activewallet"]: logger.error("No wallet has been configured for the ATM.") + logger.error("Please configure your Lightning Wallet first.") + # Add "no wallet setup" message + # Softreset and startup screen softreset() + return - if config.conf["atm"]["activewallet"] == "btcpay_lnd": + if config.FIAT == 0: + logger.info("No bills inserted.") + display.update_nocoin_screen() + softreset() + return + + lnurlproxy = config.conf["lnurl"]["lnurlproxy"] + activewallet = config.conf["atm"]["activewallet"] + # Determine if LNURL Withdrawls are possible + if lnurlproxy == "active" or activewallet == "lntxbot": + # 1. Ask if wallet supports LNURL + # 2. Offer to cancel and switch to normal scan + # 3. Process payment + if activewallet == "lntxbot": + display.update_lnurl_cancel_notice() + if config.PUSHES == 1: + # Process LNURL + logger.info("LNURL process stared") + lntxbot.process_using_lnurl(config.SATS) + softreset() + return + if config.PUSHES > 1: + # Process QR code scan + logger.info("QR scan process started") + display.update_qr_request() + config.INVOICE = qr.scan_attempts(config.MAXSCAN) + logger.info("INVOICE after scanning: %s", config.INVOICE) + if not config.INVOICE or config.INVOICE is (None or ""): + if config.RESCAN == 0: + # rescan the wallet QR + config.RESCAN = 1 + print("QR rescan process started") + logger.info("QR rescan process started") + display.update_qr_request() + config.INVOICE = qr.scan_attempts(config.MAXSCAN) + if not config.INVOICE or config.INVOICE is (None or ""): + # showing QR to scan after 2 failed scan + lntxbot.process_lnurl_directly(config.SATS, config.TIMEOUT) + else: + # payout if QR detected + display.update_payout_screen() + if str(config.INVOICE).find("LNURL") == 0: + pr = lntxbot.convert_ln(math.floor(config.SATS) * 1000, config.INVOICE) + lntxbot.payout(config.SATS, pr) + else: + lntxbot.payout(config.SATS, config.INVOICE) + else: + # payment failed + display.update_payment_failed() + # reset the flag + config.RESCAN = 0 + else: + display.update_payout_screen() + if str(config.INVOICE).find("LNURL") == 0: + pr = lntxbot.convert_ln(math.floor(config.SATS) * 1000, config.INVOICE) + lntxbot.payout(config.SATS, pr) + else: + lntxbot.payout(config.SATS, config.INVOICE) + softreset() + return + + if lnurlproxy == "active": + display.update_lnurl_cancel_notice() + if config.PUSHES == 1: + # Process LNURL + # Only implemented for LND BTCPay so far + import requests, json, qrcode + + request_url = config.conf["lnurl"]["lnurlproxyurl"] + data = {"amount": config.SATS} + + response = requests.post(request_url, json=data) + + qr_img = utils.generate_lnurl_qr(response.json()["lnurl"]) + # TODO Adjust size according to screen used + qr_img = qr_img.resize((122, 122), resample=0) + + # draw the qr code on the e-ink screen + display.draw_lnurl_qr(qr_img) + invoice = requests.get(response.json()["callback"]) + + config.INVOICE = invoice.json()["invoice"] + lndrest.handle_invoice() + softreset() + return + if config.PUSHES > 1: + # Process QR code scan + # Only implemented for LND BTCPay so far + display.update_qr_request() + qrcode = qr.scan() + config.INVOICE = lndrest.evaluate_scan(qrcode) + while config.INVOICE is False: + display.update_qr_failed() + time.sleep(1) + display.update_qr_request() + qrcode = qr.scan() + config.INVOICE = lndrest.evaluate_scan(qrcode) + display.update_payout_screen() + lndrest.handle_invoice() + softreset() + return + + elif activewallet == "btcpay_lnd": + # Process QR code scan + # Only implemented for LND BTCPay so far + logger.info("No option for LNURL. Continue with scan...") display.update_qr_request() qrcode = qr.scan() config.INVOICE = lndrest.evaluate_scan(qrcode) @@ -84,42 +194,33 @@ def button_pushed(): display.update_payout_screen() lndrest.handle_invoice() softreset() - elif config.conf["atm"]["activewallet"] == "lntxbot": - lntxbot.process_using_lnurl(config.SATS) - # Softreset and startup screen - softreset() - # lntxbot.payout(config.SATS, config.INVOICE) + return else: - pass + logger.error("No valid wallet configured") if config.PUSHES == 3: - """Scan and store new wallet credentials + """Show the exchange rate """ - # Delete current wallet flag and credentials - config.update_config("atm", "activewallet", "") - config.update_config("lntxbot", "creds", "") - config.update_config("lnd", "macaroon", "") - - display.update_wallet_scan() - qr.scan_credentials() - importlib.reload(config) - - if config.conf["atm"]["activewallet"] == "btcpay_lnd": - display.update_btcpay_lnd() - elif config.conf["atm"]["activewallet"] == "lntxbot": - balance = lntxbot.get_lnurl_balance() - display.update_lntxbot_balance(balance) - else: - logger.error("Saving of wallet credentials failed.") - - softreset() + logger.info("Show the exchange rate.") + logger.info("BTC PRICE: " + str(config.BTCPRICE)) + display.show_rate_screen() + display.update_startup_screen() if config.PUSHES == 4: - """Simulates adding a coin (for testing) + """Stop the app """ - logger.info("Button pushed four times (add coin)") - print("Button pushed four times (add coin)") - config.PULSES = 2 + logger.info("Application is stopping by 4 times button.") + display.update_shutdown_screen() + GPIO.cleanup() + sys.exit("Manually interrupted by 4 times button.") + + if config.PUSHES == 5: + """Reboot the ATM + """ + display.update_restart_screen() + GPIO.cleanup() + logger.warning("ATM reboots (5 times button)") + os.system("sudo reboot") if config.PUSHES == 6: """Shutdown the host machine @@ -128,86 +229,70 @@ def button_pushed(): GPIO.cleanup() logger.warning("ATM shutdown (6 times button)") os.system("sudo shutdown -h now") - config.PUSHES = 0 - - # # Future function to make use of LNURLProxyServer - # if config.PUSHES == 6: - # import requests, json, qrcode - # - # request_url = "https://api.lnurlproxy.me/v1/lnurl" - # data = {"amount": config.SATS} - # - # response = requests.post(request_url, json=data) - # - # qr_img = lntxbot.generate_lnurl_qr(response.json()["lnurl"]) - # qr_img = qr_img.resize((96, 96), resample=0) - # - # # draw the qr code on the e-ink screen - # display.draw_lnurl_qr(qr_img) - # invoice = requests.get(response.json()["callback"]) - # - # config.INVOICE = invoice.json()["invoice"] - # lndrest.handle_invoice() - # softreset() + config.PUSHES = 0 def coins_inserted(): """Actions coins inserted """ global led + logger.info("Bills inserted. Current PULSES: %s", str(config.PULSES)) + if config.FIAT == 0: - config.BTCPRICE = utils.get_btc_price(config.conf["atm"]["cur"]) - config.SATPRICE = math.floor((1 / (config.BTCPRICE * 100)) * 100000000) + config.BTCPRICE = math.floor(config.MARKUP * utils.get_btc_price(config.conf["atm"]["cur"])) + config.SATPRICE = 1000000 / config.BTCPRICE logger.info("Satoshi price updated") + logger.info("BTCPRICE: %s, SATPRICE: %s", str(config.BTCPRICE), str(config.SATPRICE)) + if config.PULSES == 2: - config.FIAT += 0.02 + config.FIAT += 10000 config.COINCOUNT += 1 config.SATS = utils.get_sats() config.SATSFEE = utils.get_sats_with_fee() config.SATS -= config.SATSFEE - logger.info("2 cents added") + logger.info("10,000 VND added") display.update_amount_screen() if config.PULSES == 3: - config.FIAT += 0.05 + config.FIAT += 20000 config.COINCOUNT += 1 config.SATS = utils.get_sats() config.SATSFEE = utils.get_sats_with_fee() config.SATS -= config.SATSFEE - logger.info("5 cents added") + logger.info("20,000 VND added") display.update_amount_screen() if config.PULSES == 4: - config.FIAT += 0.1 + config.FIAT += 50000 config.COINCOUNT += 1 config.SATS = utils.get_sats() config.SATSFEE = utils.get_sats_with_fee() config.SATS -= config.SATSFEE - logger.info("10 cents added") + logger.info("50,000 VND added") display.update_amount_screen() if config.PULSES == 5: - config.FIAT += 0.2 + config.FIAT += 100000 config.COINCOUNT += 1 config.SATS = utils.get_sats() config.SATSFEE = utils.get_sats_with_fee() config.SATS -= config.SATSFEE - logger.info("20 cents added") + logger.info("100,000 VND added") display.update_amount_screen() if config.PULSES == 6: - config.FIAT += 0.5 + config.FIAT += 200000 config.COINCOUNT += 1 config.SATS = utils.get_sats() config.SATSFEE = utils.get_sats_with_fee() config.SATS -= config.SATSFEE - logger.info("50 cents added") + logger.info("200,000 VND added") display.update_amount_screen() if config.PULSES == 7: - config.FIAT += 1 + config.FIAT += 500000 config.COINCOUNT += 1 config.SATS = utils.get_sats() config.SATS = utils.get_sats() config.SATSFEE = utils.get_sats_with_fee() - logger.info("100 cents added") + logger.info("500,000 VND added") display.update_amount_screen() config.PULSES = 0 @@ -256,6 +341,7 @@ def setup_coin_acceptor(): """Initialises the coin acceptor parameters and sets up a callback for button pushes and coin inserts. """ + logger.info("Setting up the bill acceptor.") # Defining GPIO BCM Mode GPIO.setmode(GPIO.BCM) diff --git a/config.py b/config.py index 1565939..243539b 100644 --- a/config.py +++ b/config.py @@ -1,8 +1,9 @@ from configparser import ConfigParser +from shutil import copyfile import logging -import os import math -from shutil import copyfile +import sys +import os import utils @@ -31,11 +32,9 @@ # Create logger for this config file logger = logging.getLogger("CONFIG") - yes = ["yes", "ye", "y"] no = ["no", "n"] - def ask_scan_config_val(section, variable): while True: try: @@ -117,15 +116,44 @@ def create_config(config_file=None): BLACK = 0 PAPIRUSROT = 0 if "papirus" in conf["atm"]["display"]: - from papirus import Papirus - - PAPIRUS = Papirus(rotation=PAPIRUSROT) + try: + from papirus import Papirus + PAPIRUS = Papirus(rotation=PAPIRUSROT) + except ImportError: + logger.warning("Papirus display library not installed.") + sys.exit("Exiting...") # Display - Waveshare 2.13 is 250 * 122 pixels -if "waveshare" in conf["atm"]["display"]: - from waveshare_epd import epd2in13_V2 - - WAVESHARE = epd2in13_V2.EPD() +elif "waveshare2in13" in conf["atm"]["display"]: + try: + from waveshare_epd import epd2in13_V2 + WAVESHARE = epd2in13_V2.EPD() + except ImportError: + logger.warning("Waveshare display library not installed.") + sys.exit("Exiting...") + +# Display - Waveshare 2.13 (D) is 212 * 104 pixels +elif "waveshare2in13d" in conf["atm"]["display"]: + try: + from waveshare_epd import epd2in13d + WAVESHARE = epd2in13d.EPD() + except ImportError: + logger.warning("Waveshare display library not installed.") + sys.exit("Exiting...") + +# Display - Waveshare 6 HD is 1448 * 1072 pixels +elif "waveshare6in" in conf["atm"]["display"]: + try: + from IT8951.display import AutoEPDDisplay + WAVESHARE = AutoEPDDisplay(vcom=-2.44, rotate=None, spi_hz=24000000) + except ImportError: + logger.warning("Waveshare display library not installed.") + sys.exit("Exiting...") + +# Display - No configuration match +else: + logger.warning("No display configuration match.") + sys.exit("Exiting...") # API URL for coingecko COINGECKO_URL_BASE = "https://api.coingecko.com/api/v3/" @@ -135,10 +163,18 @@ def create_config(config_file=None): SATS = 0 SATSFEE = 0 INVOICE = "" +# This markup will make the rate in VN nearer the market +MARKUP = 1.08 +# Flag for rescan the payout wallet +RESCAN = 0 +# Limit number of the scan attempts +MAXSCAN = 4 +# Time out for the payment +TIMEOUT = 60 # Set btc and sat price -BTCPRICE = utils.get_btc_price(conf["atm"]["cur"]) -SATPRICE = math.floor((1 / (BTCPRICE * 100)) * 100000000) +BTCPRICE = math.floor(utils.get_btc_price(conf["atm"]["cur"]) * MARKUP) +SATPRICE = ((1 / (BTCPRICE * 100)) * 100000000) # Button / Acceptor Pulses LASTIMPULSE = 0 diff --git a/displays/messages.py b/displays/messages.py index ac64319..3340212 100644 --- a/displays/messages.py +++ b/displays/messages.py @@ -1,7 +1,23 @@ +# Text for show_rate_screen() +rate_screen_1 = "BITCOIN RATE" +rate_screen_2 = "1 BTC=" +rate_screen_3 = "1 sat=" +rate_screen_4 = "TỶ GIÁ BITCOIN" + +# Text for update_restart_screen() +restart_screen_1 = "ATM rebooting" +restart_screen_2 = "Please wait for" +restart_screen_3 = "some seconds." +restart_screen_4 = "ATM đang khởi động" +restart_screen_5 = "Vui lòng đợi" +restart_screen_6 = "một chút." + # Text for update_startup_screen() startup_screen_1 = "Welcome to the" startup_screen_2 = "LightningATM" -startup_screen_3 = "- please insert coins -" +startup_screen_3 = "- please insert bills -" +startup_screen_4 = "Chào mừng đến với" +startup_screen_5 = "- hãy đưa tiền VNĐ vào -" # Text for update_qr_request() qr_request_1 = "Please scan" @@ -9,64 +25,106 @@ qr_request_3 = "Scanning..." qr_request_4 = "for " qr_request_5 = " sats." +qr_request_1_vi = "Vui lòng quét" +qr_request_2_vi = "hóa đơn sau" +qr_request_3_vi = "Đang quét..." +qr_request_4_vi = "cho " +qr_request_5_vi = " sats." # Text for update_qr_failed() qr_failed_1 = "Scan failed." qr_failed_2 = "Try again." +qr_failed_3 = "Quét không thành công." +qr_failed_4 = "Hãy thử lại." # Text for update_payout_screen() payout_screen_1 = " sats" payout_screen_2 = "on the way!" +payout_screen_3 = "đang đến!" # text for update_payment_failed() payment_failed_1 = "Payment failed!" payment_failed_2 = "Please contact" -payment_failed_3 = "operator." +payment_failed_3 = "Future.Travel" +payment_failed_1_vi = "Thanh toán thất bại!" +payment_failed_2_vi = "Vui lòng liên hệ" +payment_failed_3_vi = "Future.Travel" # Text for update_thankyou_screen() thankyou_screen_1 = "Enjoy your new" -thankyou_screen_2 = "satoshis!!" -thankyou_screen_3 = "#bitcoin #lightning" +thankyou_screen_2 = "satoshis!" +thankyou_screen_3 = "#Future.Travel" +thankyou_screen_4 = "Giao dịch hoàn tất" +thankyou_screen_5 = "Chúc vui vẻ!" # Text for update_nocoin_screen() -nocoin_screen_1 = "No coins added!" +nocoin_screen_1 = "No bills added!" nocoin_screen_2 = "Please add" -nocoin_screen_3 = "coins first" +nocoin_screen_3 = "bills first" +nocoin_screen_4 = "Chưa có tiền!" +nocoin_screen_5 = "Vui lòng đưa" +nocoin_screen_6 = "tiền vào." # Text for update_lnurl_generation() lnurl_generation_1 = "Generating" lnurl_generation_2 = "QR code to scan" +lnurl_generation_3 = "Đang tạo" +lnurl_generation_4 = "mã QR để quét." # Text for update_shutdown_screen() shutdown_screen_1 = "ATM turned off!" shutdown_screen_2 = "Please contact" -shutdown_screen_3 = "operator." +shutdown_screen_3 = "Future.Travel" +shutdown_screen_4 = "ATM đã tắt!" +shutdown_screen_5 = "Vui lòng liên hệ" +shutdown_screen_6 = "Future.Travel" # Text for update_wallet_scan() wallet_scan_1 = "Please scan" wallet_scan_2 = "your wallet" wallet_scan_3 = "credentials." +wallet_scan_4 = "Vui lòng quét" +wallet_scan_5 = "định danh" +wallet_scan_6 = "ví của bạn." # Text for update_lntxbot_balance() lntxbot_balance_1 = "Success!!" lntxbot_balance_2 = "Your current balance:" lntxbot_balance_3 = " sats" +lntxbot_balance_4 = "Thành công!!" +lntxbot_balance_5 = "Số dư hiện tại:" # Text for update_btcpay_lnd() btcpay_lnd_1 = "Success!!" btcpay_lnd_2 = "Successfuly scanned" btcpay_lnd_3 = "BTCPay LND Wallet." +btcpay_lnd_4 = "Thành công!!" +btcpay_lnd_5 = "Quét thành công" +btcpay_lnd_6 = "Ví BTCPay LND." # Text for draw_lnurl_qr() lnurl_qr_1 = "Scan to" -lnurl_qr_2 = "receive" +lnurl_qr_2 = "receive." +lnurl_qr_3 = "Quét để" +lnurl_qr_4 = "nhận." # Text for update_amount_screen() amount_screen_1 = " sats" -amount_screen_2 = "Rate" -amount_screen_3 = "= " -amount_screen_4 = " sats/" -amount_screen_5 = "Fee" +amount_screen_2 = "Rate/Tỷ giá:" +amount_screen_3 = ": " +#amount_screen_3 = "= " +amount_screen_4 = " = " +#amount_screen_4 = " sats/" +amount_screen_5 = "Fee/Phí:" amount_screen_6 = "= " -amount_screen_7 = "% (" +amount_screen_7 = "% = " amount_screen_8 = " sats)" + +# Text for update_lnurl_cancel_notice +lnurl_cancel_notice_1 = "Generate QR to scan, or" +lnurl_cancel_notice_1_vi = "Chuẩn bị tạo mã QR, hoặc" +lnurl_cancel_notice_2 = "Press button to scan" +lnurl_cancel_notice_2_vi = "Bấm nút nếu bạn" +lnurl_cancel_notice_3 = "your wallet/ invoice's QR." +lnurl_cancel_notice_3_vi = "muốn quét QR ví của bạn." + diff --git a/displays/messages6in.py b/displays/messages6in.py new file mode 100644 index 0000000..4faa98f --- /dev/null +++ b/displays/messages6in.py @@ -0,0 +1,128 @@ +# Text for show_rate_screen() +rate_screen_title_en = "BITCOIN RATE" +rate_screen_1btc = "1 BTC=" +rate_screen_1sat = "1 sat=" +rate_screen_title_vi = "TỶ GIÁ BITCOIN" +rate_screen_expire_en = "Back to welcome screen in:" +rate_screen_expire_vi = "Trở về màn hình chính sau:" + +# Text for update_restart_screen() +restart_screen_title_en = "ATM rebooting" +restart_screen_desc_en_1 = "Please wait for" +restart_screen_desc_en_2 = "some seconds." +restart_screen_title_vi = "ATM đang khởi động" +restart_screen_desc_vi_1 = "Vui lòng đợi" +restart_screen_desc_vi_2 = "một chút." + +# Text for update_startup_screen() +startup_screen_welcome_en = "Welcome to the" +startup_screen_atm = "LightningATM" +startup_screen_insert_bills_en = "- please insert VND bills -" +startup_screen_welcome_vi = "Chào mừng đến với" +startup_screen_insert_bills_vi = "- hãy đưa tiền VNĐ vào -" + +# Text for update_qr_request() +qr_request_title_en = "Please scan" +qr_request_title_en_2 = "your invoice/ credentials:" +qr_request_scanning_en = "Scanning for your QR code ..." +qr_request_for_n_sats_en = "for {0} sats." +qr_request_title_vi = "Vui lòng quét hóa đơn/ ví:" +qr_request_scanning_vi = "Đang quét mã QR ..." +qr_request_for_n_sats_vi = "cho {0} sats." + +# Text for update_qr_failed() +qr_failed_title_en = "Scan failed" +qr_failed_desc_en = "Please try again!" +qr_failed_title_vi = "Quét không thành công" +qr_failed_desc_vi = "Xin hãy thử lại!" + +# Text for update_payout_screen() +payout_screen_title_en = "Processing" +payout_screen_title_en_2 = "for {0} sats ..." +payout_screen_desc_en = "on the way!" +payout_screen_title_vi = "Đang xử lý" +payout_screen_title_vi_2 = "cho {0} sats ..." +payout_screen_desc_vi = "đang đến!" + +# text for update_payment_failed() +payment_failed_title_en = "Payment failed!" +payment_failed_desc_en = "Please contact" +payment_failed_ft = "Future.Travel" +payment_failed_title_vi = "Thanh toán thất bại!" +payment_failed_desc_vi = "Vui lòng liên hệ" +payment_failed_timeout_en = "Back to welcome screen in:" +payment_failed_timeout_vi = "Trở về màn hình chính sau:" + +# Text for update_thankyou_screen() +thankyou_screen_title_en = "Payment successful" +thankyou_screen_desc_en = "Enjoy your new satoshis!" +thankyou_screen_title_vi = "Giao dịch hoàn tất" +thankyou_screen_desc_vi = "Chúc vui vẻ!" +thankyou_screen_expire_en = "Back to welcome screen in:" +thankyou_screen_expire_vi = "Trở về màn hình chính sau:" + +# Text for update_nocoin_screen() +nocoin_screen_title_en = "No bills added!" +nocoin_screen_desc_en = "Please add bills first" +nocoin_screen_support_bills_en = "Bills accepted:" +nocoin_screen_title_vi = "Chưa có tiền!" +nocoin_screen_desc_vi = "Vui lòng đưa tiền vào." +nocoin_screen_support_bills_vi = "Mệnh giá được chấp nhận:" + +# Text for update_lnurl_generation() +lnurl_generation_en = "Generating QR code to scan..." +lnurl_generation_vi = "Đang tạo mã QR để quét..." + +# Text for update_shutdown_screen() +shutdown_screen_title_en = "ATM is turned off!" +shutdown_screen_desc_en = "Please contact" +shutdown_screen_ft = "Future.Travel" +shutdown_screen_title_vi = "ATM đã tắt!" +shutdown_screen_desc_vi = "Vui lòng liên hệ" + +# Text for update_wallet_scan() +wallet_scan_en = "Please scan QR code of your wallet credentials." +wallet_scan_vi = "Vui lòng quét mã QR ví của bạn." + +# Text for update_lntxbot_balance() +lntxbot_balance_title_en = "Success!!" +lntxbot_balance_desc_en = "Your current balance:" +lntxbot_balance_n_sats = "{} sats" +lntxbot_balance_title_vi = "Thành công!!" +lntxbot_balance_desc_vi = "Số dư hiện tại:" + +# Text for update_btcpay_lnd() +btcpay_lnd_title_en = "Success!!" +btcpay_lnd_desc_en = "Successfuly scanned" +btcpay_lnd_wallet_en = "BTCPay LND Wallet." +btcpay_lnd_title_vi = "Thành công!!" +btcpay_lnd_desc_vi = "Quét thành công" +btcpay_lnd_wallet_vi = "Ví BTCPay LND." + +# Text for draw_lnurl_qr() +lnurl_qr_en = "Scan to receive {0} sats." +lnurl_qr_vi = "Quét để nhận {0} sats." +lnurl_qr_timeout_en = "Time out in:" +lnurl_qr_timeout_vi = "Hết hạn sau:" + +# Text for update_amount_screen() +amount_screen_desc_en_1 = "Insert more bills or" +amount_screen_desc_en_2 = "press the button 1 time to receive the sats." +amount_screen_desc_vi_1 = "Có thể thêm tiền vào, hoặc" +amount_screen_desc_vi_2 = "Bấm nút 1 lần để nhận satoshi." +amount_screen_sats = "sats" +amount_screen_rate = "Rate/ Tỷ giá:" +amount_screen_rate_line = "1 sat = {0}" +amount_screen_fee = "Fee/ Phí:" +amount_screen_fee_line = "{0}% = {1} sats" + +# Text for update_lnurl_cancel_notice +lnurl_cancel_notice_1 = "Generate QR to scan, or" +lnurl_cancel_notice_2 = "Press button to scan" +lnurl_cancel_notice_3 = "your wallet/ invoice's QR." +lnurl_cancel_notice_1_vi = "Chuẩn bị tạo mã QR, hoặc" +lnurl_cancel_notice_2_vi = "Bấm nút nếu bạn" +lnurl_cancel_notice_3_vi = "muốn quét QR ví của bạn." +lnurl_cancel_notice_timeout_en = "Go to next screen in:" +lnurl_cancel_notice_timeout_vi = "Đi tới bước kế tiếp sau:" + diff --git a/displays/waveshare2in7.py b/displays/waveshare2in7.py new file mode 100644 index 0000000..c59de79 --- /dev/null +++ b/displays/waveshare2in7.py @@ -0,0 +1,812 @@ +#!/usr/bin/python3 + +import time +import math + +import config +import utils + +from displays import messages + +from PIL import Image, ImageFont, ImageDraw + +def update_lnurl_cancel_notice(): + image, width, height, draw = init_screen(color=config.WHITE) + # English + draw.text( + (5, 12), + messages.lnurl_cancel_notice_1, + fill=config.BLACK, + font=utils.create_font("sawasdee", 22), + ) + draw.text( + (5, 42), + messages.lnurl_cancel_notice_2, + fill=config.BLACK, + font=utils.create_font("sawasdee", 16), + ) + draw.text( + (4, 62), + messages.lnurl_cancel_notice_3, + fill=config.BLACK, + font=utils.create_font("sawasdee", 16), + ) + # Vietnamese + draw.text( + (5, 90), + messages.lnurl_cancel_notice_1_vi, + fill=config.BLACK, + font=utils.create_font("freemono", 22), + ) + draw.text( + (5, 120), + messages.lnurl_cancel_notice_2_vi, + fill=config.BLACK, + font=utils.create_font("freemono", 16), + ) + draw.text( + (4, 140), + messages.lnurl_cancel_notice_3_vi, + fill=config.BLACK, + font=utils.create_font("freemono", 16), + ) + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + +def show_rate_screen(): + """Show the bitcoin rate + """ + image, width, height, draw = init_screen(color=config.WHITE) + # English + draw.text( + (8, 10), + messages.rate_screen_1, + fill=config.BLACK, + font=utils.create_font("freemonobold", 24) + ) + draw.text( + (8, 40), + messages.rate_screen_2 + + str("{:,}".format(config.BTCPRICE)) + + " " + config.conf["atm"]["cur"].upper(), + fill=config.BLACK, + font=utils.create_font("freemono", 18) + ) + draw.text( + (8, 60), + messages.rate_screen_3 + + str(round(config.BTCPRICE * 0.00000001, 2)) + + " " + config.conf["atm"]["cur"].upper(), + fill=config.BLACK, + font=utils.create_font("freemono", 18) + ) + # Vietnamese + draw.text( + (8, 90), + messages.rate_screen_4, + fill=config.BLACK, + font=utils.create_font("freemonobold", 24) + ) + draw.text( + (8, 120), + messages.rate_screen_2 + + str("{:,}".format(config.BTCPRICE).replace(",", ".")) + " " + + config.conf["atm"]["cur"].upper(), + fill=config.BLACK, + font=utils.create_font("freemono", 18) + ) + draw.text( + (8, 140), + messages.rate_screen_3 + + str("{:,}".format(round(config.BTCPRICE * 0.00000001, 2)).replace(".", ",")) + + " " + + config.conf["atm"]["cur"].upper(), + fill=config.BLACK, + font=utils.create_font("freemono", 18) + ) + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + + +def update_restart_screen(): + """Restart screen on eInk Display + """ + image, width, height, draw = init_screen(color=config.WHITE) + # English + draw.text( + (10, 10), + messages.restart_screen_1, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + draw.text( + (10, 40), + messages.restart_screen_2, + fill=config.BLACK, + font=utils.create_font("freemono", 22), + ) + draw.text( + (10, 60), + messages.restart_screen_3, + fill=config.BLACK, + font=utils.create_font("freemono", 22), + ) + # Vietnamese + draw.text( + (10, 90), + messages.restart_screen_4, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + draw.text( + (10, 120), + messages.restart_screen_5, + fill=config.BLACK, + font=utils.create_font("freemono", 22), + ) + draw.text( + (10, 140), + messages.restart_screen_6, + fill=config.BLACK, + font=utils.create_font("freemono", 22), + ) + + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + +def update_startup_screen(): + """Show startup screen on eInk Display + """ + image, width, height, draw = init_screen(color=config.WHITE) + draw.text( + (10, 10), + messages.startup_screen_1, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + draw.text( + (10, 35), + messages.startup_screen_4, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + draw.text( + (10, 60), + messages.startup_screen_2, + fill=config.BLACK, + font=utils.create_font("sawasdee", 36), + ) + draw.text( + (10, 120), + messages.startup_screen_3, + fill=config.BLACK, + font=utils.create_font("freemono", 16), + ) + draw.text( + (10, 140), + messages.startup_screen_5, + fill=config.BLACK, + font=utils.create_font("freemono", 16), + ) + + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + +def update_qr_request(): + # initially set all white background + image, width, height, draw = init_screen(color=config.WHITE) + draw.rectangle( + (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK + ) + # English + draw.text( + (30, 10), + messages.qr_request_1, + fill=config.BLACK, + font=utils.create_font("sawasdee", 24), + ) + draw.text( + (30, 30), + messages.qr_request_2, + fill=config.BLACK, + font=utils.create_font("sawasdee", 24), + ) + # Vietnamese + draw.text( + (30, 65), + messages.qr_request_1_vi, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + draw.text( + (30, 85), + messages.qr_request_2_vi, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + # Draw the number of countdown + for i in range(0, 3): + draw.text( + (120, 100), + str(3 - i), + fill=config.BLACK, + font=utils.create_font("sawasdee", 55), + ) + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + draw.rectangle((100, 110, width - 4, height - 4), fill=config.WHITE, outline=config.WHITE) + time.sleep(0.5) + draw.rectangle( + (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK + ) + # English + draw.text( + (20, 15), + messages.qr_request_3, + fill=config.BLACK, + font=utils.create_font("sawasdee", 25), + ) + draw.text( + (20, 45), + messages.qr_request_4 + str(math.floor(config.SATS)) + messages.qr_request_5, + fill=config.BLACK, + font=utils.create_font("sawasdee", 25), + ) + # Vietnamese + draw.text( + (20, 90), + messages.qr_request_3_vi, + fill=config.BLACK, + font=utils.create_font("freemono", 25), + ) + draw.text( + (20, 120), + messages.qr_request_4_vi + str(math.floor(config.SATS)) + messages.qr_request_5_vi, + fill=config.BLACK, + font=utils.create_font("freemono", 25), + ) + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + +def update_qr_failed(): + # initially set all white background + image, width, height, draw = init_screen(color=config.WHITE) + draw.rectangle( + (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK + ) + # English + draw.text( + (25, 30), + messages.qr_failed_1, + fill=config.BLACK, + font=utils.create_font("freemono", 28), + ) + draw.text( + (30, 50), + messages.qr_failed_2, + fill=config.BLACK, + font=utils.create_font("freemono", 28), + ) + # Vietnamese + draw.text( + (25,90), + messages.qr_failed_3, + fill=config.BLACK, + font=utils.create_font("freemono", 28), + ) + draw.text( + (30, 110), + messages.qr_failed_4, + fill=config.BLACK, + font=utils.create_font("freemono", 28), + ) + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + +def update_payout_screen(): + """Update the payout screen to reflect balance of deposited coins. + Scan the invoice??? I don't think so! + """ + image, width, height, draw = init_screen(color=config.WHITE) + draw.rectangle( + (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK + ) + # English + draw.text( + (20, 30), + str(math.floor(config.SATS)) + messages.payout_screen_1, + fill=config.BLACK, + font=utils.create_font("freemono", 30), + ) + draw.text( + (20, 60), + messages.payout_screen_2, + fill=config.BLACK, + font=utils.create_font("freemono", 20), + ) + # Vietnamese + draw.text( + (20, 110), + str(math.floor(config.SATS)) + messages.payout_screen_1, + fill=config.BLACK, + font=utils.create_font("freemono", 30), + ) + draw.text( + (20, 140), + messages.payout_screen_3, + fill=config.BLACK, + font=utils.create_font("freemono", 20), + ) + + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + # scan the invoice TODO: I notice this is commented out, I presume this function should _not_ be + # scanning a QR code on each update? config.INVOICE = qr.scan() + +def update_payment_failed(): + image, width, height, draw = init_screen(color=config.WHITE) + # English + draw.text( + (5, 10), + messages.payment_failed_1, + fill=config.BLACK, + font=utils.create_font("freemono", 22), + ) + draw.text( + (5, 40), + messages.payment_failed_2, + fill=config.BLACK, + font=utils.create_font("freemono", 16), + ) + draw.text( + (5, 60), + messages.payment_failed_3, + fill=config.BLACK, + font=utils.create_font("freemono", 16), + ) + # Vietnamese + draw.text( + (5, 90), + messages.payment_failed_1_vi, + fill=config.BLACK, + font=utils.create_font("freemono", 22), + ) + draw.text( + (5, 120), + messages.payment_failed_2_vi, + fill=config.BLACK, + font=utils.create_font("freemono", 16), + ) + draw.text( + (5, 140), + messages.payment_failed_3_vi, + fill=config.BLACK, + font=utils.create_font("freemono", 16), + ) + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + +def update_thankyou_screen(): + image, width, height, draw = init_screen(color=config.WHITE) + # English + draw.text( + (10, 5), + messages.thankyou_screen_1, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + draw.text( + (10, 30), + messages.thankyou_screen_2, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + draw.text( + (10, 60), + messages.thankyou_screen_3, + fill=config.BLACK, + font=utils.create_font("freemono", 18), + ) + # Vietnamese + draw.text( + (10, 90), + messages.thankyou_screen_4, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + draw.text( + (10, 115), + messages.thankyou_screen_5, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + draw.text( + (10, 145), + messages.thankyou_screen_3, + fill=config.BLACK, + font=utils.create_font("freemono", 18), + ) + + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + time.sleep(5) + + +def update_nocoin_screen(): + image, width, height, draw = init_screen(color=config.WHITE) + # English + draw.text( + (20, 10), + messages.nocoin_screen_1, + fill=config.BLACK, + font=utils.create_font("freemonobold", 24), + ) + draw.text( + (50, 40), + messages.nocoin_screen_2, + fill=config.BLACK, + font=utils.create_font("freemono", 22), + ) + draw.text( + (50, 60), + messages.nocoin_screen_3, + fill=config.BLACK, + font=utils.create_font("freemono", 22), + ) + # Vietnamese + draw.text( + (20, 90), + messages.nocoin_screen_4, + fill=config.BLACK, + font=utils.create_font("freemonobold", 24), + ) + draw.text( + (50, 120), + messages.nocoin_screen_5, + fill=config.BLACK, + font=utils.create_font("freemono", 22), + ) + draw.text( + (50, 140), + messages.nocoin_screen_6, + fill=config.BLACK, + font=utils.create_font("freemono", 22), + ) + + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + + +def update_lnurl_generation(): + image, width, height, draw = init_screen(color=config.WHITE) + draw.rectangle( + (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK + ) + # English + draw.text( + (40, 35), + messages.lnurl_generation_1, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + draw.text( + (20, 60), + messages.lnurl_generation_2, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + # Vietnamese + draw.text( + (40, 95), + messages.lnurl_generation_3, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + draw.text( + (20, 120), + messages.lnurl_generation_4, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + +def update_shutdown_screen(): + image, width, height, draw = init_screen(color=config.WHITE) + # English + draw.text( + (20, 10), + messages.shutdown_screen_1, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + draw.text( + (20, 40), + messages.shutdown_screen_2, + fill=config.BLACK, + font=utils.create_font("freemono", 20), + ) + draw.text( + (20, 60), + messages.shutdown_screen_3, + fill=config.BLACK, + font=utils.create_font("freemono", 20), + ) + # Vietnamese + draw.text( + (20, 90), + messages.shutdown_screen_4, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + draw.text( + (20, 120), + messages.shutdown_screen_5, + fill=config.BLACK, + font=utils.create_font("freemono", 20), + ) + draw.text( + (20, 140), + messages.shutdown_screen_6, + fill=config.BLACK, + font=utils.create_font("freemono", 20), + ) + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + +def update_wallet_scan(): + # initially set all white background + image, width, height, draw = init_screen(color=config.WHITE) + draw.rectangle( + (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK + ) + # English + draw.text( + (10, 10), + messages.wallet_scan_1, + fill=config.BLACK, + font=utils.create_font("freemono", 25), + ) + draw.text( + (10, 30), + messages.wallet_scan_2, + fill=config.BLACK, + font=utils.create_font("freemono", 25), + ) + draw.text( + (10, 50), + messages.wallet_scan_3, + fill=config.BLACK, + font=utils.create_font("freemono", 25), + ) + # Vietnamese + draw.text( + (10, 80), + messages.wallet_scan_4, + fill=config.BLACK, + font=utils.create_font("freemono", 25), + ) + draw.text( + (10, 100), + messages.wallet_scan_5, + fill=config.BLACK, + font=utils.create_font("freemono", 25), + ) + draw.text( + (10, 120), + messages.wallet_scan_6, + fill=config.BLACK, + font=utils.create_font("freemono", 25), + ) + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + time.sleep(2) + +def update_lntxbot_balance(balance): + # initially set all white background + image, width, height, draw = init_screen(color=config.WHITE) + draw.rectangle( + (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK + ) + # English + draw.text( + (50, 15), + messages.lntxbot_balance_1, + fill=config.BLACK, + font=utils.create_font("freemonobold", 26), + ) + draw.text( + (10, 45), + messages.lntxbot_balance_2, + fill=config.BLACK, + font=utils.create_font("freemono", 18), + ) + draw.text( + (45, 65), + str("{:,}".format(balance)) + messages.lntxbot_balance_3, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + # Vietnamses + draw.text( + (50, 90), + messages.lntxbot_balance_4, + fill=config.BLACK, + font=utils.create_font("freemonobold", 26), + ) + draw.text( + (10, 125), + messages.lntxbot_balance_5, + fill=config.BLACK, + font=utils.create_font("freemono", 18), + ) + draw.text( + (45, 145), + str("{:,}".format(balance)) + messages.lntxbot_balance_3, + fill=config.BLACK, + font=utils.create_font("freemono", 24), + ) + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + time.sleep(3) + +def update_btcpay_lnd(): + # initially set all white background + image, width, height, draw = init_screen(color=config.WHITE) + draw.rectangle( + (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK + ) + draw.text( + (50, 20), + messages.btcpay_lnd_1, + fill=config.BLACK, + font=utils.create_font("freemonobold", 26), + ) + draw.text( + (10, 55), + messages.btcpay_lnd_2, + fill=config.BLACK, + font=utils.create_font("freemono", 20), + ) + draw.text( + (15, 80), + messages.btcpay_lnd_3, + fill=config.BLACK, + font=utils.create_font("freemono", 20), + ) + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + time.sleep(3) + +def draw_lnurl_qr(qr_img): + """Draw a lnurl qr code on the e-ink screen + """ + image, width, height, draw = init_screen(color=config.WHITE) + qr_img = qr_img.resize((160, 160), resample=0) + draw = ImageDraw.Draw(image) + draw.bitmap((2, 8), qr_img, fill=config.BLACK) + # English + draw.text( + (165, 16), + messages.lnurl_qr_1, + fill=config.BLACK, + font=utils.create_font("freemonobold", 18) + ) + draw.text( + (165, 31), + messages.lnurl_qr_2, + fill=config.BLACK, + font=utils.create_font("freemonobold", 18) + ) + # Show the satoshis + draw.text( + (170, 75), + str("{:,}".format(math.floor(config.SATS))), + fill=config.BLACK, + font=utils.create_font("freemono", 18) + ) + draw.text( + (165, 100), + messages.amount_screen_1, + fill=config.BLACK, + font=utils.create_font("freemono", 18) + ) + # Vietnamese + draw.text( + (165, 131), + messages.lnurl_qr_3, + fill=config.BLACK, + font=utils.create_font("freemonobold", 18) + ) + draw.text( + (165, 146), + messages.lnurl_qr_4, + fill=config.BLACK, + font=utils.create_font("freemonobold", 18) + ) + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + + +def update_amount_screen(): + """Update the amount screen to reflect new coins inserted + """ + image, width, height, draw = init_screen(color=config.WHITE) + draw.rectangle( + (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK + ) + draw.text( + (11, 15), + str("{:,}".format(math.floor(config.SATS))) + messages.amount_screen_1, + fill=config.BLACK, + font=utils.create_font("dotmbold", 30), + ) + draw.text( + (12, 45), + str("{:,}".format(round(config.FIAT, 2))) + " " + config.conf["atm"]["cur"].upper(), + fill=config.BLACK, + font=utils.create_font("dotmbold", 25), + ) + # Rate + draw.text( + (11, 70), + messages.amount_screen_2, + fill=config.BLACK, + font=utils.create_font("freemono", 18), + ) + # Rate: 1 Dong = x sats + draw.text( + (26, 90), + " 1 sat = " + + str(round(config.BTCPRICE / 100000000, 2)) + " " + + config.conf["atm"]["cur"].upper(), + fill=config.BLACK, + font=utils.create_font("freemono", 18), + ) + + #draw.text( + # (60, 70), + # messages.amount_screen_3 + # + str(math.floor(config.SATPRICE)) + # + messages.amount_screen_4 + # + config.conf["atm"]["centname"], + # fill=config.BLACK, + # font=utils.create_font("freemono", 18), + #) + + # Fee + draw.text( + (11, 110), + messages.amount_screen_5, + fill=config.BLACK, + font=utils.create_font("freemono", 18), + ) + draw.text( + (70, 130), + config.conf["atm"]["fee"] + + messages.amount_screen_7 + + str(math.floor(config.SATSFEE)) + + messages.amount_screen_1, + fill=config.BLACK, + font=utils.create_font("freemono", 18), + ) + config.WAVESHARE.init() + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + +def update_blank_screen(): + image, width, height, draw = init_screen(color=config.WHITE) + config.WAVESHARE.init(config.WAVESHARE.FULL_UPDATE) + config.WAVESHARE.display(config.WAVESHARE.getbuffer(image)) + + +def init_screen(color): + """Prepare the screen for drawing and return the draw variables + """ + image = Image.new("1", (config.WAVESHARE.height, config.WAVESHARE.width), color) + # Set width and height of screen + width, height = image.size + # prepare for drawing + draw = ImageDraw.Draw(image) + return image, width, height, draw diff --git a/displays/waveshare6in.py b/displays/waveshare6in.py new file mode 100644 index 0000000..8aee8a2 --- /dev/null +++ b/displays/waveshare6in.py @@ -0,0 +1,984 @@ +#!/usr/bin/python3 +import datetime +import time +import math +import logging +import config +import utils + +#from pathlib import Path +from displays import messages6in + +from PIL import Image, ImageFont, ImageDraw + +from IT8951 import constants + +BLACK = 0x00 +WHITE = 0xFF + +logger = logging.getLogger("WS6INCH") + +def update_lnurl_cancel_notice(): + """ Cancel LNURL generation, should be QR scanning selection + """ + width, height, draw = init_screen() + + # English + draw.text( + (50, 50), + messages6in.lnurl_cancel_notice_1, + fill=BLACK, + font=utils.create_font("freemonobold", 80), + ) + draw.text( + (50, 150), + messages6in.lnurl_cancel_notice_2, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + draw.text( + (50, 250), + messages6in.lnurl_cancel_notice_3, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + # Vietnamese + draw.text( + (50, 400), + messages6in.lnurl_cancel_notice_1_vi, + fill=BLACK, + font=utils.create_font("freemonobold", 80), + ) + draw.text( + (50, 500), + messages6in.lnurl_cancel_notice_2_vi, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + draw.text( + (50, 600), + messages6in.lnurl_cancel_notice_3_vi, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + # Timing out + draw.text( + (50, 900), + messages6in.lnurl_cancel_notice_timeout_en, + fill=BLACK, + font=utils.create_font("freemono", 60) + ) + draw.text( + (50, 950), + messages6in.lnurl_cancel_notice_timeout_vi, + fill=BLACK, + font=utils.create_font("freemono", 60) + ) + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + # show time out + for i in range(10, 0, -1): + draw.ellipse((1030, 910, 1130, 1010), fill=BLACK, outline=BLACK) + draw.text( + (1046, 910), + "0"+ str(i) if i < 10 else str(i), + fill=WHITE, + font=utils.create_font("sawasdeebold", 60) + ) + config.WAVESHARE.draw_partial(constants.DisplayModes.DU) + draw.rectangle((1020, 900, width - 20, height - 20), fill=WHITE, outline=WHITE) + time.sleep(1) + #time.sleep(60) + +def show_rate_screen(): + """Show the bitcoin rate + """ + width, height, draw = init_screen() + + # English + draw.text( + (50, 50), + messages6in.rate_screen_title_en, + fill=BLACK, + font=utils.create_font("freemonobold", 100) + ) + draw.text( + (50, 160), + messages6in.rate_screen_1btc + + str("{:,}".format(config.BTCPRICE)) + " " + config.conf["atm"]["cur"].upper(), + fill=BLACK, + font=utils.create_font("freemono", 80) + ) + draw.text( + (50, 250), + messages6in.rate_screen_1sat + + str(round(config.BTCPRICE * 0.00000001, 2)) + + " " + config.conf["atm"]["cur"].upper(), + fill=BLACK, + font=utils.create_font("freemono", 80) + ) + # Vietnamese + draw.text( + (50, 400), + messages6in.rate_screen_title_vi, + fill=BLACK, + font=utils.create_font("freemonobold", 100) + ) + draw.text( + (50, 510), + messages6in.rate_screen_1btc + + str("{:,}".format(config.BTCPRICE).replace(",", ".")) + " " + + config.conf["atm"]["cur"].upper(), + fill=BLACK, + font=utils.create_font("freemono", 80) + ) + draw.text( + (50, 600), + messages6in.rate_screen_1sat + + str("{:,}".format(round(config.BTCPRICE * 0.00000001, 2)).replace(".", ",")) + + " " + + config.conf["atm"]["cur"].upper(), + fill=BLACK, + font=utils.create_font("freemono", 80) + ) + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + + # show time out + draw.text( + (50, 900), + messages6in.rate_screen_expire_en, + fill=BLACK, + font=utils.create_font("freemono", 60) + ) + draw.text( + (50, 950), + messages6in.rate_screen_expire_vi, + fill=BLACK, + font=utils.create_font("freemono", 60) + ) + config.WAVESHARE.draw_partial(constants.DisplayModes.DU) + + for i in range(10, 0, -1): + draw.ellipse((1030, 910, 1130, 1010), fill=BLACK, outline=BLACK) + draw.text( + (1046, 910), + "0"+ str(i) if i < 10 else str(i), + fill=WHITE, + font=utils.create_font("sawasdeebold", 60), + ) + config.WAVESHARE.draw_partial(constants.DisplayModes.DU) + draw.rectangle((1020, 900, width - 20, height - 20), fill=WHITE, outline=WHITE) + time.sleep(1) + +def update_restart_screen(): + """Restart screen on eInk Display + """ + width, height, draw = init_screen() + + # English + draw.text( + (50, 50), + messages6in.restart_screen_title_en, + fill=BLACK, + font=utils.create_font("freemono", 100), + ) + draw.text( + (50, 150), + messages6in.restart_screen_desc_en_1, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + draw.text( + (50, 250), + messages6in.restart_screen_desc_en_2, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + # Vietnamese + draw.text( + (50, 500), + messages6in.restart_screen_title_vi, + fill=BLACK, + font=utils.create_font("freemono", 100), + ) + draw.text( + (50, 600), + messages6in.restart_screen_desc_vi_1, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + draw.text( + (50, 700), + messages6in.restart_screen_desc_vi_2, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + +def update_startup_screen(): + """Show startup screen on eInk Display + """ + width, height, draw = init_screen() + + # English + draw.text( + (50, 50), + messages6in.startup_screen_welcome_en, + fill=BLACK, + font=utils.create_font("freemono", 120), + ) + # Vietnamese + draw.text( + (50, 200), + messages6in.startup_screen_welcome_vi, + fill=BLACK, + font=utils.create_font("freemono", 120), + ) + # draw logo + draw_logo(draw, 414, 360) + # Text LightningATM + draw.text( + (200, 400), + messages6in.startup_screen_atm, + fill=BLACK, + font=utils.create_font("sawasdeebold", 170), + ) + draw.text( + (50, 700), + messages6in.startup_screen_insert_bills_en, + fill=BLACK, + font=utils.create_font("freemono", 85), + ) + draw.text( + (50, 850), + messages6in.startup_screen_insert_bills_vi, + fill=BLACK, + font=utils.create_font("freemono", 85), + ) + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + +def update_qr_request(): + """ Request for QR + """ + # initially set all white background + width, height, draw = init_screen() + + # English + draw.text( + (50, 50), + messages6in.qr_request_title_en, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + draw.text( + (50, 150), + messages6in.qr_request_title_en_2, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + # Vietnamese + draw.text( + (50, 300), + messages6in.qr_request_title_vi, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + + # Draw the number of countdown + for i in range(0, 3): + draw.ellipse((width//2 - 60, 400, width//2 + 60, 520), fill=BLACK, outline=BLACK) + draw.text( + (width//2 - 26, 390), + str(3 - i), + fill=WHITE, + font=utils.create_font("sawasdeebold", 90), + ) + config.WAVESHARE.draw_partial(constants.DisplayModes.DU) + draw.rectangle((width//2 - 100, 370, width - 20, height - 20), fill=WHITE, outline=WHITE) + time.sleep(1) + + # Clear white + draw.rectangle((0, 0, width - 1, height - 1), fill=WHITE, outline=WHITE) + config.WAVESHARE.draw_partial(constants.DisplayModes.DU) + + # English + draw.text( + (50, 50), + messages6in.qr_request_scanning_en, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + draw.text( + (50, 150), + messages6in.qr_request_for_n_sats_en.format("{:,}".format(math.floor(config.SATS))), + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + # Vietnamese + draw.text( + (50, 350), + messages6in.qr_request_scanning_vi, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + draw.text( + (50, 450), + messages6in.qr_request_for_n_sats_vi.format("{:,}".format(math.floor(config.SATS)).replace(",",".")), + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + config.WAVESHARE.draw_partial(constants.DisplayModes.DU) + +def update_qr_failed(): + """ QR scanning failed + """ + # initially set all white background + width, height, draw = init_screen() + + # English + draw.text( + (60,60), + messages6in.qr_failed_title_en, + fill=BLACK, + font=utils.create_font("freemonobold", 100), + ) + draw.text( + (60,210), + messages6in.qr_failed_desc_en, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + # Vietnamese + draw.text( + (60,500), + messages6in.qr_failed_title_vi, + fill=BLACK, + font=utils.create_font("freemonobold", 100), + ) + draw.text( + (60,650), + messages6in.qr_failed_desc_vi, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + +def update_payout_screen(): + """Update the payout screen to reflect balance of deposited coins. + Scan the invoice??? I don't think so! + """ + width, height, draw = init_screen() + + # English + draw.text( + (60, 100), + messages6in.payout_screen_title_en.format(math.floor(config.SATS)), + fill=BLACK, + font=utils.create_font("freemonobold", 100), + ) + draw.text( + (60, 250), + messages6in.payout_screen_title_en_2.format(str("{:,}".format(math.floor(config.SATS)))), + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + # Vietnamese + draw.text( + (60, 600), + messages6in.payout_screen_title_vi.format(str("{:,}".format(math.floor(config.SATS)).replace(",","."))), + fill=BLACK, + font=utils.create_font("freemonobold", 100), + ) + draw.text( + (60, 750), + messages6in.payout_screen_title_vi_2.format(math.floor(config.SATS)), + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + # scan the invoice TODO: I notice this is commented out, I presume this function should _not_ be + # scanning a QR code on each update? config.INVOICE = qr.scan() + +def update_payment_failed(): + """ Payment failed + """ + width, height, draw = init_screen() + + # English + draw.text( + (50, 50), + messages6in.payment_failed_title_en, + fill=BLACK, + font=utils.create_font("freemonobold", 100), + ) + draw.text( + (50, 200), + messages6in.payment_failed_desc_en, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + # draw logo + draw_logo(draw, 50, 300, True) + # Vietnamese + draw.text( + (50, 450), + messages6in.payment_failed_title_vi, + fill=BLACK, + font=utils.create_font("freemonobold", 100), + ) + draw.text( + (50, 600), + messages6in.payment_failed_desc_vi, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + # draw logo + draw_logo(draw, 50, 700, True) + + #config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + + # Timing out + draw.text( + (50, 900), + messages6in.payment_failed_timeout_en, + fill=BLACK, + font=utils.create_font("freemono", 60) + ) + draw.text( + (50, 950), + messages6in.payment_failed_timeout_vi, + fill=BLACK, + font=utils.create_font("freemono", 60) + ) + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + # show time out + time_out = 10 + for i in range(time_out, 0, -1): + draw.ellipse((1030, 910, 1130, 1010), fill=BLACK, outline=BLACK) + draw.text( + (1046, 910), + "0"+ str(i) if i < 10 else str(i), + fill=WHITE, + font=utils.create_font("sawasdeebold", 60) + ) + config.WAVESHARE.draw_partial(constants.DisplayModes.DU) + draw.rectangle((1020, 900, width - 20, height - 20), fill=WHITE, outline=WHITE) + time.sleep(1) + + #time.sleep(30) + +def update_thankyou_screen(): + """ Thank-you screen + """ + width, height, draw = init_screen() + # draw logo + draw_logo(draw, width//2 - 310, 60) + # English + draw.text( + (60, 200), + messages6in.thankyou_screen_title_en, + fill=BLACK, + font=utils.create_font("freemonobold", 100), + ) + draw.text( + (60, 350), + messages6in.thankyou_screen_desc_en, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + + # Vietnamese + draw.text( + (60, 550), + messages6in.thankyou_screen_title_vi, + fill=BLACK, + font=utils.create_font("freemonobold", 100), + ) + draw.text( + (60, 700), + messages6in.thankyou_screen_desc_vi, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + + # Timing out + draw.text( + (50, 900), + messages6in.thankyou_screen_expire_en, + fill=BLACK, + font=utils.create_font("freemono", 60) + ) + draw.text( + (50, 950), + messages6in.thankyou_screen_expire_vi, + fill=BLACK, + font=utils.create_font("freemono", 60) + ) + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + # show time out + for i in range(10, 0, -1): + draw.ellipse((1030, 910, 1130, 1010), fill=BLACK, outline=BLACK) + draw.text( + (1046, 910), + "0"+ str(i) if i < 10 else str(i), + fill=WHITE, + font=utils.create_font("sawasdeebold", 60) + ) + config.WAVESHARE.draw_partial(constants.DisplayModes.DU) + draw.rectangle((1020, 900, width - 20, height - 20), fill=WHITE, outline=WHITE) + time.sleep(1) + +def update_nocoin_screen(): + """ No coin screen + """ + width, height, draw = init_screen() + + # English + draw.text( + (50, 50), + messages6in.nocoin_screen_title_en, + fill=BLACK, + font=utils.create_font("freemonobold", 90), + ) + draw.text( + (50, 150), + messages6in.nocoin_screen_desc_en, + fill=BLACK, + font=utils.create_font("freemono", 70), + ) + # Vietnamese + draw.text( + (50, 300), + messages6in.nocoin_screen_title_vi, + fill=BLACK, + font=utils.create_font("freemonobold", 90), + ) + draw.text( + (50, 400), + messages6in.nocoin_screen_desc_vi, + fill=BLACK, + font=utils.create_font("freemono", 70), + ) + # list of accepted bills + draw.text( + (50, 530), + messages6in.nocoin_screen_support_bills_en, + fill=BLACK, + font=utils.create_font("freemonobold", 70), + ) + draw.text( + (50, 630), + messages6in.nocoin_screen_support_bills_vi, + fill=BLACK, + font=utils.create_font("freemonobold", 70), + ) + bills = (10000, 20000, 50000, 100000, 200000, 500000) + y = 680 + odd = True + for bill in bills: + odd = not odd + if odd: + x = width - 500 + draw.text( + (x, y), + str("{:,}".format(bill)) + " VND", + fill=BLACK, + font=utils.create_font("freemonobold", 60) + ) + else: + x = 80 + y = y + 80 + draw.text( + (x, y), + str("{:,}".format(bill)) + " VND", + fill=BLACK, + font=utils.create_font("freemonobold", 60) + ) + + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + time.sleep(5) + +def update_lnurl_generation(): + """ Generate LNURL + """ + width, height, draw = init_screen() + + # English + draw.text( + (50, height//2 - 100), + messages6in.lnurl_generation_en, + fill=BLACK, + font=utils.create_font("freemonobold", 80), + ) + # Vietnamese + draw.text( + (50, height//2 + 100), + messages6in.lnurl_generation_vi, + fill=BLACK, + font=utils.create_font("freemonobold", 80), + ) + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + +def update_shutdown_screen(): + """ Shutdown screen + """ + width, height, draw = init_screen() + + # English + draw.text( + (50, 50), + messages6in.shutdown_screen_title_en, + fill=BLACK, + font=utils.create_font("freemono", 120), + ) + draw.text( + (50, 200), + messages6in.shutdown_screen_desc_en, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + # draw logo + draw_logo(draw, 50, 300) + # Vietnamese + draw.text( + (50, 600), + messages6in.shutdown_screen_title_vi, + fill=BLACK, + font=utils.create_font("freemono", 120), + ) + draw.text( + (50, 750), + messages6in.shutdown_screen_desc_vi, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + # draw logo + draw_logo(draw, 50, 850) + + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + +def update_wallet_scan(): + """ Scan the wallet QR + """ + # initially set all white background + width, height, draw = init_screen() + + # English + draw.text( + (50, 50), + messages6in.wallet_scan_en, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + # Vietnamese + draw.text( + (50, 400), + messages6in.wallet_scan_vi, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + time.sleep(2) + +def update_lntxbot_balance(balance): + """ LNTXBOT success + """ + # initially set all white background + width, height, draw = init_screen() + + # English + draw.text( + (50, 50), + messages6in.lntxbot_balance_title_en, + fill=BLACK, + font=utils.create_font("freemonobold", 100), + ) + draw.text( + (50, 150), + messages6in.lntxbot_balance_desc_en, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + draw.text( + (50, 250), + messages6in.lntxbot_balance_n_sats.format(balance), + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + # Vietnamses + draw.text( + (50, 500), + messages6in.lntxbot_balance_title_vi, + fill=BLACK, + font=utils.create_font("freemonobold", 100), + ) + draw.text( + (50, 600), + messages6in.lntxbot_balance_desc_vi, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + draw.text( + (50, 700), + messages6in.lntxbot_balance_n_sats.format(balance), + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + time.sleep(3) + +def update_btcpay_lnd(): + """ BTC Pay success + """ + # initially set all white background + width, height, draw = init_screen() + + draw.text( + (50, 50), + messages6in.btcpay_lnd_title_en, + fill=BLACK, + font=utils.create_font("freemonobold", 100), + ) + draw.text( + (50, 150), + messages6in.btcpay_lnd_desc_en, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + draw.text( + (50, 250), + messages6in.btcpay_lnd_wallet_en, + fill=BLACK, + font=utils.create_font("freemono", 80), + ) + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + time.sleep(3) + +def wait_for_balance_update(start_balance, timeout=90): + import lntxbot + draw = ImageDraw.Draw(config.WAVESHARE.frame_buf) + width, height = config.WAVESHARE.frame_buf.size + + start_time = time.time() + success = False + # loop while we wait for the balance to get updated + i = timeout - 1 + logger.info("Start time: {}".format(start_time)) + while True and (time.time() < start_time + timeout): + new_balance = lntxbot.get_lnurl_balance() + draw.ellipse((1030, 910, 1130, 1010), fill=BLACK, outline=BLACK) + draw.text( + (1046, 910), + "0"+ str(i) if i < 10 else str(i), + fill=WHITE, + font=utils.create_font("sawasdeebold", 60) + ) + draw.rectangle((1020, 900, width - 20, height - 20), fill=WHITE, outline=WHITE) + config.WAVESHARE.draw_partial(constants.DisplayModes.DU) + if start_balance == new_balance: + print("Balance: " + str(start_balance) + " (no changes)") + #time.sleep(3) + else: + print( + "Balance: " + str(start_balance) + " | New Balance:" + str(new_balance) + ) + success = True + break + i-=1 + time.sleep(1) + config.WAVESHARE.draw_partial(constants.DisplayModes.DU) + logger.info("End time: {}".format(time.time())) + return success + +def draw_lnurl_qr(qr_img): + """Draw a lnurl qr code on the e-ink screen + """ + width, height, draw = init_screen() + + # English + draw.text( + (50, 50), + messages6in.lnurl_qr_en.format(str("{:,}".format(math.floor(config.SATS)))), + fill=BLACK, + font=utils.create_font("freemonobold", 80) + ) + # Vietnamese + draw.text( + (50, 150), + messages6in.lnurl_qr_vi.format(str("{:,}".format(math.floor(config.SATS)).replace(",", "."))), + fill=BLACK, + font=utils.create_font("freemonobold", 80) + ) + # QR + qr_img = qr_img.resize((600, 600), resample=0) + draw.bitmap((50, 300), qr_img, fill=BLACK) + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + +def update_payment_status(remain): + width, height = config.WAVESHARE.frame_buf.size + draw = ImageDraw.Draw(config.WAVESHARE.frame_buf) + + # Timing out + draw.text( + (700, 530), + messages6in.lnurl_qr_timeout_en, + fill=BLACK, + font=utils.create_font("freemono", 60) + ) + draw.text( + (700, 610), + messages6in.lnurl_qr_timeout_vi, + fill=BLACK, + font=utils.create_font("freemono", 60) + ) + config.WAVESHARE.draw_partial(constants.DisplayModes.DU) + draw.rectangle((1020, 900, width - 20, height - 20), fill=WHITE, outline=WHITE) + draw.ellipse((1180, 540, 1300, 660), fill=BLACK, outline=BLACK) + draw.text( + (1206, 550), + "0"+ str(remain) if remain < 10 else str(remain), + fill=WHITE, + font=utils.create_font("sawasdeebold", 62), + ) + config.WAVESHARE.draw_partial(constants.DisplayModes.DU) + + +def update_amount_screen(): + """Update the amount screen to reflect new coins inserted + """ + width, height, draw = init_screen() + # show the date time + dt = datetime.datetime.now() + draw.text( + (width - 350, 40), + dt.strftime("%Y/%b/%d"), + fill=BLACK, + font=utils.create_font("sawasdee", 50) + ) + draw.text( + (width - 260, 90), + dt.strftime("%H:%M"), + fill=BLACK, + font=utils.create_font("sawasdee", 50) + ) + # sats amount + draw.text( + (60, 10), + str("{:,}".format(math.floor(config.SATS))) + " " + messages6in.amount_screen_sats, + fill=BLACK, + font=utils.create_font("sawasdeebold", 100), + ) + # vnd amount + draw.text( + (60, 130), + str("{:,}".format(round(config.FIAT, 2))) + " " + config.conf["atm"]["cur"].upper(), + fill=BLACK, + font=utils.create_font("sawasdeebold", 80), + ) + # Rate + draw.text( + (60, 300), + messages6in.amount_screen_rate, + fill=BLACK, + font=utils.create_font("freemonobold", 70), + ) + draw.text( + (120, 370), + messages6in.amount_screen_rate_line.format(str(round((config.BTCPRICE / 100000000), 2)) + " " + config.conf["atm"]["cur"].upper()), + fill=BLACK, + font=utils.create_font("freemono", 70), + ) + + # Fee + draw.text( + (60, 470), + messages6in.amount_screen_fee, + fill=BLACK, + font=utils.create_font("freemonobold", 70), + ) + draw.text( + (120, 540), + messages6in.amount_screen_fee_line.format(config.conf["atm"]["fee"], str(math.floor(config.SATSFEE))), + fill=BLACK, + font=utils.create_font("freemono", 70), + ) + # description + draw.text( + (60, 650), + messages6in.amount_screen_desc_en_1, + fill=BLACK, + font=utils.create_font("sawasdee", 60), + ) + draw.text( + (60, 730), + messages6in.amount_screen_desc_en_2, + fill=BLACK, + font=utils.create_font("sawasdee", 60), + ) + draw.text( + (60, 850), + messages6in.amount_screen_desc_vi_1, + fill=BLACK, + font=utils.create_font("freemono", 60), + ) + draw.text( + (60, 930), + messages6in.amount_screen_desc_vi_2, + fill=BLACK, + font=utils.create_font("freemono", 60), + ) + + config.WAVESHARE.draw_full(constants.DisplayModes.GC16) + +def draw_logo(draw, x=0, y=0, lite=False): + """ Draw Future.Travel logo + """ + path = config.home + if lite==True: + img_path = path + "/LightningATM/resources/images/company-logo/logo-future-travel-lite.jpg" + else: + img_path = path + "/LightningATM/resources/images/company-logo/logo-future-travel-black.jpg" + img = Image.open(img_path) + config.WAVESHARE.frame_buf.paste(img, [ x, y, x + img.size[0], y + img.size[1] ]) + +def init_screen(): + """Prepare the screen for drawing and return the draw variables + """ + # clear to white + config.WAVESHARE.clear() + # Set width and height of screen + width, height = config.WAVESHARE.frame_buf.size + # prepare for drawing + draw = ImageDraw.Draw(config.WAVESHARE.frame_buf) + # draw the border + draw.rectangle( + (10, 10, width - 10, height - 10), fill=WHITE, outline=BLACK + ) + return width, height, draw + +def print_system_info(): + epd = config.WAVESHARE.epd + + print('*** System info:') + print(' display size: {}x{}'.format(epd.width, epd.height)) + print(' img buffer address: {:X}'.format(epd.img_buf_address)) + print(' firmware version: {}'.format(epd.firmware_version)) + print(' LUT version: {}'.format(epd.lut_version)) + + +def _place_text(text, fontsize=80, x_offset=0, y_offset=0): + ''' + Put some centered text at a location on the image. + ''' + draw = ImageDraw.Draw(config.WAVESHARE.frame_buf) + + font=utils.create_font("freemono", fontsize) + img_width, img_height = config.WAVESHARE.frame_buf.size + text_width, _ = font.getsize(text) + text_height = fontsize + + draw_x = (img_width - text_width)//2 + x_offset + draw_y = (img_height - text_height)//2 + y_offset + + draw.text((draw_x, draw_y), text, font=font) diff --git a/example_config.ini b/example_config.ini index cdcfd1d..ef86ea7 100644 --- a/example_config.ini +++ b/example_config.ini @@ -1,14 +1,14 @@ [atm] # Set your fiat currency with the three letter # currency code (https://www.xe.com/symbols.php) -cur = eur +cur = vnd # Define what a cent is called in the currency # of your choice for price display (singular). -centname = cent +centname = Dong # Set the Fee in % -fee = 2 +fee = 8.5 # dangermode=on will save lnd Macaroon and lntxbot credential to this config file # dangermode=off will wipe any saved credentials and require them to be entered each @@ -19,13 +19,13 @@ dangermode = on # Current options are: # display = papiruszero2in # display = waveshare2in13 -display = papiruszero2in +display = waveshare6in # Automatically set during initial setup to LND or LNTXBOT # Current options are: # activewallet = btcpay_lnd # activewallet = lntxbot -activewallet = +activewallet = # By default, the ATM only makes payouts when the button is pressed. # If you set this value higher than 0, it will automatically start diff --git a/lndrest.py b/lndrest.py index 5bb5895..7203077 100755 --- a/lndrest.py +++ b/lndrest.py @@ -115,8 +115,6 @@ def handle_invoice(): else: display.update_payment_failed() time.sleep(120) - - logger.info("Initiating softreset...") else: print("Please show correct invoice") diff --git a/lntxbot.py b/lntxbot.py index 735ee07..f6e9107 100755 --- a/lntxbot.py +++ b/lntxbot.py @@ -11,6 +11,10 @@ import config import utils +import lnurl +from urllib.parse import urlparse +from lnurl import Lnurl + # import display display_config = config.conf["atm"]["display"] display = getattr(__import__("displays", fromlist=[display_config]), display_config) @@ -21,6 +25,61 @@ # TODO: Add evaluation for credentials after scanning # TODO: Remove display calls from here to app.py +def process_lnurl(amt, inv): + """Processes pay an amount using the lnurl scheme + """ + pr = convert_ln(amt, inv) + data = { + "invoice": pr, + "amount": math.floor(amt) + } + response = requests.post( + str(config.conf["lntxbot"]["url"]) + "/payinvoice", + headers={"Authorization": "Basic %s" % config.conf["lntxbot"]["creds"]}, + data=json.dumps(data) + ) + response = response.json() + logger.info("Payout Response: %s", json.dumps(response)) + if ("error" in response and response["error"]) or ("payment_error" in response and response["payment_error"]): + display.update_payment_failed() + else: + # get the balance? back from the bot + start_balance = get_lnurl_balance() + print("Start balance: {0}".format(start_balance)) + logger.info("Start balance: %s", start_balance) + # loop while we wait for a balance update or until timeout reached + success = wait_for_balance_update(start_balance, timeout=config.TIMEOUT) + if success: + display.update_thankyou_screen() + logger.info("LNURL payment succeeded") + return + else: + logger.error("LNURL payment failed (within 90 seconds)") + display.update_payment_failed() + + +def convert_ln(amt, inv): + """Will convert LNURL to BOLT11 invoice + """ + logger.info("Amount: %s", str(amt)) + logger.info("Input inv: %s", inv) + pr = str(inv) + amt = math.floor(amt) + logger.info("Floored amount: %s", amt) + data = {"amount":amt} + lnurl = Lnurl(inv) + logger.info("Detected LNURL: %s", inv) + rsp = requests.get(lnurl.decoded, verify=True) + dic = rsp.json() + logger.info("Dict from request: %s", json.dumps(dic)) + url = urlparse(dic['callback']) + logger.info("Callback URL: %s", json.dumps(url)) + r = requests.post("https://lnurl.bigsun.xyz/lnurl-pay/callback/?"+url.query+"&amount="+str(amt),headers=None,data=json.dumps(data)) + logger.info("Result: %s", json.dumps(r.json())) + if "pr" in r.json(): + pr = r.json()["pr"] + logger.info("Parsed PR: %s", pr) + return pr def payout(amt, payment_request): """Attempts to pay a BOLT11 invoice @@ -29,11 +88,18 @@ def payout(amt, payment_request): "invoice": payment_request, "amount": math.floor(amt), } + logger.info("Input: %s", json.dumps(data)) response = requests.post( str(config.conf["lntxbot"]["url"]) + "/payinvoice", headers={"Authorization": "Basic %s" % config.conf["lntxbot"]["creds"]}, - data=json.dumps(data), + data=json.dumps(data) ) + response = response.json() + logger.info("Payout Response: %s", json.dumps(response)) + if ("error" in response and response["error"]) or ("payment_error" in response and response["payment_error"]): + display.update_payment_failed() + else: + display.update_thankyou_screen() def request_lnurl(amt): @@ -45,7 +111,7 @@ def request_lnurl(amt): response = requests.post( str(config.conf["lntxbot"]["url"]) + "/generatelnurlwithdraw", headers={"Authorization": "Basic %s" % config.conf["lntxbot"]["creds"]}, - data=json.dumps(data), + data=json.dumps(data) ) return response.json() @@ -57,6 +123,9 @@ def get_lnurl_balance(): response = requests.post( str(config.conf["lntxbot"]["url"]) + "/balance", headers={"Authorization": "Basic %s" % config.conf["lntxbot"]["creds"]}, + files=None, + verify=True, + timeout=10 ) return response.json()["BTC"]["AvailableBalance"] @@ -69,10 +138,14 @@ def wait_for_balance_update(start_balance, timeout): success = False # loop while we wait for the balance to get updated while True and (time.time() < start_time + timeout): + ks = time.time() new_balance = get_lnurl_balance() if start_balance == new_balance: print("Balance: " + str(start_balance) + " (no changes)") - time.sleep(3) + remain = timeout - math.floor(ks - start_time) + print("Remain: {}".format(remain)) + display.update_payment_status(remain) + time.sleep(4) else: print( "Balance: " + str(start_balance) + " | New Balance:" + str(new_balance) @@ -81,17 +154,44 @@ def wait_for_balance_update(start_balance, timeout): break return success - def process_using_lnurl(amt): """Processes receiving an amount using the lnurl scheme """ # get the new lnurl display.update_lnurl_generation() logger.info("LNURL requested") - print("LNURL requested") lnurl = request_lnurl(amt) - print(lnurl["lnurl"]) + logger.info("LN requested: %s", json.dumps(lnurl)) + # Check EPD_SIZE is defined + utils.check_epd_size() + + # create a qr code image and print it to terminal + qr_img = utils.generate_lnurl_qr(lnurl["lnurl"]) + + # draw the qr code on the e-ink screen + display.draw_lnurl_qr(qr_img) + + # get the balance? back from the bot + start_balance = get_lnurl_balance() + logger.info("Start balance: %s", start_balance) + + # loop while we wait for a balance update or until timeout reached + success = wait_for_balance_update(start_balance, timeout=config.TIMEOUT) + + if success: + display.update_thankyou_screen() + logger.info("LNURL withdrawal succeeded") + return + else: + display.update_payment_failed() + # TODO: I think we should handle a failure here + logger.error("LNURL withdrawal failed (within {0} seconds)".format(config.TIMEOUT)) +def process_lnurl_directly(amt, timeout): + """Processes receiving an amount using the lnurl scheme + """ + lnurl = request_lnurl(amt) + logger.info("LN requested: %s", json.dumps(lnurl)) # Check EPD_SIZE is defined utils.check_epd_size() @@ -101,17 +201,23 @@ def process_using_lnurl(amt): # draw the qr code on the e-ink screen display.draw_lnurl_qr(qr_img) + # get the balance? back from the bot start_balance = get_lnurl_balance() - print(start_balance) + #print(start_balance) + logger.info("Start balance: %s", start_balance) # loop while we wait for a balance update or until timeout reached - success = wait_for_balance_update(start_balance, timeout=90) + success = wait_for_balance_update(start_balance, timeout) if success: display.update_thankyou_screen() + print("Successful withdrawal.") logger.info("LNURL withdrawal succeeded") return else: + display.update_payment_failed() # TODO: I think we should handle a failure here - logger.error("LNURL withdrawal failed (within 90 seconds)") + print("Failed withdrawal.") + logger.error("LNURL withdrawal failed (within {0} seconds)".format(timeout)) + diff --git a/qr.py b/qr.py index d87b9af..acce0b0 100755 --- a/qr.py +++ b/qr.py @@ -12,6 +12,38 @@ logger = logging.getLogger("QR") +def scan_t(t=5): + + with PiCamera() as camera: + try: + camera.start_preview() + time.sleep(1) + logger.info("Start scanning for QR code") + except: + logger.error("PiCamera.start_preview() raised an exception") + + stream = BytesIO() + qr_codes = None + # Set timeout to 10 seconds + timeout = time.time() + t + while qr_codes is None and (time.time() < timeout): + stream.seek(0) + # Start camera stream (make sure RaspberryPi camera is focused correctly + # manually adjust it, if not) + camera.capture(stream, "jpeg") + qr_codes = zbarlight.scan_codes("qrcode", Image.open(stream)) + time.sleep(0.05) + camera.stop_preview() + # break immediately if we didn't get a qr code scan + if not qr_codes: + logger.info("No QR within {} seconds detected".format(t)) + return False + + # decode the first qr_code to get the data + qr_code = qr_codes[0].decode() + + return qr_code + def scan(): @@ -52,7 +84,7 @@ def scan_attempts(target_attempts): attempts = 0 while attempts < target_attempts: - qrcode = scan() + qrcode = scan_t() if qrcode: logger.info("QR code successfuly detected.") return qrcode diff --git a/resources/images/company-logo/logo-future-travel-black.jpg b/resources/images/company-logo/logo-future-travel-black.jpg new file mode 100644 index 0000000..adc7fce Binary files /dev/null and b/resources/images/company-logo/logo-future-travel-black.jpg differ diff --git a/resources/images/company-logo/logo-future-travel-lite.jpg b/resources/images/company-logo/logo-future-travel-lite.jpg new file mode 100644 index 0000000..7611258 Binary files /dev/null and b/resources/images/company-logo/logo-future-travel-lite.jpg differ diff --git a/utils.py b/utils.py index 102f08b..8a459bf 100644 --- a/utils.py +++ b/utils.py @@ -7,7 +7,6 @@ import qrcode from PIL import ImageFont -from pathlib import Path logger = logging.getLogger("UTILS") @@ -27,22 +26,22 @@ def create_font(font, size): """Create fonts from resources """ # Construct paths to foder with fonts - pathfreemono = Path.cwd().joinpath("resources", "fonts", "FreeMono.ttf") - pathfreemonobold = Path.cwd().joinpath("resources", "fonts", "FreeMonoBold.ttf") - pathsawasdee = Path.cwd().joinpath("resources", "fonts", "Sawasdee-Bold.ttf") - pathdotmbold = Path.cwd().joinpath("resources", "fonts", "DOTMBold.ttf") - + path = os.path.dirname(os.path.abspath(__file__)) + font_path = False if font == "freemono": - return ImageFont.truetype(pathfreemono.as_posix(), size) - if font == "freemonobold": - return ImageFont.truetype(pathfreemonobold.as_posix(), size) - if font == "sawasdee": - return ImageFont.truetype(pathsawasdee.as_posix(), size) - if font == "dotmbold": - return ImageFont.truetype(pathdotmbold.as_posix(), size) + font_path = path + "/resources/fonts/FreeMono.ttf" + elif font == "freemonobold": + font_path = path + "/resources/fonts/FreeMonoBold.ttf" + elif font == "sawasdee": + font_path = path + "/resources/fonts/Sawasdee.ttf" + elif font == "sawasdeebold": + font_path = path + "/resources/fonts/Sawasdee-Bold.ttf" + elif font == "dotmbold": + font_path = path + "/resources/fonts/DOTMBold.ttf" else: print("Font not available") - + if font_path is not False: + return ImageFont.truetype(font_path, size) def get_btc_price(fiat_code): """Get BTC -> FIAT conversion