├── .gitignore ├── LICENSE.md ├── LightningATM.service ├── README.md ├── app.py ├── blink.py ├── config.py ├── data └── __init__.py ├── displays ├── __init__.py ├── inkyphat.py ├── messages.py ├── messages_cz.py ├── messages_de.py ├── messages_es.py ├── messages_fr.py ├── messages_it.py ├── messages_pt.py ├── messages_tr.py ├── papiruszero2in.py ├── waveshare2in13d.py ├── waveshare2in13v2.py ├── waveshare2in13v3.py ├── waveshare2in13v3cz.py ├── waveshare2in66.py ├── waveshare2in7.py └── waveshare2in9d.py ├── docs ├── guide │ ├── autostart.md │ ├── button.md │ ├── camera.md │ ├── coin_validator.md │ ├── display.md │ ├── edit_config.md │ ├── information_and_tips.md │ ├── languages.md │ ├── parts_list.md │ ├── perform_update.md │ ├── relay.md │ ├── rpi_image_from_scratch.md │ ├── sdcard_and_wifi.md │ ├── set_up_a_blink_wallet.md │ ├── set_up_an_lnbits_wallet.md │ ├── tmux_monitoring.md │ ├── voltage_converter.md │ ├── we_need_your_help.md │ └── wiring.md └── pictures │ ├── add_on_zero2_PaPiRus_1.png │ ├── add_on_zero2_PaPiRus_2.png │ ├── add_on_zero2_SPI_1.png │ ├── add_on_zero2_SPI_2.png │ ├── add_on_zero2_SPI_3.png │ ├── add_on_zero2_SPI_4.png │ ├── add_on_zero2_edit_config.png │ ├── add_on_zero2_edit_qr.png │ ├── add_on_zero2_edit_utils.png │ ├── button_ATM.jpg │ ├── button_LED_test.png │ ├── button_back.jpg │ ├── button_button_test.png │ ├── button_front.jpg │ ├── camera_example_image.jpg │ ├── camera_example_structure.jpg │ ├── camera_installation.jpg │ ├── camera_raspi-config_1.png │ ├── camera_raspi-config_2.png │ ├── camera_terminal_raspistill.png │ ├── camera_terminal_scp.png │ ├── coin_validator_calibration.jpg │ ├── coin_validator_closeup.jpg │ ├── display_demo.jpg │ ├── display_fault.png │ ├── display_test.png │ ├── edit_config_display_ATM_off.jpg │ ├── edit_config_display_ATM_on.jpg │ ├── edit_config_first_call_exiting.png │ ├── edit_config_terminal_1.png │ ├── edit_config_terminal_2.png │ ├── languages_after.jpg │ ├── languages_before.jpg │ ├── languages_config.ini.png │ ├── languages_edit01.png │ ├── languages_edit02.png │ ├── languages_example.png │ ├── perform_update_logs.png │ ├── readme_atm_pv.png │ ├── relay_build_in.jpg │ ├── relay_contacts.png │ ├── relay_options.jpg │ ├── relay_relay.jpg │ ├── relay_test.png │ ├── set_up_a_blink_wallet_api_key.png │ ├── set_up_a_blink_wallet_wallets.png │ ├── set_up_an_lnbits_wallet.png │ ├── tmux_monitoring_terminal.png │ ├── voltage_converter_coin.jpg │ ├── voltage_converter_poti.jpg │ ├── we_need_your_help_config.ini.png │ ├── wiring_clamp.jpg │ ├── wiring_direct_wiring.jpg │ ├── wiring_fw.png │ ├── wiring_pv.png │ ├── wiring_pv_adapter_cable.jpg │ ├── wiring_pv_build-in.jpg │ ├── wiring_pv_build-in_complet.jpg │ └── wiring_pv_direct_wiring.jpg ├── example_config.ini ├── lnbits.py ├── lndrest.py ├── lntxbot.py ├── qr.py ├── requirements.txt ├── resources ├── 3dmodels │ ├── LightningATM - Pocket Edition │ │ ├── LightningATM Pocket Edition - V0.1 - Acrylic - 3mm.dxf │ │ ├── LightningATM Pocket Edition - V0.1 - Engraving.dxf │ │ ├── LightningATM Pocket Edition - V0.1 - MDF - 3mm.dxf │ │ └── STLs │ │ │ ├── Back_Lid_NORMAL_v1_NoSupp.stl │ │ │ ├── Back_Lid_THICKER_v1_NoSupp.stl │ │ │ ├── Front_Glass_v1_NoSupp.stl │ │ │ ├── Magnet Case │ │ │ ├── Case Magnet.stl │ │ │ ├── Front Magnet.stl │ │ │ ├── Logo Backplate Magnet.stl │ │ │ ├── Screen Backplate Magnet.stl │ │ │ └── info.md │ │ │ ├── README.md │ │ │ ├── Shell_v1_NoSupp.stl │ │ │ ├── Top_Lid_No_Logo_v1_NoSupp.stl │ │ │ ├── Top_Lid_with_Logo_v1_NoSupp.stl │ │ │ ├── images │ │ │ ├── 3d_printed_case.jpeg │ │ │ ├── glued_lid.jpeg │ │ │ ├── lids_and_rim.jpeg │ │ │ ├── mounting_rim.jpeg │ │ │ ├── rim_and_backlid.jpeg │ │ │ ├── shell_inserted.jpeg │ │ │ ├── shell_inserts.jpeg │ │ │ ├── shell_m3_inserts.jpeg │ │ │ ├── shell_with_mounted_pi.jpeg │ │ │ └── top_lid_with_logo.jpeg │ │ │ ├── just_logo.stl │ │ │ └── optional_Mounting_Rim_v1_NoSupp.stl │ └── LightningATM │ │ ├── LightningATM - V0.4 - Acrylic - 3mm.dxf │ │ ├── LightningATM - V0.4 - Engraving.dxf │ │ ├── LightningATM - V0.4 - MDF - 3mm.dxf │ │ └── old_versions │ │ ├── LightningATM - V0.1 - Assembly Backside.png │ │ ├── LightningATM - V0.1 - Assembly.png │ │ ├── LightningATM - V0.1 - Video.mp4 │ │ ├── LightningATM - V0.1 - final laser layout.dxf │ │ ├── LightningATM - V0.1.dxf │ │ ├── LightningATM - V0.2 - Acrylic.dxf │ │ ├── LightningATM - V0.2 - Engraving.dxf │ │ ├── LightningATM - V0.2 - MDF.dxf │ │ ├── LightningATM - V0.3 - ATM.dxf │ │ ├── LightningATM - V0.3 - ATM.png │ │ ├── LightningATM - V0.3 - Compartments.dxf │ │ └── LightningATM - V0.3 - Compartments.png ├── fonts │ ├── DOTMBold.ttf │ ├── FreeMono.ttf │ ├── FreeMonoBold.ttf │ ├── FreeMonoBoldOblique.ttf │ ├── FreeMonoOblique.ttf │ ├── Sawasdee-Bold.ttf │ ├── Sawasdee-BoldOblique.ttf │ ├── Sawasdee-Oblique.ttf │ └── Sawasdee.ttf ├── images │ ├── LightningATM.png │ ├── LightningATM1.jpg │ ├── LightningATM1.png │ ├── LightningATM1_onstage2.jpeg │ ├── LightningATM2.jpeg │ ├── LightningATM2.png │ ├── LightningATM_design_by_@cryptonobo.jpeg │ ├── LightningATM_onstage1.jpeg │ ├── LightningATM_rev3.jpg │ ├── inside │ │ ├── inside1.jpg │ │ ├── inside2.jpg │ │ ├── inside3.jpg │ │ ├── inside4.jpg │ │ └── inside5.jpg │ ├── papirus_zero.jpg │ ├── startup.png │ └── startup_screen.jpg └── setup │ ├── rc.local │ ├── setup.sh │ └── show_ip.py ├── tests ├── LED_test.py ├── __init__.py ├── acceptor_test.py ├── app_without_c_acceptor.py ├── bech32_encoding.py ├── button_test.py ├── coinacceptor.py ├── display.py ├── displayalign.py ├── lnbits_lnurlw.py ├── qr_tests.py ├── relay_test.py └── startup.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 21isenough 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LightningATM.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=LightningATM Service 3 | After=multi-user.target 4 | 5 | [Service] 6 | User=pi 7 | Type=idle 8 | WorkingDirectory=/home/pi/LightningATM 9 | ExecStart=/usr/bin/python3 /home/pi/LightningATM/app.py 10 | Restart=always 11 | TimeoutSec=60 12 | RestartSec=30 13 | 14 | [Install] 15 | WantedBy=multi-user.target 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LightningATM 2 | 3 | ### 🔥🔥🔥 Now a seconde funding source next to LNbits is available. 🔥🔥🔥 4 | ### 👉👉👉 [blink.sv](https://www.blink.sv/) 🔥 See how to update: [Update](/docs/guide/perform_update.md) 👈👈👈 5 | 6 | 7 | 8 | This LightningATM was built to distribute small amounts of BTC - obviously - it only accepts coins. It shall demonstrate the power of Bitcoins Lightning Network. A two cent coin is enough to buy some satoshis at the LightningATM. 9 | 10 | A physical coin exchanged into bitcoin and send to your lightning wallet in seconds. Use this project to educate your family and friends or guests at your bitcoin meetup - a convenient and easy on-boarding process for people that are new to bitcoin. 11 | 12 | **Please be aware that this is a hobbyist project and it is not secure and reliable enough to withstand attacks. I discourage the use of this setup in a professional environment - but it's rather a good educational tool** 13 | 14 | There are currently two versions of the Lightning ATM. The large full version with a camera for scanning invoices and a button for acknowledgment and other functions. And there is the somewhat slimmed-down version for mobile use, the pocket version. 15 | 16 | 17 | Full Version | Pocket Version 18 | :-------------------------:|:-------------------------: 19 | ![](https://github.com/21isenough/LightningATM/blob/master/resources/images/LightningATM_rev3.jpg) | ![](docs/pictures/readme_atm_pv.png) 20 | 21 | 22 | ### Required components 23 | 24 | - [parts list](/docs/guide/parts_list.md) 25 | 26 | 27 | ### Installation guideline 28 | 29 | _Carry out the points step by step as described here. This is the quickest way to succeed._ 😊 30 | 31 | - [voltage converter](/docs/guide/voltage_converter.md) 32 | - [coin validator](/docs/guide/coin_validator.md) 33 | - [wiring](/docs/guide/wiring.md) 34 | - [sd card and wifi](/docs/guide/sdcard_and_wifi.md) 35 | - [display](/docs/guide/display.md) 36 | - [edit config.ini](/docs/guide/edit_config.md) 37 | - [debugging and tmux](/docs/guide/tmux_monitoring.md) 38 | - [autostart](/docs/guide/autostart.md) 39 | - [aditional information and tips](/docs/guide/information_and_tips.md) 40 | - [option: language](/docs/guide/languages.md) 41 | - [option: button](/docs/guide/button.md) 42 | - [option: camera](/docs/guide/camera.md) 43 | - [option: lockout relay](/docs/guide/relay.md) 44 | - [option: RPi image from scratch](/docs/guide/rpi_image_from_scratch.md) 45 | 46 | If you want to update your ATM with LNbits, see this guide: [Update](/docs/guide/perform_update.md) 47 | 48 | More information of how to setup the LightningATM can be found here https://docs.lightningatm.me. Also join the Telegram group of LightningATM builders here https://t.me/lightningatm_building or contact 21isenough on Twitter (https://twitter.com/21isenough). 49 | 50 | To see the Lightning ATM in action, check out this video [LightningATM in action](https://twitter.com/21isenough/status/1170808396955738114?s=20) 51 | 52 | ### Mentioned 53 | - https://podcasts.apple.com/us/podcast/interview-with-21isenough/id1481514734?i=1000458712983 54 | - https://blog.sicksubroutine.com/lightning-junkies-episode-021/ 55 | - http://bitcoin-turm.de/bturm29/ 56 | - https://www.atmmarketplace.com/articles/lightning-atm-lets-you-buy-itty-bitty-amounts-of-bitcoin/ 57 | - https://www.bitcoinmedia.id/atm-bitcoin-lightning-ini-adalah-prototype-pertama/ 58 | - https://twitter.com/CoinATMRadar/status/1202657309765357568?s=20 59 | - https://www.hackster.io/news/lightningatm-distributes-small-amounts-of-btc-over-the-lightning-network-5e03347acd88 60 | - https://criptonizando.com/2019/10/08/desenvolvedor-cria-caixa-eletronico-de-bitcoin-que-ate-amadores-podem-construir-usando-a-lightning-network/ 61 | - https://decrypt.co/10046/ightningatm-how-to-make-your-own-bitcoin-atm?utm_content=buffer7dcee&utm_medium=social&utm_source=twitter.com&utm_campaign=buffer 62 | 63 | ### Documentations in other languages 64 | 65 | - German Video: [Tutorial - Bitcoin Lightning ATM - montieren und konfigurieren](https://www.youtube.com/watch?v=pTyTc2qPQj0) 66 | - German tutorial for the pocket edition: [Tutorial LightningATM - ereignishorizont.xyz](https://ereignishorizont.xyz/lightning-atm/) 67 | 68 | ### Todo list 69 | 70 | #### Prio 1 71 | - [ ] Implement lndconnect 72 | - [ ] Add "no wallet setup" warning at boot 73 | - [ ] Implement LNURLProxyAPI 74 | - [ ] Support for more Lightning implementations and wallets 75 | - [ ] Try different way of detecting inserted coins (populating a list and setting a delimiter) 76 | - [ ] PCB Board design (https://easyeda.com/, http://kicad.org/) 77 | - [ ] 5V-12V step up transformer (http://www.electronics-lab.com/project/5v-to-12v-step-up-dc-dc-converter/) 78 | - [ ] Solve security concerns in regards to lnurl (scan over the shoulder) 79 | #### Prio 2 80 | - [ ] Dashboard for settings (via mobile) 81 | - [ ] Add second button and admin menu 82 | - [ ] Add admin function only available through qr pass 83 | - [ ] Run certain functions in threads 84 | - [ ] Additional button for restart and shutdown 85 | - [ ] Find solution to work without jumper cables 86 | - [ ] Add encrypted partition with sensible data 87 | #### Prio 3 88 | - [ ] Add TOR support for LND calls 89 | - [ ] Store data in database 90 | - [ ] Add AP option for users to login into their wifis (https://github.com/21isenough/RaspiWiFi) 91 | - [ ] 2 layer for coins to reject before accepted 92 | #### Done 93 | - [X] Update doc with new features, screens 94 | - [X] Check out BTCPay API to integrate backend 95 | - [X] Add coin return stopper to 3d design 96 | - [X] Try different e-ink screen 97 | - [X] Add a language file to change strings easily 98 | - [X] Write installations instructions (see docs.lightningatm.me) 99 | - [X] Rethink the design decisions in regards to hardware (https://www.arrow.com/en/products) 100 | - [X] Add encryption of admin.macaroon in idle mode 101 | - [X] Design ideas for case 102 | - [X] Slightly recline front board to the back, add immersion for camera 103 | - [X] Move scanning function into utils 104 | - [X] Move all display functions into display.py 105 | - [X] Implement lnurl with lntxbot 106 | - [X] Draw printer and boards in 3D (https://www.onshape.com/) 107 | - [X] Write list of all compatible wallets/qr qr_codes 108 | - [X] Test qr code on 2" eInk display (works) 109 | - [X] Research camera resolutions / qr code scanning 110 | - [X] Check if there's other zbar python libraries 111 | - [X] Change to wide angle camera 112 | - [X] Sort out scan errors 113 | - [x] QR code scan function 114 | - [x] Validate requested amount 115 | - [x] Reject to high amounts 116 | - [x] Add config file 117 | - [x] Add parts list to readme 118 | - [x] Ask for rescan of QR code 119 | - [x] Inform about failed scan 120 | - [x] Implement continuous QR Scan 121 | - [x] Lower case ln invoices 122 | - [x] Find a faster solution for QR scans (zbarcam) 123 | - [x] Organize todos better 124 | 125 | 126 | -------------------------------------------------------------------------------- /blink.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import json 4 | import logging 5 | import time 6 | import requests 7 | import math 8 | 9 | import config 10 | 11 | # import display 12 | display_config = config.conf["atm"]["display"] 13 | display = getattr(__import__("displays", fromlist=[display_config]), display_config) 14 | 15 | logger = logging.getLogger("BLINK") 16 | 17 | # TODO: Remove display calls from here to app.py 18 | 19 | 20 | class InvoiceDecodeError(BaseException): 21 | pass 22 | 23 | 24 | def payout(amt, payment_request): 25 | """Attempts to pay a BOLT11 invoice""" 26 | # Call the payout function with the amount and payment request 27 | # payout(amount, "your_payment_request_here", config) 28 | 29 | graphql_endpoint = str(config.conf["blink"]["graphql_endpoint"]) 30 | api_key = str(config.conf["blink"]["api_key"]) 31 | wallet_id = str(config.conf["blink"]["wallet_id"]) 32 | 33 | # Prepare the GraphQL mutation as a string with placeholders 34 | graphql_query = """ 35 | mutation LnInvoicePaymentSend($input: LnInvoicePaymentInput!) { 36 | lnInvoicePaymentSend(input: $input) { 37 | status 38 | errors { 39 | message 40 | path 41 | code 42 | } 43 | } 44 | }""" 45 | 46 | # Using string formatting to insert variables into the GraphQL mutation 47 | payload = { 48 | "query": graphql_query, 49 | "variables": { 50 | "input": { 51 | "walletId": wallet_id, 52 | "paymentRequest": payment_request, 53 | "memo": "LightningATM payout", 54 | } 55 | }, 56 | } 57 | 58 | # Convert the payload dictionary to a JSON-formatted string 59 | payload_json = json.dumps(payload) 60 | 61 | headers = {"Content-Type": "application/json", "X-API-KEY": api_key} 62 | 63 | # Make the POST request to the API 64 | response = requests.post(graphql_endpoint, headers=headers, data=payload_json) 65 | 66 | # Print the response text 67 | print(response.text) 68 | 69 | 70 | def last_payment(payment_request): 71 | """Fetches the status of the given invoice.""" 72 | 73 | # Setup your endpoint and headers 74 | graphql_endpoint = str(config.conf["blink"]["graphql_endpoint"]) 75 | headers = { 76 | "Content-Type": "application/json", 77 | } 78 | 79 | graphql_query = """ 80 | query LnInvoicePaymentStatus($input: LnInvoicePaymentStatusInput!) { 81 | lnInvoicePaymentStatus(input: $input) { 82 | status 83 | } 84 | } 85 | """ 86 | 87 | variables = {"input": {"paymentRequest": payment_request}} 88 | 89 | payload = {"query": graphql_query, "variables": variables} 90 | 91 | response = requests.post(graphql_endpoint, json=payload, headers=headers) 92 | 93 | if response.status_code == 200: 94 | json_data = response.json() 95 | if ( 96 | "data" in json_data 97 | and "lnInvoicePaymentStatus" in json_data["data"] 98 | and "status" in json_data["data"]["lnInvoicePaymentStatus"] 99 | ): 100 | invoice_status = json_data["data"]["lnInvoicePaymentStatus"]["status"] 101 | 102 | if invoice_status == "PAID": 103 | print("Last transaction was successful.") 104 | return True 105 | else: 106 | print(f"Last transaction was not successful: {invoice_status}") 107 | return False 108 | else: 109 | print("Response JSON structure is not as expected.") 110 | return False 111 | else: 112 | print(f"Error fetching transaction data: HTTP {response.status_code}") 113 | return False 114 | 115 | 116 | def decode_request(payment_request): 117 | """Decodes a BOLT11 invoice""" 118 | # there is no decode request in the blink api 119 | # could implement probing here 120 | if payment_request: 121 | print({"invoice": payment_request}) 122 | if "lnbc1" in payment_request: 123 | print("Zero sat invoice detected") 124 | return 0 125 | elif "lnbc" in payment_request: 126 | print("Non-zero sat invoice detected") 127 | return 0 128 | elif "lntb" in payment_request: 129 | print("Signet invoice detected") 130 | return 0 131 | else: 132 | pass 133 | 134 | 135 | def handle_invoice(): 136 | """Decode a BOLT11 invoice. Ensure that amount is correct or 0, then attempt to 137 | make the payment. 138 | """ 139 | decode_req = decode_request(config.INVOICE) 140 | if decode_req in (math.floor(config.SATS), 0): 141 | payout(config.SATS, config.INVOICE) 142 | result = last_payment(config.INVOICE) 143 | 144 | if result: 145 | display.update_thankyou_screen() 146 | else: 147 | display.update_payment_failed() 148 | time.sleep(120) 149 | else: 150 | print("Please show correct invoice") 151 | 152 | 153 | def evaluate_scan(qrcode): 154 | """Evaluates the scanned qr code for Lightning invoices.""" 155 | if not qrcode: 156 | logger.error("QR code scanning failed") 157 | return False 158 | # check for a lightning invoice 159 | else: 160 | if "lnbc" in qrcode.lower(): 161 | logger.info("Lightning invoice detected") 162 | invoice = qrcode.lower() 163 | # if invoice preceded with "lightning:" then chop it off so that we can 164 | # handle it correctly 165 | if "lightning:" in invoice: 166 | invoice = invoice[10:] 167 | return invoice 168 | else: 169 | logger.error("This QR does not contain a Lightning invoice") 170 | return False 171 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | from configparser import ConfigParser 2 | from shutil import copyfile 3 | import logging 4 | import math 5 | import sys 6 | import os 7 | 8 | import utils 9 | 10 | home = os.path.expanduser("~") 11 | ATM_data_dir = home + "/.lightningATM/" 12 | config_file_path = ATM_data_dir + "config.ini" 13 | # check the config directory exists, create it if not 14 | if not os.path.exists(ATM_data_dir): 15 | os.makedirs(ATM_data_dir) 16 | 17 | # Set to logging.DEBUG if more info needed 18 | logging.disable(logging.DEBUG) 19 | 20 | # Set to logging.DEBUG if more "requests" debugging info needed 21 | logging.getLogger("requests").setLevel(logging.INFO) 22 | logging.getLogger("urllib3.connectionpool").setLevel(logging.INFO) 23 | 24 | # Configure basigConfig for the "logging" module 25 | logging.basicConfig( 26 | filename="{}/debug.log".format(ATM_data_dir), 27 | format="%(asctime)-23s %(name)-9s %(levelname)-7s | %(message)s", 28 | datefmt="%Y/%m/%d %I:%M:%S %p", 29 | level=logging.DEBUG, 30 | ) 31 | 32 | # Create logger for this config file 33 | logger = logging.getLogger("CONFIG") 34 | 35 | 36 | yes = ["yes", "ye", "y"] 37 | no = ["no", "n"] 38 | 39 | 40 | def ask_scan_config_val(section, variable): 41 | while True: 42 | try: 43 | res = input( 44 | "Do you want to scan to input {} {}".format(section, variable) 45 | ).lower() 46 | if res in yes: 47 | # value = scan the qr for the value 48 | # update_config(section, variable, value 49 | ... 50 | elif res in no: 51 | return 52 | else: 53 | print("Input invalid, please try again or KeyboardInterrupt to exit") 54 | except KeyboardInterrupt: 55 | return 56 | 57 | 58 | def check_config(): 59 | """Checks the config and prompt the user to provide values for missing keys 60 | """ 61 | if conf["lnd"]["macaroon"] is (None or ""): 62 | logger.warning("Missing value for lnd macaroon in config") 63 | ask_scan_config_val("lnd", "macaroon") 64 | if conf["lntxbot"]["creds"] is (None or ""): 65 | logger.warning("Missing value for lntxbot credential in config") 66 | ask_scan_config_val("lntxbot", "creds") 67 | 68 | 69 | def update_config(section, variable, value): 70 | """Update the config with the new value for the variable. 71 | If dangermode is on, we save them to config.ini, else we write them to the temporary 72 | dictionary 73 | """ 74 | if conf["atm"]["dangermode"].lower() == "on": 75 | config = create_config() 76 | config[section][variable] = value 77 | 78 | with open(CONFIG_FILE, "w") as configfile: 79 | config.write(configfile) 80 | else: 81 | conf[section][variable] = value 82 | 83 | 84 | def check_dangermode(): 85 | if conf["atm"]["dangermode"].lower() == "on": 86 | return True 87 | else: 88 | return False 89 | 90 | 91 | # config file handling 92 | def get_config_file(): 93 | # check that the config file exists, if not copy over the example_config 94 | if not os.path.exists(ATM_data_dir + "config.ini"): 95 | example_config = os.path.join(os.path.dirname(__file__), "example_config.ini") 96 | copyfile(example_config, ATM_data_dir + "config.ini") 97 | return os.environ.get("CONFIG_FILE", config_file_path) 98 | 99 | 100 | def create_config(config_file=None): 101 | parser = ConfigParser(comment_prefixes="/", allow_no_value=True, strict=False) 102 | parser.read(config_file or CONFIG_FILE) 103 | return parser 104 | 105 | 106 | CONFIG_FILE = get_config_file() 107 | conf = create_config() 108 | 109 | ###################################################### 110 | ### (Do not change any of these parameters unless ### 111 | ### you know exactly what you are doing. ### 112 | ###################################################### 113 | 114 | # TODO: Add variable to set certificate check to true or false 115 | 116 | # Papirus eInk size is 128 x 96 pixels 117 | WHITE = 1 118 | BLACK = 0 119 | PAPIRUSROT = 0 120 | if "papirus" in conf["atm"]["display"]: 121 | try: 122 | from papirus import Papirus 123 | PAPIRUS = Papirus(rotation=PAPIRUSROT) 124 | except ImportError: 125 | logger.warning("Papirus display library not installed.") 126 | print("Papirus library not installed.") 127 | sys.exit("Exiting...") 128 | 129 | # Display - Waveshare 2.13 V2 is 250 * 122 pixels 130 | elif "waveshare2in13v2" in conf["atm"]["display"]: 131 | try: 132 | from waveshare_epd import epd2in13_V2 133 | WAVESHARE = epd2in13_V2.EPD() 134 | except ImportError: 135 | logger.warning("Waveshare display library not installed.") 136 | print("Waveshare2in13v2 library not installed.") 137 | sys.exit("Exiting...") 138 | 139 | # Display - Waveshare 2.13 V3 is 250 * 122 pixels 140 | elif "waveshare2in13v3" in conf["atm"]["display"]: 141 | try: 142 | from waveshare_epd import epd2in13_V3 143 | WAVESHARE = epd2in13_V3.EPD() 144 | except ImportError: 145 | logger.warning("Waveshare display library not installed.") 146 | print("Waveshare2in13v3 library not installed.") 147 | sys.exit("Exiting...") 148 | 149 | # Display - Waveshare 2.13 (D) is 212 * 104 pixels 150 | elif "waveshare2in13d" in conf["atm"]["display"]: 151 | try: 152 | from waveshare_epd import epd2in13d 153 | WAVESHARE = epd2in13d.EPD() 154 | except ImportError: 155 | logger.warning("Waveshare display library not installed.") 156 | print("Waveshare2in13d library not installed.") 157 | sys.exit("Exiting...") 158 | 159 | # Display - Waveshare 2.66 is 296 * 152 pixels 160 | elif "waveshare2in66" in conf["atm"]["display"]: 161 | try: 162 | from waveshare_epd import epd2in66 163 | WAVESHARE = epd2in66.EPD() 164 | except ImportError: 165 | logger.warning("Waveshare display library not installed.") 166 | print("Waveshare2in66 library not installed.") 167 | sys.exit("Exiting...") 168 | 169 | # Display - Waveshare 2.7 is 264 * 176 pixels 170 | elif "waveshare2in7" in conf["atm"]["display"]: 171 | try: 172 | from waveshare_epd import epd2in7 173 | WAVESHARE = epd2in7.EPD() 174 | except ImportError: 175 | logger.warning("Waveshare display library not installed.") 176 | print("Waveshare2in7 library not installed.") 177 | sys.exit("Exiting...") 178 | 179 | # Display - Waveshare 2.9 (D) is 296 * 128 pixels 180 | elif "waveshare2in9d" in conf["atm"]["display"]: 181 | try: 182 | from waveshare_epd import epd2in9d 183 | WAVESHARE = epd2in9d.EPD() 184 | except ImportError: 185 | logger.warning("Waveshare display library not installed.") 186 | print("Waveshare2in9d library not installed.") 187 | sys.exit("Exiting...") 188 | 189 | # Display - Inky pHAT 190 | elif "inkyphat" in conf["atm"]["display"]: 191 | try: 192 | from inky.auto import auto 193 | 194 | WHITE = 0 195 | BLACK = 1 196 | INKY = auto(ask_user=True, verbose=True) 197 | INKY.set_border(INKY.WHITE) 198 | except ImportError: 199 | logger.warning("Inky display library not installed.") 200 | print("Inkyphat library not installed.") 201 | sys.exit("Exiting...") 202 | 203 | # This could help for testing purposes (e.g. tests/lnbits_lnurlw.py) 204 | elif "testing" in conf["atm"]["display"]: 205 | logger.warning("Display is in testing mode. This won't work in production.") 206 | 207 | # Display - No configuration match 208 | else: 209 | logger.warning("No display configuration match.") 210 | print("No display configuration matched.") 211 | sys.exit("Exiting...") 212 | 213 | # API URL for coingecko 214 | COINGECKO_URL_BASE = "https://api.coingecko.com/api/v3/" 215 | 216 | # Fiat and satoshi variables 217 | FIAT = 0 218 | SATS = 0 219 | SATSFEE = 0 220 | INVOICE = "" 221 | 222 | # Set btc and sat price 223 | BTCPRICE = utils.get_btc_price(conf["atm"]["cur"]) 224 | SATPRICE = (1 / (BTCPRICE * 100)) * 1e8 225 | 226 | # Button / Acceptor Pulses 227 | LASTIMPULSE = 0 228 | PULSES = 0 229 | LASTPUSHES = 0 230 | PUSHES = 0 231 | COINCOUNT = 0 232 | 233 | # State 234 | CONNECTIVITY=True 235 | 236 | # Determine different coin types 237 | COINTYPES={} 238 | try: 239 | for coin_type in conf['coins']['coin_types'].split("\n"): 240 | coin_type_pulses,coin_type_fiat,coin_type_name=coin_type.split(',') 241 | COINTYPES[int(coin_type_pulses)]={'fiat': coin_type_fiat, 'name': coin_type_name} 242 | except: 243 | print("Pulses not set in the new way, please update config.") 244 | sys.exit(2) 245 | 246 | -------------------------------------------------------------------------------- /data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/data/__init__.py -------------------------------------------------------------------------------- /displays/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/displays/__init__.py -------------------------------------------------------------------------------- /displays/messages.py: -------------------------------------------------------------------------------- 1 | # Text for update_startup_screen() 2 | startup_screen_1 = "Welcome to the" 3 | startup_screen_2 = "LightningATM" 4 | startup_screen_3 = "- please insert coins -" 5 | 6 | # Text for error_screen() 7 | error_screen_1 = "Error ocurred:" 8 | 9 | # Text for update_qr_request() 10 | qr_request_1 = "Please scan" 11 | qr_request_2 = "your invoice in" 12 | qr_request_3 = "Scanning..." 13 | qr_request_4 = "for " 14 | qr_request_5 = " sats." 15 | 16 | # Text for update_qr_failed() 17 | qr_failed_1 = "Scan failed." 18 | qr_failed_2 = "Try again." 19 | 20 | # Text for update_payout_screen() 21 | payout_screen_1 = " sats" 22 | payout_screen_2 = "on the way!" 23 | 24 | # text for update_payment_failed() 25 | payment_failed_1 = "Payment failed!" 26 | payment_failed_2 = "Please contact" 27 | payment_failed_3 = "operator." 28 | 29 | # Text for update_thankyou_screen() 30 | thankyou_screen_1 = "Enjoy your new" 31 | thankyou_screen_2 = "satoshis!!" 32 | thankyou_screen_3 = "#bitcoin #lightning" 33 | 34 | # Text for update_nocoin_screen() 35 | nocoin_screen_1 = "No coins added!" 36 | nocoin_screen_2 = "Please add" 37 | nocoin_screen_3 = "coins first" 38 | 39 | # Text for update_lnurl_generation() 40 | lnurl_generation_1 = "Generating" 41 | lnurl_generation_2 = "QR code to scan" 42 | 43 | # Text for update_shutdown_screen() 44 | shutdown_screen_1 = "ATM turned off!" 45 | shutdown_screen_2 = "Please contact" 46 | shutdown_screen_3 = "operator." 47 | 48 | # Text for update_wallet_scan() 49 | wallet_scan_1 = "Please scan" 50 | wallet_scan_2 = "your wallet" 51 | wallet_scan_3 = "credentials." 52 | 53 | # Text for update_lntxbot_balance() 54 | lntxbot_balance_1 = "Success!!" 55 | lntxbot_balance_2 = "Your current balance:" 56 | lntxbot_balance_3 = " sats" 57 | 58 | # Text for update_btcpay_lnd() 59 | btcpay_lnd_1 = "Success!!" 60 | btcpay_lnd_2 = "Successfuly scanned" 61 | btcpay_lnd_3 = "BTCPay LND Wallet." 62 | 63 | # Text for draw_lnurl_qr() 64 | lnurl_qr_1 = "Scan to" 65 | lnurl_qr_2 = "receive" 66 | 67 | # Text for update_amount_screen() 68 | amount_screen_1 = " sats" 69 | amount_screen_2 = "Rate" 70 | amount_screen_3 = "= " 71 | amount_screen_4 = " sats/" 72 | amount_screen_5 = "Fee" 73 | amount_screen_6 = "= " 74 | amount_screen_7 = "% (" 75 | amount_screen_8 = " sats)" 76 | 77 | # Text for update_lnurl_cancel_notice 78 | lnurl_cancel_notice_1 = "Prepare for LNURL" 79 | lnurl_cancel_notice_2 = "(Not LNURL compatible?" 80 | lnurl_cancel_notice_3 = "Press button to cancel)" 81 | 82 | # Text for update_button_fault() 83 | button_fault_1 = "Oh Sorry.." 84 | button_fault_2 = "Button fault.." 85 | button_fault_3 = "Try again!" 86 | 87 | # Text for update_wallet_fault() 88 | wallet_fault_1 = "Wallet fault.." 89 | wallet_fault_2 = "No wallet data." 90 | wallet_fault_3 = "Set it first!" 91 | -------------------------------------------------------------------------------- /displays/messages_cz.py: -------------------------------------------------------------------------------- 1 | # Text for update_startup_screen() 2 | startup_screen_1 = "Vítejte u" 3 | startup_screen_2 = "LightningATM" 4 | startup_screen_3 = "- prosím vložte mince -" 5 | 6 | # Text for error_screen() 7 | error_screen_1 = "Chyba:" 8 | 9 | # Text for update_qr_request() 10 | qr_request_1 = "Naskenujte" 11 | qr_request_2 = "fakturu za" 12 | qr_request_3 = "Skenujte..." 13 | qr_request_4 = "pro " 14 | qr_request_5 = " satu." 15 | 16 | # Text for update_qr_failed() 17 | qr_failed_1 = "Sken selhal." 18 | qr_failed_2 = "Zkuste znovu." 19 | 20 | # Text for update_payout_screen() 21 | payout_screen_1 = " satu" 22 | payout_screen_2 = "na cestě!" 23 | 24 | # text for update_payment_failed() 25 | payment_failed_1 = "Platba neprošla!" 26 | payment_failed_2 = "Kontaktujte" 27 | payment_failed_3 = "operátora." 28 | 29 | # Text for update_thankyou_screen() 30 | thankyou_screen_1 = "Užijte si" 31 | thankyou_screen_2 = "satoši!!" 32 | thankyou_screen_3 = "#bitcoin #lightning" 33 | 34 | # Text for update_nocoin_screen() 35 | nocoin_screen_1 = "Nebyli vhozeny mince!" 36 | nocoin_screen_2 = "Nejprve " 37 | nocoin_screen_3 = "vložte mince" 38 | 39 | # Text for update_lnurl_generation() 40 | lnurl_generation_1 = "Generuji" 41 | lnurl_generation_2 = "QR kod ke skenu" 42 | 43 | # Text for update_shutdown_screen() 44 | shutdown_screen_1 = "ATM vypnuté!" 45 | shutdown_screen_2 = "Prosím kontaktujte" 46 | shutdown_screen_3 = "operátora." 47 | 48 | # Text for update_wallet_scan() 49 | wallet_scan_1 = "Naskenujte" 50 | wallet_scan_2 = "údaje své" 51 | wallet_scan_3 = "peněženky." 52 | 53 | # Text for update_lntxbot_balance() 54 | lntxbot_balance_1 = "Úspěch!!" 55 | lntxbot_balance_2 = "Váš zůstatek:" 56 | lntxbot_balance_3 = " sat" 57 | 58 | # Text for update_btcpay_lnd() 59 | btcpay_lnd_1 = "Úspěch!!" 60 | btcpay_lnd_2 = "Úspěšně naskenována" 61 | btcpay_lnd_3 = "BTCPay LND Peněženka." 62 | 63 | # Text for draw_lnurl_qr() 64 | lnurl_qr_1 = "Skenujte" 65 | lnurl_qr_2 = "k přijetí" 66 | 67 | # Text for update_amount_screen() 68 | amount_screen_1 = " satu" 69 | amount_screen_2 = "Sazba" 70 | amount_screen_3 = "= " 71 | amount_screen_4 = " sats/" 72 | amount_screen_5 = "Poplatek" 73 | amount_screen_6 = "= " 74 | amount_screen_7 = "% (" 75 | amount_screen_8 = " satu)" 76 | 77 | # Text for update_lnurl_cancel_notice 78 | lnurl_cancel_notice_1 = "Připravit pro LNURL" 79 | lnurl_cancel_notice_2 = "(LNURL nekompatibilní?" 80 | lnurl_cancel_notice_3 = "Pro zrušení zmáčknete tlačítko)" 81 | 82 | # Text for update_button_fault() 83 | button_fault_1 = "Omlouváme se.." 84 | button_fault_2 = "Chyba tlačítka.." 85 | button_fault_3 = "Zkuste znovu!" 86 | 87 | # Text for update_wallet_fault() 88 | wallet_fault_1 = "Chyba peněženky.." 89 | wallet_fault_2 = "Chybí data peněženky." 90 | wallet_fault_3 = "Nejdříve nastavte!" 91 | -------------------------------------------------------------------------------- /displays/messages_de.py: -------------------------------------------------------------------------------- 1 | # Text for update_startup_screen() 2 | startup_screen_1 = "Willkommen beim" 3 | startup_screen_2 = "LightningATM" 4 | startup_screen_3 = "- Wirf eine Münze ein -" 5 | 6 | # Text for error_screen() 7 | error_screen_1 = "Fehler aufgetreten:" 8 | 9 | # Text for update_qr_request() 10 | qr_request_1 = "Bitte abscannen" 11 | qr_request_2 = "Deine Rechnung in" 12 | qr_request_3 = "Ich scanne..." 13 | qr_request_4 = "für " 14 | qr_request_5 = " Satoshis" 15 | 16 | # Text for update_qr_failed() 17 | qr_failed_1 = "Scan ungültig." 18 | qr_failed_2 = "Versuchs nochmal" 19 | 20 | # Text for update_payout_screen() 21 | payout_screen_1 = " Satoshis" 22 | payout_screen_2 = "auf dem Weg!" 23 | 24 | # text for update_payment_failed() 25 | payment_failed_1 = "Zahlung gescheitet" 26 | payment_failed_2 = "Kontaktiere" 27 | payment_failed_3 = "den Betreiber" 28 | 29 | # Text for update_thankyou_screen() 30 | thankyou_screen_1 = "Viel Spaß mit den" 31 | thankyou_screen_2 = "neuen Satoshis!" 32 | thankyou_screen_3 = "#bitcoin #lightning" 33 | 34 | # Text for update_nocoin_screen() 35 | nocoin_screen_1 = "Kein Münzeinwurf" 36 | nocoin_screen_2 = "Wirf erstmal" 37 | nocoin_screen_3 = "Münzen ein" 38 | 39 | # Text for update_lnurl_generation() 40 | lnurl_generation_1 = "Erstelle den QR" 41 | lnurl_generation_2 = "Code zum scannen" 42 | 43 | # Text for update_shutdown_screen() 44 | shutdown_screen_1 = "ATM außer Betrieb" 45 | shutdown_screen_2 = "Kontaktiere den" 46 | shutdown_screen_3 = "Betreiber!" 47 | 48 | # Text for update_wallet_scan() 49 | wallet_scan_1 = "Bitte scanne" 50 | wallet_scan_2 = "deine Wallet" 51 | wallet_scan_3 = "Informationen" 52 | 53 | # Text for update_lntxbot_balance() 54 | lntxbot_balance_1 = "Erfolg !!" 55 | lntxbot_balance_2 = "Dein Kontostand:" 56 | lntxbot_balance_3 = " Satoshis" 57 | 58 | # Text for update_btcpay_lnd() 59 | btcpay_lnd_1 = "Erfolg !!" 60 | btcpay_lnd_2 = "Scan erfolgreich" 61 | btcpay_lnd_3 = "BTCPay LND Wallet." 62 | 63 | # Text for draw_lnurl_qr() 64 | lnurl_qr_1 = "Scan to" 65 | lnurl_qr_2 = "receive" 66 | 67 | # Text for update_amount_screen() 68 | amount_screen_1 = " Satoshis" 69 | amount_screen_2 = "Rate" 70 | amount_screen_3 = "= " 71 | amount_screen_4 = " sats/" 72 | amount_screen_5 = "Gebühr" 73 | amount_screen_6 = "= " 74 | amount_screen_7 = "% (" 75 | amount_screen_8 = " sats)" 76 | 77 | # Text for update_lnurl_cancel_notice 78 | lnurl_cancel_notice_1 = "Vorbereitung LNURL" 79 | lnurl_cancel_notice_2 = "Nicht LNURL kompatibel?" 80 | lnurl_cancel_notice_3 = "Abbrechen Taste drücken!" 81 | 82 | # Text for update_button_fault() 83 | button_fault_1 = "Oh Nein.." 84 | button_fault_2 = "Taster Fehler.." 85 | button_fault_3 = "Versuchs nochmal!" 86 | 87 | # Text for update_wallet_fault() 88 | wallet_fault_1 = "Wallet Fehler.." 89 | wallet_fault_2 = "Keine Daten." 90 | wallet_fault_3 = "Bitte setzten!" 91 | -------------------------------------------------------------------------------- /displays/messages_es.py: -------------------------------------------------------------------------------- 1 | # Text for update_startup_screen() 2 | startup_screen_1 = "Bienvenido a" 3 | startup_screen_2 = "LightningATM" 4 | startup_screen_3 = "- ingresar monedas -" 5 | 6 | # Text for error_screen() 7 | error_screen_1 = "Error:" 8 | 9 | # Text for update_qr_request() 10 | qr_request_1 = "Escanea" 11 | qr_request_2 = "tu factura" 12 | qr_request_3 = "Escanenado..." 13 | qr_request_4 = "por " 14 | qr_request_5 = " sats." 15 | 16 | # Text for update_qr_failed() 17 | qr_failed_1 = "Escaneado fallido." 18 | qr_failed_2 = "Intenta nuevamente." 19 | 20 | # Text for update_payout_screen() 21 | payout_screen_1 = " sats" 22 | payout_screen_2 = "en camino!" 23 | 24 | # text for update_payment_failed() 25 | payment_failed_1 = "Pago fallido" 26 | payment_failed_2 = "Ponte en contacto" 27 | payment_failed_3 = "con el operador." 28 | 29 | # Text for update_thankyou_screen() 30 | thankyou_screen_1 = "Disfruta de tus" 31 | thankyou_screen_2 = "satoshis!!" 32 | thankyou_screen_3 = "#bitcoin #lightning" 33 | 34 | # Text for update_nocoin_screen() 35 | nocoin_screen_1 = "No has ingresado monedas" 36 | nocoin_screen_2 = "Ingresa" 37 | nocoin_screen_3 = "monedas primero" 38 | 39 | # Text for update_lnurl_generation() 40 | lnurl_generation_1 = "Generando" 41 | lnurl_generation_2 = "codigo QR para escanear" 42 | 43 | # Text for update_shutdown_screen() 44 | shutdown_screen_1 = "Cajero apagado" 45 | shutdown_screen_2 = "Ponte en contacto" 46 | shutdown_screen_3 = "con el operador." 47 | 48 | # Text for update_wallet_scan() 49 | wallet_scan_1 = "Escanea" 50 | wallet_scan_2 = "las credenciales" 51 | wallet_scan_3 = "de tu wallet." 52 | 53 | # Text for update_lntxbot_balance() 54 | lntxbot_balance_1 = "¡Éxito!" 55 | lntxbot_balance_2 = "Tu balance es de:" 56 | lntxbot_balance_3 = " sats" 57 | 58 | # Text for update_btcpay_lnd() 59 | btcpay_lnd_1 = "¡Éxito!" 60 | btcpay_lnd_2 = "Escaneado exitoso de" 61 | btcpay_lnd_3 = "BTCPay LND wallet." 62 | 63 | # Text for draw_lnurl_qr() 64 | lnurl_qr_1 = "Escanea para" 65 | lnurl_qr_2 = "recibir" 66 | 67 | # Text for update_amount_screen() 68 | amount_screen_1 = " sats" 69 | amount_screen_2 = "Tasa" 70 | amount_screen_3 = "= " 71 | amount_screen_4 = " sats/" 72 | amount_screen_5 = "Tarifa" 73 | amount_screen_6 = "= " 74 | amount_screen_7 = "% (" 75 | amount_screen_8 = " sats)" 76 | 77 | # Text for update_lnurl_cancel_notice 78 | lnurl_cancel_notice_1 = "Prepare for LNURL" 79 | lnurl_cancel_notice_2 = "¿No es LNURL compatible?" 80 | lnurl_cancel_notice_3 = "Presiona el botón para cancelar" 81 | 82 | # Text for update_button_fault() 83 | button_fault_1 = "Oh Sorry.." 84 | button_fault_2 = "Fallo en el botón" 85 | button_fault_3 = "Prueba nuevamente" 86 | 87 | # Text for update_wallet_fault() 88 | wallet_fault_1 = "Fallo en la wallet" 89 | wallet_fault_2 = "Sin datos de wallet" 90 | wallet_fault_3 = "Set it first!" 91 | -------------------------------------------------------------------------------- /displays/messages_fr.py: -------------------------------------------------------------------------------- 1 | # Text for update_startup_screen() 2 | startup_screen_1 = "Bienvenue au" 3 | startup_screen_2 = "LightningATM" 4 | startup_screen_3 = "- veuillez insérer des pièces -" 5 | 6 | # Text for error_screen() 7 | error_screen_1 = "Une erreur s'est produite:" 8 | 9 | # Text for update_qr_request() 10 | qr_request_1 = "Veuillez scanner" 11 | qr_request_2 = "votre facture" 12 | qr_request_3 = "Scan en cours..." 13 | qr_request_4 = "pour " 14 | qr_request_5 = " sats." 15 | 16 | # Text for update_qr_failed() 17 | qr_failed_1 = "Le scan a échoué." 18 | qr_failed_2 = "Veuillez réessayer." 19 | 20 | # Text for update_payout_screen() 21 | payout_screen_1 = " sats" 22 | payout_screen_2 = "en chemain!" 23 | 24 | # text for update_payment_failed() 25 | payment_failed_1 = "Le paiement a échoué!" 26 | payment_failed_2 = "Veuillez contacter" 27 | payment_failed_3 = "l'opérateur." 28 | 29 | # Text for update_thankyou_screen() 30 | thankyou_screen_1 = "Profitez de vos nouveaux" 31 | thankyou_screen_2 = "satoshis!!" 32 | thankyou_screen_3 = "#bitcoin #lightning" 33 | 34 | # Text for update_nocoin_screen() 35 | nocoin_screen_1 = "Pièces non ajoutées!" 36 | nocoin_screen_2 = "Veuillez ajouter" 37 | nocoin_screen_3 = "des pièces en premier" 38 | 39 | # Text for update_lnurl_generation() 40 | lnurl_generation_1 = "Génération du" 41 | lnurl_generation_2 = "QR code à scanner" 42 | 43 | # Text for update_shutdown_screen() 44 | shutdown_screen_1 = "ATM éteinte!" 45 | shutdown_screen_2 = "Veuillez contacter" 46 | shutdown_screen_3 = "l'opérateur." 47 | 48 | # Text for update_wallet_scan() 49 | wallet_scan_1 = "Veuillez scanner" 50 | wallet_scan_2 = "la référence de" 51 | wallet_scan_3 = "votre portefeuille." 52 | 53 | # Text for update_lntxbot_balance() 54 | lntxbot_balance_1 = "Succès!!" 55 | lntxbot_balance_2 = "Solde actuel:" 56 | lntxbot_balance_3 = " sats" 57 | 58 | # Text for update_btcpay_lnd() 59 | btcpay_lnd_1 = "Succès!!" 60 | btcpay_lnd_2 = "Scanné avec succès" 61 | btcpay_lnd_3 = "BTCPay LND Portefeuille." 62 | 63 | # Text for draw_lnurl_qr() 64 | lnurl_qr_1 = "Scanner pour" 65 | lnurl_qr_2 = "recevoir" 66 | 67 | # Text for update_amount_screen() 68 | amount_screen_1 = " sats" 69 | amount_screen_2 = "Taux" 70 | amount_screen_3 = "= " 71 | amount_screen_4 = " sats/" 72 | amount_screen_5 = "Frais" 73 | amount_screen_6 = "= " 74 | amount_screen_7 = "% (" 75 | amount_screen_8 = " sats)" 76 | 77 | # Text for update_lnurl_cancel_notice 78 | lnurl_cancel_notice_1 = "Preparation au LNURL" 79 | lnurl_cancel_notice_2 = "(Non compatible LNURL?" 80 | lnurl_cancel_notice_3 = "Appuyer sur le bouton pour annuler)" 81 | 82 | # Text for update_button_fault() 83 | button_fault_1 = "Oh Sorry.." 84 | button_fault_2 = "Button fault.." 85 | button_fault_3 = "Try again!" 86 | 87 | # Text for update_wallet_fault() 88 | wallet_fault_1 = "Wallet fault.." 89 | wallet_fault_2 = "No wallet data." 90 | wallet_fault_3 = "Set it first!" 91 | -------------------------------------------------------------------------------- /displays/messages_it.py: -------------------------------------------------------------------------------- 1 | # Text for update_startup_screen() 2 | startup_screen_1 = "Benvenuto al" 3 | startup_screen_2 = "LightningATM" 4 | startup_screen_3 = "- Inserisci una moneta -" 5 | 6 | # Text for error_screen() 7 | error_screen_1 = "Errore:" 8 | 9 | # Text for update_qr_request() 10 | qr_request_1 = "Scansiona" 11 | qr_request_2 = "la tua invoice" 12 | qr_request_3 = "Scansionando..." 13 | qr_request_4 = "per " 14 | qr_request_5 = " sats." 15 | 16 | # Text for update_qr_failed() 17 | qr_failed_1 = "Scansione fallita." 18 | qr_failed_2 = "Riprova." 19 | 20 | # Text for update_payout_screen() 21 | payout_screen_1 = " sats" 22 | payout_screen_2 = "in arrivo!" 23 | 24 | # text for update_payment_failed() 25 | payment_failed_1 = "Pagamento fallito!" 26 | payment_failed_2 = "Contatta" 27 | payment_failed_3 = "un operatore." 28 | 29 | # Text for update_thankyou_screen() 30 | thankyou_screen_1 = "Gotiditi i nuovi" 31 | thankyou_screen_2 = "satoshi!!" 32 | thankyou_screen_3 = "#bitcoin #lightning" 33 | 34 | # Text for update_nocoin_screen() 35 | nocoin_screen_1 = "Nessuna moneta!" 36 | nocoin_screen_2 = "Aggiungi delle" 37 | nocoin_screen_3 = "monete prima" 38 | 39 | # Text for update_lnurl_generation() 40 | lnurl_generation_1 = "Generando il" 41 | lnurl_generation_2 = "QRcode da scansionare" 42 | 43 | # Text for update_shutdown_screen() 44 | shutdown_screen_1 = "ATM spento!" 45 | shutdown_screen_2 = "Contatta" 46 | shutdown_screen_3 = "un operatore." 47 | 48 | # Text for update_wallet_scan() 49 | wallet_scan_1 = "Scansiona" 50 | wallet_scan_2 = "le credenziali" 51 | wallet_scan_3 = "del tuo wallet." 52 | 53 | # Text for update_lntxbot_balance() 54 | lntxbot_balance_1 = "Complimenti!!" 55 | lntxbot_balance_2 = "Saldo attuale:" 56 | lntxbot_balance_3 = " sats" 57 | 58 | # Text for update_btcpay_lnd() 59 | btcpay_lnd_1 = "Complimenti!!" 60 | btcpay_lnd_2 = "BTCPay LND Wallet" 61 | btcpay_lnd_3 = "scansionato con successo." 62 | 63 | # Text for draw_lnurl_qr() 64 | lnurl_qr_1 = "Scansiona per" 65 | lnurl_qr_2 = "ricevere" 66 | 67 | # Text for update_amount_screen() 68 | amount_screen_1 = " sats" 69 | amount_screen_2 = "Rate" 70 | amount_screen_3 = "= " 71 | amount_screen_4 = " sats/" 72 | amount_screen_5 = "Fee" 73 | amount_screen_6 = "= " 74 | amount_screen_7 = "% (" 75 | amount_screen_8 = " sats)" 76 | 77 | # Text for update_lnurl_cancel_notice 78 | lnurl_cancel_notice_1 = "Preparati per LNURL" 79 | lnurl_cancel_notice_2 = "(Non compatibile con LNURL?" 80 | lnurl_cancel_notice_3 = "Premi il bottone per annullare)" 81 | 82 | # Text for update_button_fault() 83 | button_fault_1 = "Oh Scusa.." 84 | button_fault_2 = "Anomalia del bottone.." 85 | button_fault_3 = "Riprova!" 86 | 87 | # Text for update_wallet_fault() 88 | wallet_fault_1 = "Anomalia wallet.." 89 | wallet_fault_2 = "Nessun dato del wallet." 90 | wallet_fault_3 = "Configuralo prima!" 91 | -------------------------------------------------------------------------------- /displays/messages_pt.py: -------------------------------------------------------------------------------- 1 | # Text for update_startup_screen() 2 | startup_screen_1 = "Welcome to the" 3 | startup_screen_2 = "LightningATM" 4 | startup_screen_3 = "- help translate to PT -" 5 | 6 | # Text for error_screen() 7 | error_screen_1 = "Error ocurred:" 8 | 9 | # Text for update_qr_request() 10 | qr_request_1 = "Please scan" 11 | qr_request_2 = "your invoice in" 12 | qr_request_3 = "Scanning..." 13 | qr_request_4 = "for " 14 | qr_request_5 = " sats." 15 | 16 | # Text for update_qr_failed() 17 | qr_failed_1 = "Scan failed." 18 | qr_failed_2 = "Try again." 19 | 20 | # Text for update_payout_screen() 21 | payout_screen_1 = " sats" 22 | payout_screen_2 = "on the way!" 23 | 24 | # text for update_payment_failed() 25 | payment_failed_1 = "Payment failed!" 26 | payment_failed_2 = "Please contact" 27 | payment_failed_3 = "operator." 28 | 29 | # Text for update_thankyou_screen() 30 | thankyou_screen_1 = "Enjoy your new" 31 | thankyou_screen_2 = "satoshis!!" 32 | thankyou_screen_3 = "#bitcoin #lightning" 33 | 34 | # Text for update_nocoin_screen() 35 | nocoin_screen_1 = "No coins added!" 36 | nocoin_screen_2 = "Please add" 37 | nocoin_screen_3 = "coins first" 38 | 39 | # Text for update_lnurl_generation() 40 | lnurl_generation_1 = "Generating" 41 | lnurl_generation_2 = "QR code to scan" 42 | 43 | # Text for update_shutdown_screen() 44 | shutdown_screen_1 = "ATM turned off!" 45 | shutdown_screen_2 = "Please contact" 46 | shutdown_screen_3 = "operator." 47 | 48 | # Text for update_wallet_scan() 49 | wallet_scan_1 = "Please scan" 50 | wallet_scan_2 = "your wallet" 51 | wallet_scan_3 = "credentials." 52 | 53 | # Text for update_lntxbot_balance() 54 | lntxbot_balance_1 = "Success!!" 55 | lntxbot_balance_2 = "Your current balance:" 56 | lntxbot_balance_3 = " sats" 57 | 58 | # Text for update_btcpay_lnd() 59 | btcpay_lnd_1 = "Success!!" 60 | btcpay_lnd_2 = "Successfuly scanned" 61 | btcpay_lnd_3 = "BTCPay LND Wallet." 62 | 63 | # Text for draw_lnurl_qr() 64 | lnurl_qr_1 = "Scan to" 65 | lnurl_qr_2 = "receive" 66 | 67 | # Text for update_amount_screen() 68 | amount_screen_1 = " sats" 69 | amount_screen_2 = "Rate" 70 | amount_screen_3 = "= " 71 | amount_screen_4 = " sats/" 72 | amount_screen_5 = "Fee" 73 | amount_screen_6 = "= " 74 | amount_screen_7 = "% (" 75 | amount_screen_8 = " sats)" 76 | 77 | # Text for update_lnurl_cancel_notice 78 | lnurl_cancel_notice_1 = "Prepare for LNURL" 79 | lnurl_cancel_notice_2 = "(Not LNURL compatible?" 80 | lnurl_cancel_notice_3 = "Press button to cancel)" 81 | 82 | # Text for update_button_fault() 83 | button_fault_1 = "Oh Sorry.." 84 | button_fault_2 = "Button fault.." 85 | button_fault_3 = "Try again!" 86 | 87 | # Text for update_wallet_fault() 88 | wallet_fault_1 = "Wallet fault.." 89 | wallet_fault_2 = "No wallet data." 90 | wallet_fault_3 = "Set it first!" 91 | -------------------------------------------------------------------------------- /displays/messages_tr.py: -------------------------------------------------------------------------------- 1 | # Text for update_startup_screen() 2 | startup_screen_1 = "Welcome to the" 3 | startup_screen_2 = "LightningATM" 4 | startup_screen_3 = "- help translate to TR -" 5 | 6 | # Text for error_screen() 7 | error_screen_1 = "Error ocurred:" 8 | 9 | # Text for update_qr_request() 10 | qr_request_1 = "Please scan" 11 | qr_request_2 = "your invoice in" 12 | qr_request_3 = "Scanning..." 13 | qr_request_4 = "for " 14 | qr_request_5 = " sats." 15 | 16 | # Text for update_qr_failed() 17 | qr_failed_1 = "Scan failed." 18 | qr_failed_2 = "Try again." 19 | 20 | # Text for update_payout_screen() 21 | payout_screen_1 = " sats" 22 | payout_screen_2 = "on the way!" 23 | 24 | # text for update_payment_failed() 25 | payment_failed_1 = "Payment failed!" 26 | payment_failed_2 = "Please contact" 27 | payment_failed_3 = "operator." 28 | 29 | # Text for update_thankyou_screen() 30 | thankyou_screen_1 = "Enjoy your new" 31 | thankyou_screen_2 = "satoshis!!" 32 | thankyou_screen_3 = "#bitcoin #lightning" 33 | 34 | # Text for update_nocoin_screen() 35 | nocoin_screen_1 = "No coins added!" 36 | nocoin_screen_2 = "Please add" 37 | nocoin_screen_3 = "coins first" 38 | 39 | # Text for update_lnurl_generation() 40 | lnurl_generation_1 = "Generating" 41 | lnurl_generation_2 = "QR code to scan" 42 | 43 | # Text for update_shutdown_screen() 44 | shutdown_screen_1 = "ATM turned off!" 45 | shutdown_screen_2 = "Please contact" 46 | shutdown_screen_3 = "operator." 47 | 48 | # Text for update_wallet_scan() 49 | wallet_scan_1 = "Please scan" 50 | wallet_scan_2 = "your wallet" 51 | wallet_scan_3 = "credentials." 52 | 53 | # Text for update_lntxbot_balance() 54 | lntxbot_balance_1 = "Success!!" 55 | lntxbot_balance_2 = "Your current balance:" 56 | lntxbot_balance_3 = " sats" 57 | 58 | # Text for update_btcpay_lnd() 59 | btcpay_lnd_1 = "Success!!" 60 | btcpay_lnd_2 = "Successfuly scanned" 61 | btcpay_lnd_3 = "BTCPay LND Wallet." 62 | 63 | # Text for draw_lnurl_qr() 64 | lnurl_qr_1 = "Scan to" 65 | lnurl_qr_2 = "receive" 66 | 67 | # Text for update_amount_screen() 68 | amount_screen_1 = " sats" 69 | amount_screen_2 = "Rate" 70 | amount_screen_3 = "= " 71 | amount_screen_4 = " sats/" 72 | amount_screen_5 = "Fee" 73 | amount_screen_6 = "= " 74 | amount_screen_7 = "% (" 75 | amount_screen_8 = " sats)" 76 | 77 | # Text for update_lnurl_cancel_notice 78 | lnurl_cancel_notice_1 = "Prepare for LNURL" 79 | lnurl_cancel_notice_2 = "(Not LNURL compatible?" 80 | lnurl_cancel_notice_3 = "Press button to cancel)" 81 | 82 | # Text for update_button_fault() 83 | button_fault_1 = "Oh Sorry.." 84 | button_fault_2 = "Button fault.." 85 | button_fault_3 = "Try again!" 86 | 87 | # Text for update_wallet_fault() 88 | wallet_fault_1 = "Wallet fault.." 89 | wallet_fault_2 = "No wallet data." 90 | wallet_fault_3 = "Set it first!" 91 | -------------------------------------------------------------------------------- /displays/papiruszero2in.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import time 4 | import math 5 | 6 | import config 7 | import utils 8 | 9 | from PIL import Image, ImageFont, ImageDraw 10 | 11 | from displays import messages 12 | from displays import messages_de 13 | from displays import messages_es 14 | from displays import messages_fr 15 | from displays import messages_it 16 | from displays import messages_pt 17 | from displays import messages_tr 18 | 19 | 20 | # Select language from config.ini. If no match => English 21 | if config.conf["atm"]["language"] == "de": 22 | messages = messages_de 23 | elif config.conf["atm"]["language"] == "es": 24 | messages = messages_es 25 | elif config.conf["atm"]["language"] == "fr": 26 | messages = messages_fr 27 | elif config.conf["atm"]["language"] == "it": 28 | messages = messages_it 29 | elif config.conf["atm"]["language"] == "pt": 30 | messages = messages_pt 31 | elif config.conf["atm"]["language"] == "tr": 32 | messages = messages_tr 33 | else: 34 | messages = messages 35 | 36 | 37 | def update_startup_screen(): 38 | """Show startup screen on eInk Display 39 | """ 40 | image, width, height, draw = init_screen(color=config.WHITE) 41 | 42 | draw.text( 43 | (20, 10), 44 | messages.startup_screen_1, 45 | fill=config.BLACK, 46 | font=utils.create_font("freemono", 18), 47 | ) 48 | draw.text( 49 | (10, 20), 50 | messages.startup_screen_2, 51 | fill=config.BLACK, 52 | font=utils.create_font("sawasdee", 30), 53 | ) 54 | draw.text( 55 | (7, 75), 56 | messages.startup_screen_3, 57 | fill=config.BLACK, 58 | font=utils.create_font("freemono", 14), 59 | ) 60 | 61 | config.PAPIRUS.display(image) 62 | config.PAPIRUS.update() 63 | 64 | def error_screen(message="ERROR"): 65 | """Error screen 66 | """ 67 | image, width, height, draw = init_screen(color=config.WHITE) 68 | 69 | draw.text( 70 | (20, 10), 71 | messages.error_screen_1, 72 | fill=config.BLACK, 73 | font=utils.create_font("freemono", 18), 74 | ) 75 | draw.text( 76 | (10, 20), 77 | message, 78 | fill=config.BLACK, 79 | font=utils.create_font("freemono", 14), 80 | ) 81 | 82 | config.PAPIRUS.display(image) 83 | config.PAPIRUS.update() 84 | 85 | 86 | def update_qr_request(): 87 | # initially set all white background 88 | image, width, height, draw = init_screen(color=config.WHITE) 89 | 90 | draw.rectangle( 91 | (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK 92 | ) 93 | draw.text( 94 | (25, 10), 95 | messages.qr_request_1, 96 | fill=config.BLACK, 97 | font=utils.create_font("freemono", 20), 98 | ) 99 | draw.text( 100 | (10, 30), 101 | messages.qr_request_2, 102 | fill=config.BLACK, 103 | font=utils.create_font("freemono", 20), 104 | ) 105 | 106 | config.PAPIRUS.display(image) 107 | config.PAPIRUS.update() 108 | 109 | for i in range(0, 3): 110 | draw.text( 111 | (80, 45), 112 | str(3 - i), 113 | fill=config.BLACK, 114 | font=utils.create_font("freemono", 50), 115 | ) 116 | config.PAPIRUS.display(image) 117 | config.PAPIRUS.partial_update() 118 | draw.rectangle((75, 50, 115, 90), fill=config.WHITE, outline=config.WHITE) 119 | time.sleep(1) 120 | 121 | draw.rectangle( 122 | (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK 123 | ) 124 | draw.text( 125 | (25, 10), 126 | messages.qr_request_3, 127 | fill=config.BLACK, 128 | font=utils.create_font("freemono", 20), 129 | ) 130 | draw.text( 131 | (15, 35), 132 | messages.qr_request_4 + str(math.floor(config.SATS)) + " sats.", 133 | fill=config.BLACK, 134 | font=utils.create_font("freemono", 20), 135 | ) 136 | config.PAPIRUS.display(image) 137 | config.PAPIRUS.partial_update() 138 | 139 | 140 | def update_qr_failed(): 141 | # initially set all white background 142 | image, width, height, draw = init_screen(color=config.WHITE) 143 | 144 | draw.rectangle( 145 | (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK 146 | ) 147 | 148 | draw.text( 149 | (25, 30), 150 | messages.qr_failed_1, 151 | fill=config.BLACK, 152 | font=utils.create_font("freemono", 20), 153 | ) 154 | draw.text( 155 | (25, 50), 156 | messages.qr_failed_2, 157 | fill=config.BLACK, 158 | font=utils.create_font("freemono", 20), 159 | ) 160 | 161 | config.PAPIRUS.display(image) 162 | config.PAPIRUS.partial_update() 163 | 164 | 165 | def update_payout_screen(): 166 | """Update the payout screen to reflect balance of deposited coins. 167 | Scan the invoice??? I don't think so! 168 | """ 169 | image, width, height, draw = init_screen(color=config.WHITE) 170 | 171 | draw.rectangle( 172 | (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK 173 | ) 174 | draw.text( 175 | (15, 30), 176 | str(math.floor(config.SATS)) + messages.payout_screen_1, 177 | fill=config.BLACK, 178 | font=utils.create_font("freemono", 20), 179 | ) 180 | draw.text( 181 | (15, 50), 182 | messages.payout_screen_2, 183 | fill=config.BLACK, 184 | font=utils.create_font("freemono", 15), 185 | ) 186 | 187 | config.PAPIRUS.display(image) 188 | config.PAPIRUS.update() 189 | 190 | # scan the invoice 191 | # TODO: I notice this is commented out, I presume this function should _not_ be 192 | # scanning a QR code on each update? 193 | # config.INVOICE = qr.scan() 194 | 195 | 196 | def update_payment_failed(): 197 | image, width, height, draw = init_screen(color=config.WHITE) 198 | 199 | draw.text( 200 | (15, 10), 201 | messages.payment_failed_1, 202 | fill=config.BLACK, 203 | font=utils.create_font("freemono", 19), 204 | ) 205 | draw.text( 206 | (25, 45), 207 | messages.payment_failed_2, 208 | fill=config.BLACK, 209 | font=utils.create_font("freemono", 17), 210 | ) 211 | draw.text( 212 | (45, 65), messages.payment_failed_3, fill=config.BLACK, font=utils.create_font("freemono", 17) 213 | ) 214 | 215 | config.PAPIRUS.display(image) 216 | config.PAPIRUS.update() 217 | 218 | 219 | def update_thankyou_screen(): 220 | image, width, height, draw = init_screen(color=config.WHITE) 221 | 222 | draw.text( 223 | (15, 10), 224 | messages.thankyou_screen_1, 225 | fill=config.BLACK, 226 | font=utils.create_font("freemono", 19), 227 | ) 228 | draw.text( 229 | (40, 35), 230 | messages.thankyou_screen_2, 231 | fill=config.BLACK, 232 | font=utils.create_font("freemono", 19), 233 | ) 234 | draw.text( 235 | (15, 70), 236 | messages.thankyou_screen_3, 237 | fill=config.BLACK, 238 | font=utils.create_font("freemono", 14), 239 | ) 240 | config.PAPIRUS.display(image) 241 | config.PAPIRUS.update() 242 | time.sleep(5) 243 | 244 | 245 | def update_nocoin_screen(): 246 | image, width, height, draw = init_screen(color=config.WHITE) 247 | 248 | draw.text( 249 | (15, 10), 250 | messages.nocoin_screen_1, 251 | fill=config.BLACK, 252 | font=utils.create_font("freemonobold", 18), 253 | ) 254 | draw.text( 255 | (30, 40), 256 | messages.nocoin_screen_2, 257 | fill=config.BLACK, 258 | font=utils.create_font("freemono", 20), 259 | ) 260 | draw.text( 261 | (30, 65), 262 | messages.nocoin_screen_3, 263 | fill=config.BLACK, 264 | font=utils.create_font("freemono", 20), 265 | ) 266 | 267 | config.PAPIRUS.display(image) 268 | config.PAPIRUS.update() 269 | 270 | 271 | def update_lnurl_generation(): 272 | image, width, height, draw = init_screen(color=config.WHITE) 273 | 274 | draw.rectangle( 275 | (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK 276 | ) 277 | draw.text( 278 | (30, 20), 279 | messages.lnurl_generation_1, 280 | fill=config.BLACK, 281 | font=utils.create_font("freemono", 20), 282 | ) 283 | draw.text( 284 | (10, 40), 285 | messages.lnurl_generation_2, 286 | fill=config.BLACK, 287 | font=utils.create_font("freemono", 20), 288 | ) 289 | 290 | config.PAPIRUS.display(image) 291 | config.PAPIRUS.partial_update() 292 | 293 | 294 | def update_shutdown_screen(): 295 | image, width, height, draw = init_screen(color=config.WHITE) 296 | 297 | draw.text( 298 | (20, 10), 299 | messages.shutdown_screen_1, 300 | fill=config.BLACK, 301 | font=utils.create_font("freemono", 18), 302 | ) 303 | draw.text( 304 | (25, 45), 305 | messages.shutdown_screen_2, 306 | fill=config.BLACK, 307 | font=utils.create_font("freemono", 17), 308 | ) 309 | draw.text( 310 | (45, 65), messages.shutdown_screen_3, fill=config.BLACK, font=utils.create_font("freemono", 17) 311 | ) 312 | 313 | config.PAPIRUS.display(image) 314 | config.PAPIRUS.update() 315 | 316 | 317 | def update_wallet_scan(): 318 | # initially set all white background 319 | image, width, height, draw = init_screen(color=config.WHITE) 320 | 321 | draw.rectangle( 322 | (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK 323 | ) 324 | draw.text( 325 | (35, 20), 326 | messages.wallet_scan_1, 327 | fill=config.BLACK, 328 | font=utils.create_font("freemono", 18), 329 | ) 330 | draw.text( 331 | (33, 40), 332 | messages.wallet_scan_2, 333 | fill=config.BLACK, 334 | font=utils.create_font("freemono", 18), 335 | ) 336 | draw.text( 337 | (35, 60), 338 | messages.wallet_scan_3, 339 | fill=config.BLACK, 340 | font=utils.create_font("freemono", 18), 341 | ) 342 | 343 | config.PAPIRUS.display(image) 344 | config.PAPIRUS.update() 345 | time.sleep(2) 346 | 347 | 348 | def update_lntxbot_balance(balance): 349 | # initially set all white background 350 | image, width, height, draw = init_screen(color=config.WHITE) 351 | 352 | draw.rectangle( 353 | (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK 354 | ) 355 | draw.text( 356 | (45, 15), 357 | messages.lntxbot_balance_1, 358 | fill=config.BLACK, 359 | font=utils.create_font("freemonobold", 20), 360 | ) 361 | draw.text( 362 | (10, 45), 363 | messages.lntxbot_balance_2, 364 | fill=config.BLACK, 365 | font=utils.create_font("freemono", 15), 366 | ) 367 | draw.text( 368 | (45, 65), 369 | str("{:,}".format(balance)) + messages.lntxbot_balance_3, 370 | fill=config.BLACK, 371 | font=utils.create_font("freemono", 18), 372 | ) 373 | 374 | config.PAPIRUS.display(image) 375 | config.PAPIRUS.update() 376 | time.sleep(3) 377 | 378 | 379 | def update_btcpay_lnd(): 380 | # initially set all white background 381 | image, width, height, draw = init_screen(color=config.WHITE) 382 | 383 | draw.rectangle( 384 | (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK 385 | ) 386 | draw.text( 387 | (45, 15), 388 | messages.btcpay_lnd_1, 389 | fill=config.BLACK, 390 | font=utils.create_font("freemonobold", 20), 391 | ) 392 | draw.text( 393 | (10, 45), 394 | messages.btcpay_lnd_2, 395 | fill=config.BLACK, 396 | font=utils.create_font("freemono", 15), 397 | ) 398 | draw.text( 399 | (15, 65), 400 | messages.btcpay_lnd_3, 401 | fill=config.BLACK, 402 | font=utils.create_font("freemono", 16), 403 | ) 404 | 405 | config.PAPIRUS.display(image) 406 | config.PAPIRUS.update() 407 | time.sleep(3) 408 | 409 | 410 | def draw_lnurl_qr(qr_img): 411 | """Draw a lnurl qr code on the e-ink screen 412 | """ 413 | image, width, height, draw = init_screen(color=config.BLACK) 414 | 415 | qr_img = qr_img.resize((96, 96), resample=0) 416 | 417 | draw = ImageDraw.Draw(image) 418 | draw.bitmap((0, 0), qr_img, fill=config.WHITE) 419 | draw.text( 420 | (110, 25), 421 | messages.lnurl_qr_1, 422 | fill=config.WHITE, 423 | font=utils.create_font("freemonobold", 16), 424 | ) 425 | draw.text( 426 | (110, 45), 427 | messages.lnurl_qr_2, 428 | fill=config.WHITE, 429 | font=utils.create_font("freemonobold", 16), 430 | ) 431 | 432 | config.PAPIRUS.display(image) 433 | config.PAPIRUS.update() 434 | 435 | 436 | def update_amount_screen(): 437 | """Update the amount screen to reflect new coins inserted 438 | """ 439 | image, width, height, draw = init_screen(color=config.WHITE) 440 | 441 | draw.rectangle( 442 | (2, 2, width - 2, height - 2), fill=config.WHITE, outline=config.BLACK 443 | ) 444 | draw.text( 445 | (11, 10), 446 | str("{:,}".format(math.floor(config.SATS))) + messages.amount_screen_1, 447 | fill=config.BLACK, 448 | font=utils.create_font("dotmbold", 27), 449 | ) 450 | draw.text( 451 | (13, 37), 452 | "%.2f" % round(config.FIAT, 2) + " " + config.conf["atm"]["cur"].upper(), 453 | fill=config.BLACK, 454 | font=utils.create_font("dotmbold", 19), 455 | ) 456 | draw.text( 457 | (11, 60), messages.amount_screen_2, fill=config.BLACK, font=utils.create_font("freemonobold", 14), 458 | ) 459 | draw.text( 460 | (60, 60), 461 | messages.amount_screen_3 462 | + "%.1f" % round(config.SATPRICE, 1) 463 | + messages.amount_screen_4 464 | + config.conf["atm"]["centname"], 465 | fill=config.BLACK, 466 | font=utils.create_font("freemonobold", 14), 467 | ) 468 | draw.text( 469 | (11, 75), messages.amount_screen_5, fill=config.BLACK, font=utils.create_font("freemonobold", 14), 470 | ) 471 | draw.text( 472 | (60, 75), 473 | messages.amount_screen_6 474 | + config.conf["atm"]["fee"] 475 | + messages.amount_screen_7 476 | + str(math.floor(config.SATSFEE)) 477 | + messages.amount_screen_8, 478 | fill=config.BLACK, 479 | font=utils.create_font("freemonobold", 14), 480 | ) 481 | 482 | config.PAPIRUS.display(image) 483 | config.PAPIRUS.partial_update() 484 | 485 | 486 | def update_lnurl_cancel_notice(): 487 | image, width, height, draw = init_screen(color=config.WHITE) 488 | 489 | draw.text( 490 | (8, 18), 491 | messages.lnurl_cancel_notice_1, 492 | fill=config.BLACK, 493 | font=utils.create_font("freemono", 18), 494 | ) 495 | draw.text( 496 | (11, 53), 497 | messages.lnurl_cancel_notice_2, 498 | fill=config.BLACK, 499 | font=utils.create_font("freemono", 14), 500 | ) 501 | draw.text( 502 | (10, 73), 503 | messages.lnurl_cancel_notice_3, 504 | fill=config.BLACK, 505 | font=utils.create_font("freemono", 14), 506 | ) 507 | 508 | config.PAPIRUS.display(image) 509 | config.PAPIRUS.update() 510 | 511 | 512 | def update_button_fault(): 513 | image, width, height, draw = init_screen(color=config.WHITE) 514 | 515 | draw.text( 516 | (15, 10), 517 | messages.button_fault_1, 518 | fill=config.BLACK, 519 | font=utils.create_font("freemonobold", 18), 520 | ) 521 | draw.text( 522 | (15, 40), 523 | messages.button_fault_2, 524 | fill=config.BLACK, 525 | font=utils.create_font("freemono", 20), 526 | ) 527 | draw.text( 528 | (15, 65), 529 | messages.button_fault_3, 530 | fill=config.BLACK, 531 | font=utils.create_font("freemono", 20), 532 | ) 533 | 534 | config.PAPIRUS.display(image) 535 | config.PAPIRUS.update() 536 | 537 | 538 | def update_wallet_fault(): 539 | image, width, height, draw = init_screen(color=config.WHITE) 540 | 541 | draw.text( 542 | (15, 10), 543 | messages.wallet_fault_1, 544 | fill=config.BLACK, 545 | font=utils.create_font("freemonobold", 18), 546 | ) 547 | draw.text( 548 | (15, 40), 549 | messages.wallet_fault_2, 550 | fill=config.BLACK, 551 | font=utils.create_font("freemono", 20), 552 | ) 553 | draw.text( 554 | (15, 65), 555 | messages.wallet_fault_3, 556 | fill=config.BLACK, 557 | font=utils.create_font("freemono", 20), 558 | ) 559 | 560 | config.PAPIRUS.display(image) 561 | config.PAPIRUS.update() 562 | 563 | 564 | def update_blank_screen(): 565 | image, width, height, draw = init_screen(color=config.WHITE) 566 | 567 | config.PAPIRUS.display(image) 568 | config.PAPIRUS.update() 569 | 570 | 571 | def init_screen(color): 572 | """Prepare the screen for drawing and return the draw variables 573 | """ 574 | image = Image.new("1", config.PAPIRUS.size, color) 575 | # Set width and height of screen 576 | width, height = image.size 577 | # prepare for drawing 578 | draw = ImageDraw.Draw(image) 579 | return image, width, height, draw 580 | -------------------------------------------------------------------------------- /docs/guide/autostart.md: -------------------------------------------------------------------------------- 1 | ## Autostart and start/stop the ATM 2 | 3 | ###### Install and activate the autostart service: 4 | 5 | ``` 6 | $ cd ~/LightningATM 7 | $ sudo cp LightningATM.service /etc/systemd/system/LightningATM.service 8 | $ sudo systemctl enable LightningATM.service 9 | $ sudo reboot 10 | ``` 11 | 12 | - From now on the ATM will start automatically after booting. 13 | 14 | ###### Commands to control the ATM: 15 | 16 | ``` 17 | $ sudo systemctl status LightningATM.service 18 | $ sudo systemctl stop LightningATM.service 19 | $ sudo systemctl start LightningATM.service 20 | ``` 21 | 22 | - `Note:` When the autostart service is once installed and activated, you only need to "start" the ATM if you previously stopped the ATM. 23 | - `Note:` If you want to observe the app.py in tmux, you musst `stop` the "LightningATM.service" manually, bevor you start the app.py in the tmux. Then restart the ATM with the "start" command or simply restart the entire ATM afterwards by unplugging the power and plugging it back in. 24 | 25 | --- 26 | 27 | #### [debugging and tmux](/docs/guide/tmux_monitoring.md) ᐊ previous | next ᐅ [information_and_tips](/docs/guide/information_and_tips.md) 28 | -------------------------------------------------------------------------------- /docs/guide/button.md: -------------------------------------------------------------------------------- 1 | ## Installation and test of the LED button 2 | 3 | The most sensible option for the LightningATM is probably the button. An LED button is a nice feature but not essential, a simple button will do. 4 | With a button you can trigger the confirmation for the withdrawal process manually and is not dependent on a time control after inserting a coin. 5 | That makes it a little more relaxed. You can also use the button to access additional options. 6 | 7 | 8 | 9 | When the start screen is displayed, the button has four functions depending on the number of button presses: 10 | 11 | 1 : Shows screen: "No coins added! Please add coins first" 12 | 3 : Simulates adding a coin (2 coin pulses, only for testing) 13 | 5 : Show all possible display messages once 14 | 7 : Shutdown the host machine 15 | 9 : Reset Wallet (!!!) an scan new wallet credentials with camera 16 | 17 | If coins have been inserted, the LED goes on and the button only has the function of starting the withdrawal process and generating the QR code or activating the camera for scanning. 18 | 19 | `Attention:` Nine pulses reset the wallet. If you don't have a camera to scan a new one, you'll have to set up a new wallet manually! 20 | 21 | --- 22 | 23 | ### Installation 24 | 25 | The button is relatively easy to wire. See [wiring](/docs/guide/wiring.md). A soldering iron may be required. The individual poles must be measured unless this is clearly stated in the documentation for the button. The button closes the contact and is therefore NO (normal open) switching. 26 | 27 | The LED is preferably designed for 3V. Slightly different voltages (e.g. 5V) can still work. Classic doorbell buttons often meet this requirement. 28 | It is important that you **not** connect the LED directly between LED+ and GND! The output current would overwhelm the Raspberry Pi and possibly destroy it. Therefore, a resistor must always be connected in series to limit the current. It's a bit dependent on the LED used, but 220 or 330 ohms is a guide. Of course without warranty and guarantee! DYOR 29 | 30 | Here is an [example](https://www.amazon.de/dp/B00UFNI47I/) for a small LED button that also fits well in the pocket version. 31 | 32 | 33 | 34 | Here is the back. Protects cable routing for less movement at solder joints and mark the cable color. 35 | 36 | 37 | 38 | ### Testing 39 | 40 | It is best to test the button and the LED once after the installation. We have prepared small programs for this purpose. Log in to the ATM via CLI as usual. To be on the safe side, terminate the ATM service so that there are no overlaps. 41 | 42 | $ sudo systemctl stop LightningATM.service 43 | 44 | Change to the following directory 45 | 46 | $ cd ~/LightningATM/tests 47 | 48 | #### Start the button test program 49 | 50 | $ sudo python3 button_test.py 51 | 52 | Here you can see the two button pushes as "event". 53 | 54 | 55 | 56 | #### Start the LED test program 57 | 58 | $ sudo python3 LED_test.py 59 | 60 | The LED program shows only that it is working and the LED should now flash. 61 | 62 | 63 | 64 | #### Do not forget 65 | 66 | Restart the ATM service 67 | 68 | $ sudo systemctl start LightningATM.service 69 | 70 | --- 71 | 72 | #### [option: language](/docs/guide/languages.md) ᐊ previous | next ᐅ [option: camera](/docs/guide/camera.md) 73 | -------------------------------------------------------------------------------- /docs/guide/camera.md: -------------------------------------------------------------------------------- 1 | ## Installation and testing the camera 2 | 3 | The camera is an optional feature and allows you to scan LNURL addresses. The 5MP Camera OV5647 is available from different manufacturers and with slightly different designs. 4 | To test the function of the camera you can take a picture and download it to you computer. 5 | 6 | ### Installation 7 | 8 | The ribbon cable is a bit special as the connectors on the camera and the Raspberry Pi are slightly different sizes. So the cable tapers. 9 | Before sliding the cable into the slot, the black latch must be released by gently pulling upwards. 10 | The cable can then simply be pushed in. The silver contacts must point away from the detent. See image! 11 | When the cable is centered, you can lock the latches in place by sliding them back slightly. 12 | 13 | connection 14 | 15 | 16 | 17 | ### Activate camera 18 | 19 | - Login to the Raspberry Pi 20 | 21 | $ ssh admin@192.168.x.x 22 | 23 | - Directory displayed: `pi@raspberrypi:~ $` 24 | 25 | - Call up the Raspi-Config and activate the camera 26 | 27 | $ sudo raspi-config 28 | 29 | - Choose: `Interfacing Options Configure connections to peripherals` 30 | - Choose: `Camera Enable/Disable connection to the Raspberry Pi Camera` 31 | - Confirm: `Would you like the camera interface to be enabled?` -> `\` 32 | - Confirm: `The camera interface is enabled` -> `\` 33 | - Go To: `\` 34 | - When ask for: `Would you like to reboot now?` -> `\` 35 | - If you have not already restarted from the menu, then restart manually to activate the changes 36 | 37 | $ sudo reboot 38 | 39 | raspi-config 40 | 41 | ![](../pictures/camera_raspi-config_1.png) 42 | ![](../pictures/camera_raspi-config_2.png) 43 | 44 | Your config menu can look slightly different, depends of you hard- and software 45 | 46 | ### Take a picture 47 | 48 | - Take a picture after logging in to the Raspberry Pi 49 | 50 | $ raspistill -v -o test.jpg 51 | 52 | Note: The -v is only optional and will show more display data. The -o is necessary to write the file. 53 | 54 | The recording lasts 5 seconds (time delay 5000 (ms)). You can shorten the recording time, but you should not make the time too short, otherwise the picture quality will suffer. One second (= 1000 ms) is definitely possible. The command then looks like: 55 | 56 | $ raspistill -v -o test.jpg -t 1000 57 | 58 | Further functions for a recording can be found in the help `$ raspistill --help` 59 | 60 | - Check if the image has been saved 61 | 62 | $ ls -l 63 | 64 | The file `test.jpg` should now appear in the list. 65 | 66 | - Check the directory structure 67 | 68 | $ pwd 69 | 70 | The directory should read: `/home/pi/` 71 | 72 | raspistill command 73 | 74 | 75 | 76 | ### Transfer the image to the computer 77 | 78 | - Open a *second terminal window* on the computer 79 | - Switch to a folder of your choice (example `C:\temp`) 80 | - The command to copy for a Windows system is 81 | 82 | $ scp pi@192.168.x.x:/home/pi/test.jpg . 83 | 84 | -> The password from the Raspberry Pi must be entered for comfirmation 85 | 86 | - If everything went well, the image was transferred to the computer and can now be vieweden 87 | 88 | - Note: The command is slightly different on a Mac or Linux system 89 | 90 | $ scp 'pi@192.168.x.x:/home/pi/test.jpg' ./ 91 | 92 | scp command 93 | 94 | 95 | 96 | Example of a recording 97 | 98 | The structure | The picture 99 | :-------------------------:|:-------------------------: 100 | ![](../pictures/camera_example_structure.jpg) | ![](../pictures/camera_example_image.jpg) 101 | 102 | Note: This camera was pretty cheap and has a variable lens that was pretty loose and difficult to adjust. You might want to use a better one for your project. 103 | 104 | ### Aditional hints 105 | 106 | - If you want to take several pictures, the pictures must be numbered consecutively. E.g. test1.jpg, test2.jpg etc. 107 | - Then you can transfer all images at once 108 | 109 | $ scp pi@192.168.x.x:/home/pi/*.jpg . or scp 'pi@192.168.x.x:/home/pi/*.jpg' ./ 110 | 111 | - You can do also videos. The command is `raspivid` 112 | 113 | --- 114 | 115 | #### [option: button](/docs/guide/button.md) ᐊ previous | next ᐅ [option: lockout relay](/docs/guide/relay.md) 116 | -------------------------------------------------------------------------------- /docs/guide/coin_validator.md: -------------------------------------------------------------------------------- 1 | ### Configure the coin validator (set 6 coins / e.g. 5 cents to 2 euros) 2 | 3 | - Set the switches on the coin validator to "NO" (contact normal open) and "Medium" (medium pulse speed). 4 | - Apply the 12V to the coin validator (without Raspberry Pi) => The coin validator will flash and show 0 (zero) 5 | - Press and hold simultaneously the ADD and MINUS buttons until A appears in the display 6 | - When B appears, switch back to A by ADD or MINUS 7 | - Press SET (slowly), then E will appear on the display 8 | - This indicates: You are in them menu to set the amount of differnet coin types 9 | - Set it to 6 (6 coins = 5 cents to 2 euros) with ADD (or MINUS) and press SET 10 | - Now the display shows H1 (for the first coin). The first of 6 LEDs has come on 11 | - Now specify how often the coin should be inserted for calibration 12 | - Set to 20 with ADD (or MINUS) and then press SET 13 | - The display now shows P1 for further settings of coin 1 and you can define the output signal 14 | - 5 cents = 2 pulses / 10 cents = 3 / 20 cents = 4 / 50 cents = 5 / 10 euros = 6 / 2 euros = 7 15 | - Set to 2 pulses (for 5 cents) with ADD (or MINUS) and then press SET 16 | - The last thing on the display is F1, which represents the accuracy of the coin recognition. The value 8 worked well 17 | - Set to 8 with ADD (or MINUS) and then press SET 18 | - The parameters for the first coin have now been set 19 | - Now the second LED is on and the display shows H2 20 | - Now repeat the same steps again for the second coin (e.g. 10 Cent) up to coin 6 vor (e.g. 2 Euro) 21 | - When all coins are set, all LEDs flash briefly to confirm and the display shows A again 22 | - After a short time, 0 (zero) appears again in the display 23 | 24 | ### Calibrating the coin validator 25 | 26 | - Press the SET button (slowly) twice 27 | - The first LED light and A1 will show in the display 28 | - Now insert the first coin (5 cents) 20 times 29 | - Use as many different coins as posible 30 | - Finally all LEDs flash to confirm and the display shows A2 31 | - Repeat for the remaining coin types 32 | - All LEDs flash briefly again to confirm and the display shows 0 (zero) again 33 | - The coin validator is now ready 34 | 35 | coin validator 36 | 37 | 38 | 39 | coin calibration 40 | ![coin calibration](../pictures/coin_validator_calibration.jpg) 41 | 42 | --- 43 | 44 | #### [voltage_converter](/docs/guide/voltage_converter.md) ᐊ previous | next ᐅ [wiring](/docs/guide/wiring.md) 45 | 46 | -------------------------------------------------------------------------------- /docs/guide/display.md: -------------------------------------------------------------------------------- 1 | ## Set up and test the display 2 | 3 | ### Update display software 4 | 5 | ``` 6 | $ cd 7 | $ git clone https://github.com/AxelHamburch/e-Paper/ 8 | $ cd ~/e-Paper/RaspberryPi*/python 9 | $ sudo python3 setup.py install 10 | ``` 11 | `Info:` Normally cloning is done from the current Waveshare repository [https://github.com/waveshare/e-Paper](https://github.com/waveshare/e-Paper), but the new version currently still has problems with the recommended ATM configuration. Therefore, an older backup of "AxelHamburch" is recommended here for the time being. 12 | 13 | ### Testing the display 14 | 15 | `Note:` This is special for the "Waveshare 2in13 V2". Yours may be different, **check your version** carefully! 16 | 17 | ``` 18 | $ cd ~/e-Paper/RaspberryPi_JetsonNano/python/examples 19 | $ sudo python3 ./epd_2in13_V2_test.py 20 | ``` 21 | 22 | If everything has been correctly connected and installed, the display will now show a demonstration and finally the screen will cleare. 23 | 24 | `Help:`If it dosn't work, try the D-Version (flexible): `$ sudo python3 ./epd_2in13d_test.py`. Another option is the display "Waveshare 2in13 V3", it is a new display. `$ sudo python3 ./epd_2in13_V3_test.py`. (Work is in progress to fully support the V3). 25 | If you have a display from waveshare not listed here, try to find the right display type in the [waveshare repository](https://github.com/waveshare/e-Paper/tree/master/RaspberryPi_JetsonNano/python/examples). 26 | 27 | display demo 28 | 29 | 30 | 31 | display test example 32 | 33 | 34 | 35 | Possible source of error 36 | 37 | `AttributeError: 'SpiDev' object has no attribute 'writebytes2'` 38 | 39 | 40 | 41 | Solution: 42 | 43 | ``` 44 | $ cd 45 | $ sudo rm -r e-Paper 46 | $ git clone https://github.com/AxelHamburch/e-Paper/ 47 | $ cd ~/e-Paper/RaspberryPi*/python 48 | $ sudo python3 setup.py install 49 | $ cd ~/e-Paper/RaspberryPi_JetsonNano/python/examples 50 | $ sudo python3 ./epd_2in13d_test.py # Change it to your version 51 | ``` 52 | 53 | `Note:` If it dose not work, check the wiring and your display version again. 54 | 55 | --- 56 | 57 | #### [sdcard_and_wifi](/docs/guide/sdcard_and_wifi.md) ᐊ previous | next ᐅ [edit_config](/docs/guide/edit_config.md) 58 | -------------------------------------------------------------------------------- /docs/guide/edit_config.md: -------------------------------------------------------------------------------- 1 | ## Setting the config.ini 2 | 3 | Basic settings are made in `config.ini`. 4 | 5 | - Login on Raspberry Pi via CLI: `ssh pi@192.168.x.x` 6 | 7 | ### Start ATM once to create the config.ini 8 | 9 | ``` 10 | $ cd ~/LightningATM/ 11 | $ ./app.py 12 | ``` 13 | 14 | - When you first start you will get an "No display configuration matched. Exiting...". 15 | 16 | 17 | 18 | - The config.ini has now been created in the background. 19 | - Now edit the config.ini as described next step to set the display and other configurations. 20 | 21 | `Note:` If the display is already set correctly, the process will not "Exiting..". You have to stop it with `CTRL+C`. 22 | 23 | ### Open the config.ini file 24 | 25 | ``` 26 | $ nano ~/.lightningATM/config.ini 27 | ``` 28 | 29 | - Don't be surprised, the spelling is really `~/.lightningATM/config.ini` with a dot and lowercase letters. 30 | 31 | ### Set the config.ini file 32 | 33 | #### Enter display type under `[atm]` 34 | 35 | ``` 36 | display = waveshare2in13v2 37 | ``` 38 | `Note:` **Please compare your display type carefully!** Yours may require different settings and an incorrect setting will not work. 39 | 40 | #### *Only for Pocket Version:* Delay time (set from 0 to 12 seconds) 41 | 42 | ``` 43 | payoutdelay = 12 44 | ``` 45 | 46 | - Its needed for the the pocket version, because it has no push button for confimrmation. The invoice will be generated automatically after 12 seconds. 47 | - Note: If you have a button version, you must keep the 0. 48 | 49 | #### Set activate wallet type / example LNbits 50 | 51 | - If you would like to set a BTCPayServer wallet, get further information here: [BTCPayServer](https://docs.lightningatm.me/lightningatm-setup/wallet-setup/lnd_btcpay) - NOT RECOMENDET - 52 | - To use the Blink wallet as the funding source continue here: [Blink](/docs/guide/set_up_a_blink_wallet.md) 53 | - Note: A quick guide how to set up an LNbits wallet find [here](/docs/guide/set_up_an_lnbits_wallet.md) 54 | 55 | For LNbits: 56 | 57 | ``` 58 | [atm] 59 | .. 60 | activewallet = lnbits 61 | ``` 62 | ``` 63 | [lnurl] 64 | lnurlproxy = inactive 65 | ``` 66 | 67 | For Blink: 68 | 69 | ``` 70 | [atm] 71 | .. 72 | activewallet = blink 73 | ``` 74 | ``` 75 | [lnurl] 76 | lnurlproxy = active 77 | ``` 78 | Note: leave `lnurlproxyurl = https://api.lightningatm.me/v1/lnurl` like it is. 79 | 80 | #### Under `[lnbits]` customize the data for the LNbits wallet 81 | 82 | ``` 83 | [lnbits] 84 | # api credentials 85 | url = https://legend.lnbits.com/api/v1 86 | apikey = 87 | # One of "invoice" or "lnurlw" 88 | method = lnurlw 89 | # only for lnurlw - millisseconds to redeem the lnurlw 90 | timeout = 90000 91 | ``` 92 | Note: Here you only need to specify the `apikey`. You get this from the [LNbits wallet](/docs/guide/set_up_an_lnbits_wallet.md), e.g. `apikey = 8682516eaf0c457...` 93 | 94 | #### For Blink wallet customize the data unter `[blink]` 95 | 96 | ``` 97 | [blink] 98 | graphql_endpoint = https://api.blink.sv/graphql 99 | api_key = blink_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx 100 | wallet_id = 5dxxxxxxxxxxxxxxxxxxxxx 101 | ``` 102 | Note: You can see how it works [here](/docs/guide/set_up_a_blink_wallet.md) 103 | 104 | #### Next, set the coins to the pulses 105 | 106 | - Each coin generates a specific number of pulses. Here 2 to 7 pulses. 107 | 108 | ``` 109 | [coins] 110 | # Pulsecount, fiat value, name 111 | coin_types = 2,0.05,5 eur cent 112 | 3,0.10,10 eur cent 113 | 4,0.20,20 eur cent 114 | 5,0.50,50 eur cent 115 | 6,1.00,1 eur 116 | 7,2.00,2 eur 117 | ``` 118 | 119 | - When you finshed with changings in the config.ini, save and exit the editor: `CTRL+x -> y -> ENTER` 120 | - `Note:` In the config.ini you can also change the currency and fees. 121 | 122 | config.ini part 1 (example) 123 | ![config.ini part 1](../pictures/edit_config_terminal_1.png) 124 | 125 | config.ini part 2 (example) 126 | ![config.ini part 2](../pictures/edit_config_terminal_2.png) 127 | 128 | #### Test the settings (or the entire ATM) once 129 | 130 | - Start the ATM again 131 | 132 | ``` 133 | $ cd ~/LightningATM/ 134 | $ ./app.py 135 | ``` 136 | 137 | - The display should now show `LightningATM` 138 | - If everything is entered correctly, the ATM should now be ready for use 139 | - Just test it with few coins 140 | - To stop the ATM just press `CTRL+C` 141 | - After a short time, `Manually Interrupted` is displayed and you can see on the display that the `ATM is turned off` 142 | 143 | display LightningATM 144 | 145 | 146 | 147 | display ATM turned off! 148 | 149 | 150 | 151 | - `Note:` If it doesn't work properly, you can run the debug logger with the command `tail -f ~/.lightningATM/debug.log` in a separate terminal window. We will show more information later in the chapter [`tmux`](/docs/guide/tmux_monitoring.md). 152 | - `Note:` How to give the ATM an autostart function, you will learn later in chapter [`autostart`](/docs/guide/autostart.md) 153 | - `Note:` If you have already set the autostart and also start the app.py manually as described here, the display will not work properly. Then first stop the service as described in [`autostart`](/docs/guide/autostart.md). 154 | 155 | --- 156 | 157 | #### [display](/docs/guide/display.md) ᐊ previous | next ᐅ [debugging and tmux](/docs/guide/tmux_monitoring.md) 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /docs/guide/information_and_tips.md: -------------------------------------------------------------------------------- 1 | 2 | ## Aditional information and tips 3 | 4 | - You can get more help in the Telegram group: [https://t.me/lightningatm_building](https://t.me/lightningatm_building) 5 | - When making entries in the terminal window (CLI = Command Line Interface), it is best to copy and paste the commands: 6 | 7 | > Copy the command to the clipboard with `STRG+C` and then point to the blinking cursor in the CLI window and press the right mouse button to paste the clipboard into the terminal window. (If it doesn't work, make the terminal window "activ" - with one left click on the window or even move the curser with tap space bar - befor you do the right click.) 8 | - If you get the error `bash: $: command not found`, you may have copied the dollar sign "$" in front of the command. Don't do that! 9 | - When logging in via SSH, make sure to use the correct user `pi`. If you accidentally use `admin` or make a typo, you will not see an error, but the password will not be accepted 10 | - You can change WiFi parameters with the command: `$ sudo nano /etc/wpa_supplicant/wpa_supplicant.conf` 11 | - A collection of the most used commands for quick access: 12 | 13 | ``` 14 | cd ~/LightningATM/ 15 | ./app.py 16 | nano ~/.lightningATM/config.ini 17 | tail -f ~/.lightningATM/debug.log 18 | sudo systemctl status LightningATM.service 19 | sudo systemctl stop LightningATM.service 20 | sudo systemctl start LightningATM.service 21 | ``` 22 | --- 23 | 24 | #### [autostart](/docs/guide/autostart.md) ᐊ previous | next ᐅ [option: language](/docs/guide/languages.md) 25 | -------------------------------------------------------------------------------- /docs/guide/languages.md: -------------------------------------------------------------------------------- 1 | ## How to set a another language 2 | 3 | The ATM offers the option of setting a different language via `config.ini`. That's relatively easy. But in order to make the whole thing look nice, we still have to manually adjust a few texts for our display. 4 | 5 | --- 6 | 7 | ### 1. Set language in config.ini 8 | 9 | To do this, you have to log in to the ATM via SSH `$ ssh pi@your-ip` and your password and then open the config.ini. 10 | 11 | $ nano ~/.lightningATM/config.ini 12 | 13 | The config.ini: 14 | 15 | 16 | 17 | Here you can select one of the languages. E.g. "language = de" for German. When you're done, save and exit the editor with `CTRL+x -> y -> ENTER`. 18 | 19 | Now restart the ATM with the new language setting 20 | 21 | $ sudo systemctl restart LightningATM 22 | 23 | ### 2. The fine adjustments 24 | 25 | You will probably not be entirely happy with the way the display looks. This is because the texts in the different languages ​​are of different lengths. The English language is also usually very short, which means that the translations often extend beyond the display and cut off words. Since the ATM supports different display types and we don't know many languages ​​will come, we can't set and fix all possibilities. So you have to set this up for your set yourself. 26 | 27 | #### 2.1 View all pages one by one 28 | 29 | If you have a button on your ATM, it's easy now. Simply press the button 5 times and all possible displays will be shown briefly once. 30 | 31 | If you don't have one, you have to start a small program. To do this, log in again and enter the following commands one after the other. 32 | 33 | $ cd ~/LightningATM/ 34 | $ python3 ./tests/displayalign.py 35 | 36 | -> The ATM will now show all possible displays and finally restart itself, if the autostart is still acitve. 37 | 38 | Now that you've seen all the display pages, you've probably noticed one or the other page where you might want to change the text position or the text size or maybe even the text itself. It's best to go through the whole thing again and take pictures of the displays you might want to change. You will then use these to identify the texts and make settings. 39 | 40 | #### 2.2 Change the text 41 | 42 | That's the easiest. Just go to the text message file an edit it. 43 | 44 | $ nano ~/LightningATM/displays/messages_de.py 45 | 46 | - If you want to change another language, just change the "messages_de.py" into "messages_es.py" for Spanish for example. 47 | - Info: The english version is called "messages.py". 48 | - After you are satisfied, save the changes and close the editor with `CTRL+x -> y -> ENTER`. Then test the whole thing again as described in point 2.1 or restart the ATM with `$ sudo systemctl restart LightningATM` to update and then press the button to see the messages again. 49 | - `One request:` If you think your translation works well and you want to help us, please copy and paste the entire text into a plain text file like "messages_xx.txt" and DM it to AxelHamburch from the Telegram Group. 🙏 50 | 51 | Example: messages_de.py 52 | 53 | 54 | 55 | #### 2.3 Alligne the text 56 | 57 | Now comes a slightly difficult part. You can adjust the font size and text position for each display. To do this, you first need to know which display type you have and secondly what the text block (function) is called that you want to edit. You can get the display type from "config.ini" and it is in between "display = ". For example: "waveshare2in13v2". The best way to get the name for the text block is from the text display messages. See point 2.2. This can be e.g. `# Text for update_startup_screen()`. The text block is therefore called `update_startup_screen()`. 58 | 59 | Now you going to edit the Python file for the display and look for the text block (function). 60 | 61 | $ nano ~/LightningATM/displays/waveshare2in13v2.py 62 | 63 | - `Note:` Swap the "waveshare2in13v2.py" to your display type you use. E.g. "waveshare2in13d.py" for the D version. 64 | 65 | Here is an example for the "update_shut_down" text block (function): 66 | 67 | 68 | 69 | The before and after pictures: 70 | 71 | before | after 72 | :-------------------------:|:-------------------------: 73 | ![](../pictures/languages_before.jpg) | ![](../pictures/languages_after.jpg) 74 | 75 | The alignment for the above before / after pictures: 76 | 77 | 78 | 79 | If you set everthing you think it will help, exit the editior with `CTRL+x -> y -> ENTER` and restart the ATM `$ sudo systemctl restart LightningATM` or got to point 2.1 and start the test program again to see the result. 80 | 81 | ### Annotation 82 | 83 | I hope this could help you and you are happy with the result. 💛 84 | 85 | `Note:` We recommend backing up your changes somewhere. Just copy the text or parts of it and save it somewhere convenient. If you update once the ATM, your changes are gone. So you can easily restore your settings again. 86 | 87 | `Note:` If you have problems and don't know where they come from, you can [update](/docs/guide/we_need_your_help.md) the ATM. Then everything except the config.ini is overwritten and is new. Everything should be fine again and you also have the latest version. 88 | 89 | --- 90 | 91 | #### [information_and_tips](/docs/guide/information_and_tips.md) ᐊ previous | next ᐅ [option: button](/docs/guide/button.md) 92 | -------------------------------------------------------------------------------- /docs/guide/parts_list.md: -------------------------------------------------------------------------------- 1 | ## Parts list 2 | 3 | - Raspberry Pi Zero W* / supported alternatives: 4 | 5 | - Raspberry Pi Zero WH (best option, with populated header) [link](https://www.google.com/search?q=Raspberry+Pi+Zero+WH) 6 | - Raspberry Pi Zero W (without header, you have to solder a header) [link](https://www.google.com/search?q=Raspberry+Pi+Zero+W) 7 | - Raspberry Pi Zero 2 W (possible, but you have to create an image from [scratch](/docs/guide/rpi_image_from_scratch.md)) [link](https://www.google.com/search?q=Raspberry+Pi+Zero+2+W) 8 | 9 | - Micro SD Card (16 - 64 GB tested) [link](https://www.google.com/search?q=Micro+SD+Card) 10 | - DC-DC Adjustable Step Up Power Module (5V->12V) e.g. [LM2587S](https://www.ebay.de/itm/DC-DC-Adjustable-Step-up-Boost-Power-Supply-LM2587S-3V-5V-12V-to-19V-24V-30V-36V-/402196830271) or [HW-637](https://www.amazon.de/Converter-verstellbar-Spannungsregler-Effizienz-Board-Green-1-gr%C3%BCn/dp/B07S5YH2MB) 11 | - Multi Coin Acceptor (programmable, e.g. [Coin Acceptor 616](https://www.google.com/search?q=Coin+Acceptor+616) or [this](https://de.aliexpress.com/item/1005002636710497.html)) 12 | - Display with HAT / supported alternatives: 13 | 14 | - PaPiRus Zero 2.0 "- ePaper pHAT 15 | - Inky pHAT 2.13inch ePaper/eInk/EPD 16 | - Waveshare 2.13inch e-Paper HAT [link](https://www.waveshare.com/wiki/2.13inch_e-Paper_HAT) 17 | - Waveshare 2.13inch e-Paper HAT (D) [link](https://www.waveshare.com/2.13inch-e-paper-hat-d.htm) (Recommendation for Pocket Edition!) 18 | - Waveshare 2.66inch e-Paper E-Ink Display Module + [HAT](https://www.waveshare.com/wiki/E-Paper_Driver_HAT) 19 | - Waveshare 2.7inch E-Ink display HAT [link](https://www.waveshare.com/wiki/2.7inch_e-Paper_HAT) 20 | 21 | - Micro USB cable [link](https://www.google.com/search?q=usb+2+micro+b) 22 | - Jumper cable [link](https://www.google.com/search?q=jumper+cable) 23 | - A good USB power supply. Power adapter or power pack with 2 amps. 24 | 25 | 26 | ### Opitional parts 27 | 28 | - Push Button (Recommended option!). Optionally with LED (3V) e.g. this [example](https://www.amazon.de/dp/B00UFNI47I/). For the LED you will also need a resistor. 29 | - Lockout Relay Module (Makes sense!) e.g. [HW-482](https://www.google.com/search?q=HW-482) or [KY-019](https://www.google.com/search?q=KY-019) 30 | - Raspberry Pi Camera 5MP OV5647 31 | - ATM Case (self printing [DXF files](https://github.com/21isenough/LightningATM/tree/master/resources/3dmodels) or [STL files for Pocket Edition](https://github.com/21isenough/LightningATM/tree/master/resources/3dmodels/LightningATM%20-%20Pocket%20Edition/STLs) plus [threaded insert](https://www.google.com/search?q=threaded+insert) or buy from [Fulmo](https://shop.fulmo.org/shop/)) 32 | 33 | ### Alternative a total construction set for the Pocket Edition from [Fulmo](https://shop.fulmo.org/product/the-lightning-atm-bitcoin-construction-set/) 34 | -------------------------------------------------------------------------------- /docs/guide/perform_update.md: -------------------------------------------------------------------------------- 1 | ## Instruction to update the ATM 📜🧐 2 | 3 | We have now added LNbits and Blink as a new funding source for the LightningATM. 🎉 4 | 5 | ### 1. Quick guide on how to set up an LNbits wallet or Blink wallet 6 | 7 | A quick guide how to set up an LNbits wallet find [here](/docs/guide/set_up_an_lnbits_wallet.md) 8 | 9 | A quick guide how to set up an Blink wallet find [here](/docs/guide/set_up_a_blink_wallet.md) 10 | 11 | ### 2. Update the LigthningATM 12 | 13 | Connect the ATM to the power supply and log in to your LightningATM via [Wifi/SSH](https://github.com/21isenough/LightningATM/blob/master/docs/guide/sdcard_and_wifi.md#carry-out-basic-software-settings-and-updates). You may find the IP in the network of your router. Open a command line editor and write the command `ssh pi@192.168.x.x`. Hopefully you still have the assigned password. We will now load the new repository on the ATM and activate it. You have to stop the `LightningATM.service` once, otherwise you will get a strange display and the ATM will not work properly. 14 | 15 | $ sudo systemctl stop LightningATM.service 16 | $ cd ~/LightningATM 17 | $ git pull 18 | 19 | Now your ATM is set to the new version. Next you have to configure it for the new wallet. 20 | 21 | ### 3. Edit the config.ini 22 | 23 | $ nano ~/.lightningATM/config.ini 24 | 25 | #### Add the following lines at the very end for LNbits and 🆕 for Blink 26 | 27 | ``` 28 | [lnbits] 29 | # api credentials 30 | url = https://legend.lnbits.com/api/v1 31 | apikey = 32 | # One of "invoice" or "lnurlw" 33 | method = lnurlw 34 | # only for lnurlw - millisseconds to redeem the lnurlw 35 | timeout = 90000 36 | 37 | [blink] 38 | graphql_endpoint = https://api.blink.sv/graphql 39 | api_key = blink_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx 40 | wallet_id = 5dxxxxxxxxxxxxxxxxxxxxx 41 | ``` 42 | 43 | `Note for LNbits:` Customize your `apikey = 8682516eaf0c457...` from the LNbis wallet. See [here](/docs/guide/set_up_an_lnbits_wallet.md) 44 | 45 | `Note for Blink:` Customize your `api_key` and `wallet_id` from Blink wallet. See [here](/docs/guide/set_up_a_blink_wallet.md). 46 | 47 | #### Change active wallet to LNbits 48 | 49 | [atm] 50 | .. 51 | activewallet = lnbits 52 | 53 | #### Or change active wallet to Blink 54 | 55 | [atm] 56 | .. 57 | activewallet = blink 58 | 59 | and 60 | 61 | [lnurl] 62 | lnurlproxy = active 63 | 64 | #### In case you have a very old config.ini. Check this too. 65 | 66 | [atm] 67 | .. 68 | # Set language: "en", "de", "fr", "it", "es", "pt", "tr" currently available 69 | language = en 70 | 71 | # Do you have a camera? "False" or "True" 72 | camera = False 73 | 74 | Example, see [here](https://github.com/21isenough/LightningATM/blob/master/example_config.ini). 75 | 76 | Save and exit editor: `CTRL+x` -> `y` -> `Enter` 77 | 78 | ### 4. Start ATM for testing 79 | 80 | $ cd LightningATM 81 | $ ./app.py 82 | 83 | - It takes a few seconds for the display to update.. 84 | - The ATM has started and you can use it normally or test the functions. 85 | - Stop the ATM with `CTRL+C` 86 | 87 | To debug: Launch a second terminal window, login with ssh and access logs with 88 | 89 | $ tail -f ~/.lightningATM/debug.log 90 | 91 | Log file to debug 92 | ![Log file to debug](../pictures/perform_update_logs.png) 93 | 94 | ### 5. Final step 95 | 96 | Restart the LightningATM service by power cycle 97 | 98 | $ sudo systemctl start LightningATM.service 99 | 100 | - Your ATM should now restart as usual 101 | 102 | #### [README](/README.md) ᐊ previous 103 | -------------------------------------------------------------------------------- /docs/guide/relay.md: -------------------------------------------------------------------------------- 1 | # Coin acceptor lockout relay / electromagnetism gate 2 | 3 | ## The electromagnetic gate 4 | 5 | The electromagnetic gate is a blocking pin that diverts the coins to the ejection. When the coin validator is de-energized, the pin always remains locked and ejects all coins. The blocking pin is only pulled back by a magnetic coil when the coins are correctly recognized. Only then do the coins fall through into fiat hell. This electromagnetic gate can be controlled or blocked from outside. The function can be used to exclude multiple impulses (in the case of fast coin insertion in a row) or coin insertion during a certain period of time (a display reading or with AMT offline). 6 | 7 | ## There are two ways to lock the electromagnetic gate 8 | 9 | 1. Via the "SET" input on the ATM. It is always "open / not connected" by default. The electromagnetic gate is only locked when these two contacts are short-circuited, or released when the contact is opened / not connected again. 10 | 2. The interruption of the coil control. To do this, however, you have to cut open the line and put a clamp in between so that you can loop in a contact there. 11 | 12 | Relay options 13 | 14 | 15 | 16 | Which option you choose is up to you. Option 1 is certainly the easiest. However, plugging in the cables can prove difficult. I finally decided on option 2 because the clamp connection seemed more stable than the plug connection. But it doesn't matter which variant you choose, you just have to make sure that you use the opener or the closer contact. 17 | 18 | ## The Relay Module 19 | 20 | A potential-free contact is required to switch the contact for the coin checker. A relay module with a changeover contact is ideal here. 21 | 22 | Relay | build in 23 | :-------------------------:|:-------------------------: 24 | ![](../pictures/relay_relay.jpg) | ![](../pictures/relay_build_in.jpg) 25 | 26 | For a special typ, see [parts_list](/docs/guide/parts_list.md). 27 | 28 | ## Wiring 29 | 30 | You can find the contacts / wiring for the relay here and in total [here](/docs/guide/wiring.md). 31 | 32 | When the relay is not energized, terminal 11 is the common contact and the contact to terminal 14 is open (NO = normal open). Terminal 12 is closed when unswitched (NC = normal close). If the coin acceptor is "released", then the whole thing is reversed. 33 | 34 | ## Relay Test 35 | 36 | If you want to test the relay, we have a small test program here. It toggles the relay on and off alternately every two seconds. After starting the ATM, log in to the AMT via SSH and gradually enter the following command, similar to the button and LED tests in the [button](/docs/guide/button.md) chapter. 37 | 38 | ``` 39 | $ sudo systemctl stop LightningATM.service 40 | $ cd ~/LightningATM/tests 41 | $ sudo python3 relay_test.py 42 | $ sudo systemctl start LightningATM.service 43 | ``` 44 | 45 | Relay test 46 | 47 | 48 | 49 | ## The result 50 | 51 | Once everything has been wired and is back in operation, you should notice the following: 52 | 53 | 1. ATM power off: All coins are ejected. 54 | 2. ATM Ready: Coins are accepted. 55 | 3. Coin inserted: Relay switches for a short time and ejects all new coins immediately. Only when the display has updated is the coin validator released again. 56 | 4. The ATM shows the QR code: The coin validator is blocked, the coins are ejected. 57 | 5. You have a different display than the start display (selection for versions with a button): The coin validator is blocked. 58 | 6. The ATM is out of service: The coin validator is blocked. 59 | 60 | 61 | If the ATM is ready but does not accept the coins, then check whether the relay switches is on (see LED) after the AMT has started up. If the relay works an you still can't insert coins, swap the terminals 12 and 14. 62 | 63 | --- 64 | 65 | #### [option: camera](/docs/guide/camera.md) ᐊ previous | next ᐅ [option: RPi image from scratch](/docs/guide/rpi_image_from_scratch.md) -------------------------------------------------------------------------------- /docs/guide/rpi_image_from_scratch.md: -------------------------------------------------------------------------------- 1 | ## Raspberry Pi image from scratch 2 | 3 | ### For example for the RPi Zero 2 or using new version of Raspibian as Bullseye in any other RPi. 4 | 5 | #### `Warning:` It is not recommended to use the RPi Zero 2 for the Lightning ATM. However, if you already have one, you can use this guide to get it working. 6 | 7 | The Raspberry Pi Zero 2 is not supported in the Raspberry "Stretch" version. The tried and tested "2019-04-08-raspbian-stretch-lightningatm.gz" image does not run on the Zero 2. Accordingly, a new image must be created that supports the RPi Zero 2. The current [Bullseye](https://en.wikipedia.org/wiki/Raspberry_Pi_OS) version (32-bit) does this. You can use Bullseye image, but still needs to be modified so that it can be used for the Lightning ATM. 8 | 9 | Unfortunately, Bullseye does not support all components directly. The Waveshare displays are direct compatible and for the PaPiRus displays you have to install the library. 10 | 11 | This is only a guide for creating a compatible version. Deviations are possible and maybe even necessary. For the best result, however, it is recommended to stick to the procedure. Otherwise, troubleshooting support will be difficult. Since this is an experimental setting, any guarantee or warranty is excluded. 12 | 13 | ### The individual steps: 14 | 15 | 1. Download and burn the raw image 16 | 2. Add SSH, userconf and Wifi data to the raw image 17 | 3. First start and first settings 18 | 4. Option: Install PaPiRus display library 19 | 5. Carry out updates and installations 20 | 6. Install and test the display 21 | 7. Create and edit the config.ini 22 | 8. Postprocessing 23 | 24 | --- 25 | 26 | ### 1. Download and burn the raw image 27 | 28 | Find the appropriate Raspberry Pi OS (32-bit) image from the raspberrypi.org [Archive](https://downloads.raspberrypi.org/raspios_armhf/images/) and download it. You can use a newer version, but the "2022-01-28-raspios-bullseye-armhf.zip" version was tested for this tutorial. Unpack the file and write the image with [balenaEtcher](https://www.balena.io/etcher/). When done, remove the microSD. 29 | 30 | ### 2. Add SSH, userconf and Wifi data to the raw image 31 | 32 | - Create an empty file named `SSH` in preparation. Make sure that the file has __no__ file extension such as `.txt`. It is just called `SSH` and will later activate the SSH connection on the Raspberry Pi. 33 | - Create a `wpa_supplicant.conf` file as described in chapter [sd card and wifi](/docs/guide/sdcard_and_wifi.md). This gives the Raspberry Pi the necessary information about your Wifi. 34 | - Create a file named `userconf` with __no__ file extension and add the next content: 35 | ``` 36 | pi:$6$AkXCaGAo9kHlKdQS$FOsYUzv6Ypm/QEw78HBidWQtT1n83T4IsQcmDsWdOfZDgHTd1HUoVT6c3VZ6WDHC36/OHt380mIFiMyHlAU8A/ 37 | ``` 38 | This content sets the password for the `pi` user to `password`. In newer versions as Bullseye the default password is not set automatically so we need to make this step. 39 | - Put the newly written microSD back into the computer slot and copy the three files into the "boot" directory. 40 | - Remove the microSD. It is now ready for the Raspberry Pi. 41 | 42 | ### 3. First start and first settings 43 | 44 | After starting the Zero 2 and waiting a few minutes. Then you can log in as described in the [sd card and wifi](/docs/guide/sdcard_and_wifi.md) chapter. 45 | 46 | - Change the password and remember it! 47 | 48 | $ passwd 49 | 50 | - Activate the SPI interface for the display. 51 | 52 | $ sudo raspi-config 53 | 54 | 55 | 56 | 57 | 58 | 59 | - Exit with "Finish". 60 | 61 | ### 4. Option: Install PaPiRus display library 62 | 63 | If you use a PaPiRus Display, you have to install the PaPiRus display library. If you have a Waveshare or other, you can cancel this step. 64 | 65 | $ curl -sSL https://pisupp.ly/papiruscode | sudo bash 66 | 67 | Set it to Python 3 68 | 69 | 70 | 71 | Choose your display size 72 | 73 | 74 | 75 | 76 | ### 5. Carry out updates and installations 77 | 78 | It's the same like described in chapter [sdcard_and_wifi](/docs/guide/sdcard_and_wifi.md). Run the following commands one by one. 79 | 80 | ``` 81 | $ sudo apt update && sudo apt upgrade 82 | $ git clone https://github.com/21isenough/LightningATM.git 83 | $ cd ~/LightningATM/ 84 | $ pip3 install -r requirements.txt 85 | ``` 86 | Note: When updating, you sometimes have to confirm with `y`. 87 | 88 | ### 6. Install and test the display 89 | 90 | Please refer to the [display](/docs/guide/display.md) chapter on docs. 91 | The only difference is that you can use the latest version of the Waveshare library when installing: 92 | ``` 93 | $ cd 94 | $ git clone https://github.com/waveshare/e-Paper 95 | $ cd ~/e-Paper/RaspberryPi*/python 96 | $ sudo python3 setup.py install 97 | ``` 98 | 99 | ### 7. Create and edit the config.ini 100 | 101 | Start the app.py once. 102 | 103 | 104 | 105 | The program is immediately aborted with "Exiting..." because the wrong display is still stored in the basic setting. But the config.ini was created in the background and you can open and edit it as described in chapter [edit config.ini](/docs/guide/edit_config.md). 106 | 107 | ### 8. Postprocessing 108 | 109 | Everything else is as usual and listed in the [README](/README.md). 110 | 111 | --- 112 | 113 | #### [option: lockout relay](/docs/guide/relay.md) ᐊ previous | next ᐅ [README](/README.md) 114 | -------------------------------------------------------------------------------- /docs/guide/sdcard_and_wifi.md: -------------------------------------------------------------------------------- 1 | ## Create SD card and write WiFi data to it 2 | 3 | ### Download the image and write it to the SD card 4 | 5 | - Download the Raspbian image "2019-04-08-raspbian-stretch-lightningatm.gz" from the LightningATM Docs page 6 | - [https://docs.lightningatm.me/lightningatm-setup/hardware-setup/assembly-and-software](https://docs.lightningatm.me/lightningatm-setup/hardware-setup/assembly-and-software) 7 | - Write SD card image with Balena Etcher 8 | - [https://www.balena.io/etcher/](https://www.balena.io/etcher/) 9 | - Take out the SD card and put it aside 10 | 11 | ### Set up WiFi for the Raspberry Pi 12 | 13 | Create a file named `wpa_supplicant.conf` with the following content: 14 | 15 | ``` 16 | ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev 17 | update_config=1 18 | country=DE 19 | 20 | network={ 21 | ssid="Home" 22 | scan_ssid=1 23 | psk="XXXXXXXXXXXX" 24 | id_str="Home" 25 | } 26 | 27 | network={ 28 | ssid="Mobil" 29 | scan_ssid=1 30 | psk="12345678" 31 | id_str="Mobil" 32 | } 33 | ``` 34 | 35 | - Change country code to your [area code](https://www.arubanetworks.com/techdocs/InstantWenger_Mobile/Advanced/Content/Instant%20User%20Guide%20-%20volumes/Country_Codes_List.htm) 36 | - The file contains two WiFi networks. "Home" for home use and the "Mobil" for mobility 37 | - "Mobil" can be you hotspot on your cell phone 38 | - Adjust the values for SSID and PSK as you like 39 | - More networks can be added if needed 40 | - Make sure the file ends with `.conf` and *not* with `.conf.txt` 41 | - In the basic setting, Windows has the habit of hiding known file type extensions 42 | - See "Folder Options/View/Advanced Settings" 43 | - -> "Hide extensions for known file types" must be deselected! 44 | - Slide the newly written SD card back into the slot of you computer 45 | - -> The BOOT directory of the SD card is displayed 46 | - Copy the new file `wpa_supplicant.conf` into it 47 | - Insert the SD card into Raspberry Pi 48 | - Note: After starting the Raspberry Pi for the first time, the ".conf" file disappears from the directory 49 | - It can then be edited later via CLI by the command: 50 | - `$ sudo nano /etc/wpa_supplicant/wpa_supplicant.conf` (the ssh connection is required, see next topic) 51 | 52 | ## Carry out basic software settings and updates 53 | 54 | - Check the wiring again and then apply voltage 55 | - Wait for a while and then look for the assigned WiFi IP in your own WiFi router 56 | - Login to Raspberry Pi via CLI: `ssh pi@192.168.x.x` 57 | - Confirm `The authenticity..` with `yes` 58 | - Enter `raspberry` in the password prompt 59 | - If you are logged in correctly you will now see: `pi@raspberrypi:~ $` 60 | 61 | #### Adjust password and please remember! 62 | 63 | ``` 64 | $ passwd 65 | ``` 66 | 67 | - Note: `$` stands for `pi@raspberrypi:~ $` in CLI and does not need to be typed 68 | 69 | #### Perform an update, clone the ATM Github and install necessary additional options 70 | 71 | ``` 72 | $ sudo apt update && sudo apt upgrade 73 | $ git clone https://github.com/21isenough/LightningATM.git 74 | $ cd LightningATM 75 | $ pip3 install -r requirements.txt 76 | ``` 77 | 78 | - Note: When updating, you sometimes have to confirm with `y` or `q` 79 | - If everything was installed, disconnect the power supply and log in again via SSH and the the new password 80 | 81 | --- 82 | 83 | #### [wiring](/docs/guide/wiring.md) ᐊ previous | next ᐅ [display](/docs/guide/display.md) 84 | -------------------------------------------------------------------------------- /docs/guide/set_up_a_blink_wallet.md: -------------------------------------------------------------------------------- 1 | # How to set up a Blink wallet 2 | 3 | This is a minimal guide to set up Blink (ex Bitcoin Beach Wallet) account as a funding source for the LightningATM.
4 | Get more info and download the app from [blink.sv](https://blink.sv). To learn more about the API visit [dev.blink.sv](https://dev.blink.sv) or discuss on the [Galoy Mattermost](https://chat.galoy.io). 5 | 6 | 7 | ## Get the credentials from the Blink Dashboard 8 | 9 | * open the [Blink Dashboard](https://dashboard.blink.sv) 10 | * log in with your email or phone number if you are using Blink already 11 | * if you have no account yet can create a new one by logging in with a phone number 12 | 13 | ### Wallet ID 14 | * once logged in to dashboard find the wallet IDs under the balances 15 | 16 | wallets 17 | 18 | * copy the BTC wallet ID to pay out from the BTC balance 19 | * copy the USD wallet ID to pay out from the Stablesats balance 20 | * paste the wallet ID into the config.ini file under the `[blink]` section 21 | 22 | ### API key 23 | 24 | * create a new key on the `API Keys` tab 25 | * give it a name and choose a `Scope` which permits to send payments (Read and Write) 26 | * the default expiry is 90 days 27 | 28 | api_key 29 | 30 | * copy the key (starting with `blink_` ) and paste it into the config.ini file under the `[blink]` section 31 | 32 | ## Set the config.ini file 33 | 34 | * in the `[atm]` section set the `activewallet` to `blink` 35 | 36 | ``` 37 | activewallet = blink 38 | ``` 39 | 40 | * in the `[lnurl]` section set the `lnurlproxy` to `active` 41 | 42 | ``` 43 | lnurlproxy = active 44 | ``` 45 | 46 | * the `[blink]` section must include these: 47 | 48 | ``` 49 | [blink] 50 | graphql_endpoint = https://api.blink.sv/graphql 51 | wallet_id = 5dxxxxxxxxxxxxxxxxxxxxx 52 | api_key = blink_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx 53 | ``` 54 | 55 | * A complete example of a `config.ini` using the pocket version of the ATM without a button with USD coins: 56 | ``` 57 | [atm] 58 | cur = usd 59 | centname = cent 60 | language = "en" 61 | camera = False 62 | fee = 0 63 | dangermode = on 64 | display = waveshare2in13v2 65 | activewallet = blink 66 | payoutdelay = 10 67 | 68 | [lnurl] 69 | lnurlproxy = active 70 | lnurlproxyurl = https://api.lightningatm.me/v1/lnurl 71 | 72 | [btcpay] 73 | url = 74 | 75 | [lnd] 76 | macaroon = 77 | verify = on 78 | 79 | [lntxbot] 80 | url = 81 | creds = 82 | 83 | [lnbits] 84 | url = https://legend.lnbits.com/api/v1 85 | apikey = 86 | method = lnurlw 87 | timeout = 90 88 | 89 | [blink] 90 | graphql_endpoint = https://api.blink.sv/graphql 91 | api_key = blink_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx 92 | wallet_id = 5dxxxxxxxxxxxxxxxxxxxxx 93 | 94 | [coins] 95 | coin_types = 2,0.01,1 usd cent 96 | 3,0.05,5 usd cent 97 | 4,0.10,10 usd cent 98 | 5,0.25,25 usd cent 99 | 6,1.00,1 usd 100 | ``` 101 | 102 | ## Notes 103 | * remember to top up the funding wallet with some sats 104 | * easiest to monitor the payouts if you are logged in to the Blink app on your phone 105 | * be aware when testing that paying the same account is not allowed (cannot withdraw from the LightningATM to the same account from which the credentials are used) 106 | * note the date of the expiry of the API key (valid for 90 days by default) and create a new one before the ATM stops working 107 | 108 | #### [edit_config.md](/docs/guide/edit_config.md) ᐊ previous 109 | -------------------------------------------------------------------------------- /docs/guide/set_up_an_lnbits_wallet.md: -------------------------------------------------------------------------------- 1 | ## Quick guide on how to set up an LNbits wallet 2 | 3 | - Go e.g. to the [ledgend.lnbits.com](https://legend.lnbits.com/) demo server 4 | - Assign a name and press `ADD A NEW WALLET` 5 | - Bookmark the page and/or copy and paste the URL to a safe place. This will log you in to your wallet. 6 | - Understand the warning! It is a demo server for testing. Keep only the necessary Satoshis in the wallet. 7 | - See a pop-up menu box on the right or bottom labeled `API info` and open it. 8 | - You should see `Admin key: 8682516eaf0c457...`. The number-letter combination is the 'apikey' that you will need afterwards. 9 | - But before that you need to install an extension. Go to `Extensions`, search for `LNURLw` and activate it with `ENABLE`. 10 | - Fund the wallet with a few thousand Satoshis. 11 | 12 | LNbits Wallet 13 | ![LNbits Wallet](../pictures/set_up_an_lnbits_wallet.png) 14 | 15 | #### [edit_config](/docs/guide/edit_config.md) ᐊ previous 16 | -------------------------------------------------------------------------------- /docs/guide/tmux_monitoring.md: -------------------------------------------------------------------------------- 1 | ## Monitoring system "tmux" to control the processes 2 | 3 | The easiest way to monitor multiple processes at the same time is to open multiple windows. So you can open one for the "app.py" and one for the debug logger `tail -f ~/.lightningATM/debug.log` and then display them side by side. But to watch both processes at the same time in one window, you can use "tmux" (terminal multiplexer). This allows the terminal window to be split vertically into two parts. 4 | 5 | ###### Install tmux 6 | 7 | ``` 8 | $ cd 9 | $ sudo apt install tmux 10 | ``` 11 | 12 | ###### Necessary commands for the terminal multiplexer (tmux) 13 | 14 | ``` 15 | CTRL + b -> % = split window 16 | CTRL + b -> right or left arrow = change the window 17 | CTRL + b -> CTRL + right or left arrow = move dividing line 18 | CTRL + b -> d = back to single window 19 | ``` 20 | 21 | ###### Start and use tmux 22 | 23 | ``` 24 | $ tmux 25 | ``` 26 | 27 | - Split tmux windows: `CTRL+b -> %` 28 | - Switch between left and right window: `CTRL + b -> right or left arrow` 29 | - If necessary, move the dividing line: `CTRL+b -> CTRL + arrow right or left` 30 | 31 | ###### Start the `app.py` process (ATM) in the left window 32 | 33 | ``` 34 | $ cd ~/LightningATM 35 | $ ./app.py 36 | ``` 37 | 38 | - `Note:` If you have already activated the autostart function, problems can arise if you start the app.py in the tmux window at the same time. Therefore, it is better to end the service `sudo systemctl stop LightningATM.service` for the tmux and activate it again later `sudo systemctl start LightningATM.service`. See next chapter [`autostart`](/docs/guide/autostart.md). 39 | 40 | ###### Start `debug.log` 41 | 42 | - Switch to the right window and paste or type 43 | 44 | ``` 45 | $ tail -f ~/.lightningATM/debug.log 46 | ``` 47 | 48 | - Back to single window: `CTRL+b -> d` 49 | 50 | Example tmux window 51 | 52 | ![tmux window](../pictures/tmux_monitoring_terminal.png) 53 | 54 | Two withdrawals were made here. Once 5 cents and once 10 cent. Left side you see the pulses. Right side you see the coins to it. 2 pulses = 5 Cent = 199 Sats and 3 pulses = 10 Cent = 399 Sats. After that the program was stopped manually with CTRL+c. 55 | 56 | --- 57 | 58 | #### [edit_config](/docs/guide/edit_config.md) ᐊ previous | next ᐅ [autostart](/docs/guide/autostart.md) 59 | 60 | -------------------------------------------------------------------------------- /docs/guide/voltage_converter.md: -------------------------------------------------------------------------------- 1 | ### Prepare the voltage converter 2 | 3 | - Cut the micro USB cable. Approx. 15 cm from the Micro USB connector 4 | - Strip both sides and solder together in parallel (++ / --) if you have a soldering iron, otherwise it has to be like this 5 | - Place both ends on the input of the voltage converter and screw tight 6 | - Connect the USB plug to the power pack or USB power pack 7 | - Set the voltmeter to the correct measuring range. At least 35V DC 8 | - Connect a voltmeter on the output side of the converter 9 | - Using a screwdriver, turn the small rotary potentiometer (see picture) several times (probably first to the left) until the voltage shows 12.1 volts 10 | - `Caution:` The initial voltage can be very high. It can be about 35 volts. So do not connect directly to the coin validator! 11 | - Prepare cables for the output side: 12 | - Connect plus (12V) and GND (0V) from the voltage converter to the coin validator 13 | - Join a second wire from GND of the voltage converter (output) to the terminal 25 of the Raspberry Pi 14 | - Check you wiring with pictures and [wiring diagrams](wiring.md) 15 | 16 | voltage converter poti 17 | 18 | 19 | 20 | --- 21 | 22 | #### [README](/README.md) ᐊ previous | next ᐅ [coin_validator](/docs/guide/coin_validator.md) 23 | -------------------------------------------------------------------------------- /docs/guide/we_need_your_help.md: -------------------------------------------------------------------------------- 1 | ## Stagnation is regression. Update your ATM! 📜🧐 2 | 3 | Many updates have been made in the last two months. 💪😅 Updates for more reliability and some nice features. See the [guidelines](https://github.com/21isenough/LightningATM#installation-guideline) for more information and try it out. 4 | 5 | --- 6 | ## Steps to update 7 | ### 1. Expand the config.ini with "language" and "camera" (If not allready done) 8 | 9 | Because we are also expanding the config.ini with this update, we unfortunately have to expand config.ini manually, otherwise we get an error message when starting the ATM. Expanding is not difficult and does not have to be undone later. First we logon again via SSH and stop the LightningATM service and call up the config.ini. 10 | 11 | $ sudo systemctl stop LightningATM.service 12 | $ nano ~/.lightningATM/config.ini 13 | 14 | Then we add the following text between `centname = cent` and `# Set the Fee in %` 15 | 16 | # Set language: "en", "de", "fr", "it", "es", "pt", "tr" currently available 17 | # Code 2 from https://www.science.co.il/language/Codes.php 18 | language = en 19 | 20 | # Do you have a camera? "False" or "True" 21 | camera = False 22 | 23 | 24 | 25 | - When you finshed with changings in the config.ini, save and exit the editor: `CTRL+x -> y -> ENTER` 26 | - `Help:` If you want to copy and paste the text, take the text to the clipboard and then place the cursor where you want to paste the text and right-click to paste. But important is just `language = en` and `camera = False`. 27 | 28 | ### 2. Update to the new version 29 | 30 | Logon via SSH and stop the LightningATM service, make a backup from directory LightningATM, clone the new Github to "temp", sync once from "temp" to "LightningAMT" and then delete the "temp" directory that is no longer needed. 31 | 32 | $ sudo systemctl stop LightningATM.service 33 | $ cp -r -v LightningATM LightningATM_Backup 34 | $ git clone --branch master https://github.com/21isenough/LightningATM.git temp 35 | $ rsync -a temp/ LightningATM/ 36 | $ sudo rm -r temp 37 | 38 | ### 3. Start and test the version 39 | 40 | $ cd LightningATM 41 | $ ./app.py 42 | 43 | - It takes a few seconds for the display to update, but then.. 44 | - The ATM has started and you can use it normally or test the functions. 45 | - Stop the ATM with `CTRL+C` 46 | - If something went wrong or you just want to watch the ATM, launch a second terminal window, login via ssh and call the debugger: `$ tail -f ~/.lightningATM/debug.log` 47 | - If you like this version? Just keep it and delete the Backup: `$ cd` and then `$ sudo rm -r LightningATM_Backup` 48 | 49 | ### 4. If you don't like this version and want to get rid of it 50 | 51 | Make the backup the major version again and then delete the backup. 52 | 53 | $ cd 54 | $ rsync -a LightningATM_Backup/ LightningATM/ 55 | $ sudo rm -r LightningATM_Backup 56 | 57 | - Everthing should now be as befor. Even the wallat data. 58 | - You can clean up the coinfig.ini, but you don't have to. 59 | 60 | ### 5. Final step 61 | 62 | Restart the LightningATM service 63 | 64 | $ sudo systemctl start LightningATM.service 65 | 66 | - Your ATM should now restart as usual 67 | - If you find some issues or have some suggestions call @AxelHamburch in the telegram group or on Github 68 | 69 | ## Thank you for your support! ❤️ 70 | -------------------------------------------------------------------------------- /docs/guide/wiring.md: -------------------------------------------------------------------------------- 1 | ### Hardware wiring 2 | 3 | - Wire the hardware according to the pictures, but do not yet install the ATM in the housing 4 | - Set the switch on the e-Paper Display HAT to A and 0 5 | - Be careful when plugging in the fine display ribbon cable! 6 | - The clamp has a small lock and must be released beforehand. See pictures! 7 | 8 | Wiring - Full Version 9 | ![Full Version](../pictures/wiring_fw.png) 10 | 11 | Wiring - Pocket Version 12 | ![Pocket Version](../pictures/wiring_pv.png) 13 | 14 | Clamp for ribbon cable 15 | 16 | 17 | 18 | Pocket version adapter cable 19 | ![Pocket version adapter cable](../pictures/wiring_pv_adapter_cable.jpg) 20 | 21 | Pocket version direct wiring 22 | ![Pocket version direct wiring](../pictures/wiring_pv_direct_wiring.jpg) 23 | 24 | Pocket version wiring build-in 25 | ![Pocket version wiring bild in](../pictures/wiring_pv_build-in.jpg) 26 | 27 | 28 | Pocket version wiring build-in with coin validator | Direct wiring, without adapter cable 29 | :-------------------------:|:-------------------------: 30 | ![](../pictures/wiring_pv_build-in_complet.jpg) | ![](../pictures/wiring_direct_wiring.jpg) 31 | 32 | --- 33 | 34 | #### [coin_validator](/docs/guide/coin_validator.md) ᐊ previous | next ᐅ [sdcard_and_wifi](/docs/guide/sdcard_and_wifi.md) 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/pictures/add_on_zero2_PaPiRus_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/add_on_zero2_PaPiRus_1.png -------------------------------------------------------------------------------- /docs/pictures/add_on_zero2_PaPiRus_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/add_on_zero2_PaPiRus_2.png -------------------------------------------------------------------------------- /docs/pictures/add_on_zero2_SPI_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/add_on_zero2_SPI_1.png -------------------------------------------------------------------------------- /docs/pictures/add_on_zero2_SPI_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/add_on_zero2_SPI_2.png -------------------------------------------------------------------------------- /docs/pictures/add_on_zero2_SPI_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/add_on_zero2_SPI_3.png -------------------------------------------------------------------------------- /docs/pictures/add_on_zero2_SPI_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/add_on_zero2_SPI_4.png -------------------------------------------------------------------------------- /docs/pictures/add_on_zero2_edit_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/add_on_zero2_edit_config.png -------------------------------------------------------------------------------- /docs/pictures/add_on_zero2_edit_qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/add_on_zero2_edit_qr.png -------------------------------------------------------------------------------- /docs/pictures/add_on_zero2_edit_utils.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/add_on_zero2_edit_utils.png -------------------------------------------------------------------------------- /docs/pictures/button_ATM.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/button_ATM.jpg -------------------------------------------------------------------------------- /docs/pictures/button_LED_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/button_LED_test.png -------------------------------------------------------------------------------- /docs/pictures/button_back.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/button_back.jpg -------------------------------------------------------------------------------- /docs/pictures/button_button_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/button_button_test.png -------------------------------------------------------------------------------- /docs/pictures/button_front.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/button_front.jpg -------------------------------------------------------------------------------- /docs/pictures/camera_example_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/camera_example_image.jpg -------------------------------------------------------------------------------- /docs/pictures/camera_example_structure.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/camera_example_structure.jpg -------------------------------------------------------------------------------- /docs/pictures/camera_installation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/camera_installation.jpg -------------------------------------------------------------------------------- /docs/pictures/camera_raspi-config_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/camera_raspi-config_1.png -------------------------------------------------------------------------------- /docs/pictures/camera_raspi-config_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/camera_raspi-config_2.png -------------------------------------------------------------------------------- /docs/pictures/camera_terminal_raspistill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/camera_terminal_raspistill.png -------------------------------------------------------------------------------- /docs/pictures/camera_terminal_scp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/camera_terminal_scp.png -------------------------------------------------------------------------------- /docs/pictures/coin_validator_calibration.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/coin_validator_calibration.jpg -------------------------------------------------------------------------------- /docs/pictures/coin_validator_closeup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/coin_validator_closeup.jpg -------------------------------------------------------------------------------- /docs/pictures/display_demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/display_demo.jpg -------------------------------------------------------------------------------- /docs/pictures/display_fault.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/display_fault.png -------------------------------------------------------------------------------- /docs/pictures/display_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/display_test.png -------------------------------------------------------------------------------- /docs/pictures/edit_config_display_ATM_off.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/edit_config_display_ATM_off.jpg -------------------------------------------------------------------------------- /docs/pictures/edit_config_display_ATM_on.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/edit_config_display_ATM_on.jpg -------------------------------------------------------------------------------- /docs/pictures/edit_config_first_call_exiting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/edit_config_first_call_exiting.png -------------------------------------------------------------------------------- /docs/pictures/edit_config_terminal_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/edit_config_terminal_1.png -------------------------------------------------------------------------------- /docs/pictures/edit_config_terminal_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/edit_config_terminal_2.png -------------------------------------------------------------------------------- /docs/pictures/languages_after.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/languages_after.jpg -------------------------------------------------------------------------------- /docs/pictures/languages_before.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/languages_before.jpg -------------------------------------------------------------------------------- /docs/pictures/languages_config.ini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/languages_config.ini.png -------------------------------------------------------------------------------- /docs/pictures/languages_edit01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/languages_edit01.png -------------------------------------------------------------------------------- /docs/pictures/languages_edit02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/languages_edit02.png -------------------------------------------------------------------------------- /docs/pictures/languages_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/languages_example.png -------------------------------------------------------------------------------- /docs/pictures/perform_update_logs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/perform_update_logs.png -------------------------------------------------------------------------------- /docs/pictures/readme_atm_pv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/readme_atm_pv.png -------------------------------------------------------------------------------- /docs/pictures/relay_build_in.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/relay_build_in.jpg -------------------------------------------------------------------------------- /docs/pictures/relay_contacts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/relay_contacts.png -------------------------------------------------------------------------------- /docs/pictures/relay_options.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/relay_options.jpg -------------------------------------------------------------------------------- /docs/pictures/relay_relay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/relay_relay.jpg -------------------------------------------------------------------------------- /docs/pictures/relay_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/relay_test.png -------------------------------------------------------------------------------- /docs/pictures/set_up_a_blink_wallet_api_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/set_up_a_blink_wallet_api_key.png -------------------------------------------------------------------------------- /docs/pictures/set_up_a_blink_wallet_wallets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/set_up_a_blink_wallet_wallets.png -------------------------------------------------------------------------------- /docs/pictures/set_up_an_lnbits_wallet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/set_up_an_lnbits_wallet.png -------------------------------------------------------------------------------- /docs/pictures/tmux_monitoring_terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/tmux_monitoring_terminal.png -------------------------------------------------------------------------------- /docs/pictures/voltage_converter_coin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/voltage_converter_coin.jpg -------------------------------------------------------------------------------- /docs/pictures/voltage_converter_poti.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/voltage_converter_poti.jpg -------------------------------------------------------------------------------- /docs/pictures/we_need_your_help_config.ini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/we_need_your_help_config.ini.png -------------------------------------------------------------------------------- /docs/pictures/wiring_clamp.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/wiring_clamp.jpg -------------------------------------------------------------------------------- /docs/pictures/wiring_direct_wiring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/wiring_direct_wiring.jpg -------------------------------------------------------------------------------- /docs/pictures/wiring_fw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/wiring_fw.png -------------------------------------------------------------------------------- /docs/pictures/wiring_pv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/wiring_pv.png -------------------------------------------------------------------------------- /docs/pictures/wiring_pv_adapter_cable.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/wiring_pv_adapter_cable.jpg -------------------------------------------------------------------------------- /docs/pictures/wiring_pv_build-in.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/wiring_pv_build-in.jpg -------------------------------------------------------------------------------- /docs/pictures/wiring_pv_build-in_complet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/wiring_pv_build-in_complet.jpg -------------------------------------------------------------------------------- /docs/pictures/wiring_pv_direct_wiring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/docs/pictures/wiring_pv_direct_wiring.jpg -------------------------------------------------------------------------------- /example_config.ini: -------------------------------------------------------------------------------- 1 | [atm] 2 | # Set your fiat currency with the three letter 3 | # currency code (https://www.xe.com/symbols.php) 4 | cur = eur 5 | 6 | # Define what a cent is called in the currency 7 | # of your choice for price display (singular). 8 | centname = cent 9 | 10 | # Set language: "en", "de", "fr", "it", "es", "pt", "tr" currently available 11 | # Code 2 from https://www.science.co.il/language/Codes.php 12 | language = en 13 | 14 | # Do you have a camera? "False" or "True" 15 | camera = False 16 | 17 | # Set the Fee in % 18 | fee = 2 19 | 20 | # dangermode=on will save lnd Macaroon and lntxbot credential to this config file 21 | # dangermode=off will wipe any saved credentials and require them to be entered each 22 | # time the ATM starts 23 | dangermode = on 24 | 25 | # Define what screen you are using with the ATM 26 | # Current options are: 27 | # display = papiruszero2in 28 | # display = waveshare2in13v2 29 | # display = waveshare2in13v3 30 | # display = waveshare2in13d 31 | # display = waveshare2in66 32 | # display = waveshare2in7 33 | # display = waveshare2in9d 34 | # display = inkyphat 35 | 36 | display = 37 | 38 | # Automatically set during initial setup to LND or LNTXBOT 39 | # Current options are: 40 | # activewallet = btcpay_lnd 41 | # activewallet = lnbits 42 | # activewallet = blink 43 | activewallet = 44 | 45 | # By default, the ATM only makes payouts when the button is pressed. 46 | # If you set this value higher than 0, it will automatically start 47 | # a payout after the specified amount of seconds have passed 48 | # Standard 12 = 12 seconds 49 | payoutdelay = 12 50 | 51 | [lnurl] 52 | # Be aware of the privacy implications when communicating over a third party server 53 | # Make use of the LNURLProxyAPI (https://github.com/21isenough/LNURLProxyAPI) 54 | # Set this variable to active, to activate LNURLProxyAPI 55 | lnurlproxy = inactive 56 | # Use your own LNURLProxyAPI by replacing the following URL 57 | # Default is the publicly accessible API by @21isneough 58 | lnurlproxyurl = https://api.lightningatm.me/v1/lnurl 59 | 60 | [btcpay] 61 | url = 62 | 63 | [lnd] 64 | # HEX encoded LND macaroon 65 | macaroon = 66 | verify = on 67 | 68 | [lntxbot] 69 | # base64 encoded lntxbot api credentials 70 | # lntxbot will be deleted soon 71 | url = 72 | creds = 73 | 74 | [lnbits] 75 | # api credentials 76 | url = https://legend.lnbits.com/api/v1 77 | apikey = 78 | # One of "invoice" or "lnurlw" 79 | method = lnurlw 80 | # only for lnurlw - seconds to redeem the lnurlw 81 | timeout = 90 82 | 83 | [blink] 84 | # Learn more about the Blink API at https://dev.blink.sv 85 | graphql_endpoint = https://api.blink.sv/graphql 86 | # Find your wallet ID at https://dashboard.blink.sv under the balances 87 | # Choose BTC to pay out from the BTC balance 88 | # Choose USD to pay out from the Stablesats balance 89 | wallet_id = 90 | # Generate an API key in the dashboard (starts with: blink_...) 91 | api_key = 92 | # Set lnurlproxy = active in the [lnurl] section 93 | 94 | [coins] 95 | # Pulsecount, fiat value, name 96 | coin_types = 2,0.05,5 eur cent 97 | 3,0.10,10 eur cent 98 | 4,0.20,20 eur cent 99 | 5,0.50,50 eur cent 100 | 6,1.00,1 eur 101 | 7,2.00,2 eur 102 | -------------------------------------------------------------------------------- /lnbits.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import json 4 | import os.path 5 | import logging 6 | import time 7 | import requests 8 | import math 9 | 10 | import config 11 | 12 | # import display 13 | display_config = config.conf["atm"]["display"] 14 | try: 15 | display = getattr(__import__("displays", fromlist=[display_config]), display_config) 16 | except AttributeError: 17 | if config.conf["atm"]["display"] == "testing": 18 | pass 19 | else: 20 | raise Exception(''' 21 | Display type not found. Please check your config.ini file. 22 | ''') 23 | 24 | logger = logging.getLogger("LNBITS") 25 | 26 | 27 | # TODO: Remove display calls from here to app.py 28 | # TODO: Add the "verify=False" param to all post and get requests for local api queries 29 | 30 | 31 | class InvoiceDecodeError(BaseException): 32 | pass 33 | 34 | 35 | def payout(amt, payment_request): 36 | """Attempts to pay a BOLT11 invoice 37 | """ 38 | data = { 39 | "out": "true", 40 | "amount": amt, 41 | "bolt11": payment_request, 42 | } 43 | 44 | response = requests.post( 45 | str(config.conf["lnbits"]["url"]) + "/payments", 46 | headers={"X-Api-Key": str(config.conf["lnbits"]["apikey"])}, 47 | data=json.dumps(data), 48 | ) 49 | res_json = response.json() 50 | 51 | if res_json.get("detail"): 52 | errormessage = res_json.get("detail") 53 | logger.error("Payment failed (%s)" % errormessage) 54 | print("Error: " + res_json.get("detail")) 55 | 56 | 57 | def last_payment(payment_request): 58 | """Returns whether the last payment attempt succeeded or failed 59 | """ 60 | url = str(config.conf["lnbits"]["url"]) + "/payments?limit=1" 61 | response = requests.get( 62 | url, 63 | headers={"X-Api-Key": str(config.conf["lnbits"]["apikey"])}, 64 | ) 65 | res_json = response.json() 66 | res_json = res_json[0] 67 | 68 | if (res_json["bolt11"] == payment_request) and ( 69 | res_json["pending"] == False 70 | ): 71 | logger.info("Payment succeeded") 72 | print("Payment succeeded") 73 | return True 74 | else: 75 | logger.info("Payment failed") 76 | print("Payment failed") 77 | return False 78 | 79 | 80 | def decode_request(payment_request): 81 | """Decodes a BOLT11 invoice 82 | """ 83 | data = { 84 | "data": payment_request, 85 | } 86 | 87 | response = requests.post( 88 | str(config.conf["lnbits"]["url"]) + "/payments/decode", 89 | headers={"X-Api-Key": str(config.conf["lnbits"]["apikey"])}, 90 | data=json.dumps(data), 91 | ) 92 | 93 | # successful response 94 | if response.status_code != 200: 95 | raise InvoiceDecodeError( 96 | "Invoice {} got bad decode response {}".format( 97 | payment_request, response.text 98 | ) 99 | ) 100 | res_json = response.json() 101 | if "lnbc1" in payment_request: 102 | print("Zero sat invoice") 103 | return 0 104 | else: 105 | return int(res_json["amount_msat"]/1000) 106 | 107 | def lnurlp_request(lnurlp): 108 | """Decodes a BOLT11 invoice 109 | """ 110 | data = { 111 | "data": lnurlp.upper(), 112 | } 113 | 114 | response = requests.post( 115 | str(config.conf["lnbits"]["url"]) + "/payments/decode", 116 | headers={"X-Api-Key": str(config.conf["lnbits"]["apikey"])}, 117 | data=json.dumps(data), 118 | ) 119 | 120 | # successful response 121 | if response.status_code != 200: 122 | raise InvoiceDecodeError( 123 | "LNURLp {} got bad decode response {}".format( 124 | response.text 125 | ) 126 | ) 127 | 128 | res_json = response.json() 129 | 130 | if res_json.get("message"): 131 | errormessage = res_json.get("message") 132 | logger.error("LNURLp request failed (%s)" % errormessage) 133 | print("Error: " + res_json.get("message")) 134 | else: 135 | response = requests.get( 136 | res_json["domain"], 137 | ) 138 | res_json = response.json() 139 | 140 | if "kind" in res_json["callback"]: 141 | res_json["callback"] = res_json["callback"].replace('?kind=', '') 142 | 143 | callback = res_json["callback"] + "?amount=" + str(math.floor(config.SATS * 1000)) 144 | response = requests.get( 145 | callback, 146 | ) 147 | res_json = response.json() 148 | return res_json["pr"] 149 | 150 | 151 | def handle_invoice(): 152 | """Decode a BOLT11 invoice. Ensure that amount is correct or 0, then attempt to 153 | make the payment. 154 | """ 155 | decode_req = decode_request(config.INVOICE) 156 | if decode_req in (math.floor(config.SATS), 0): 157 | payout(config.SATS, config.INVOICE) 158 | result = last_payment(config.INVOICE) 159 | 160 | if result: 161 | display.update_thankyou_screen() 162 | else: 163 | display.update_payment_failed() 164 | time.sleep(120) 165 | else: 166 | print("Please show correct invoice") 167 | 168 | 169 | def evaluate_scan(qrcode): 170 | """Evaluates the scanned qr code for Lightning invoices. 171 | """ 172 | if not qrcode: 173 | logger.error("QR code scanning failed") 174 | return False 175 | # check for a lightning invoice 176 | else: 177 | if "lnbc" in qrcode.lower(): 178 | logger.info("Lightning invoice detected") 179 | invoice = qrcode.lower() 180 | # if invoice preceded with "lightning:" then chop it off so that we can 181 | # handle it correctly 182 | if "lightning:" in invoice: 183 | invoice = invoice[10:] 184 | return invoice 185 | elif "lnurl" in qrcode.lower(): 186 | logger.info("LNURL detected") 187 | lnurl = qrcode.lower() 188 | if "lightning:" in lnurl: 189 | lnurl = lnurl[10:] 190 | invoice = lnurlp_request(lnurl) 191 | return invoice 192 | else: 193 | logger.error("This QR does not contain a Lightning invoice") 194 | return False 195 | 196 | def create_lnurlw(): 197 | """Creates a LNURL withdraw link which can only be used once 198 | it returns the json-response 199 | This call does NOT handle displaying the QR code 200 | """ 201 | data = { 202 | "title": "ATM withdraw "+ str(config.SATS) + " sats for " + str(config.FIAT), 203 | "min_withdrawable": config.SATS, 204 | "max_withdrawable": config.SATS, 205 | "uses": 1, 206 | "wait_time": 1, 207 | "is_unique": True, 208 | # "webhook_url": "string", 209 | # "webhook_headers": "string", 210 | # "webhook_body": "string", 211 | # "custom_url": "string" 212 | } 213 | # compatibility with the way it's used for invoice-creation 214 | base_url = config.conf["lnbits"]["url"].replace("/api/v1", "") 215 | url = base_url + "/withdraw/api/v1/links" 216 | logger.info("LNURL withdraw link creation request: %s" % url) 217 | response = requests.post( 218 | url, 219 | headers={"X-Api-Key": str(config.conf["lnbits"]["apikey"])}, 220 | data=json.dumps(data), 221 | ) 222 | res_json = response.json() 223 | # You get something like this: 224 | # { 225 | # "id":"gCnZ9y", 226 | # "wallet":"some wallet id", 227 | # "title":"a title", 228 | # "min_withdrawable":5, 229 | # "max_withdrawable":5, 230 | # "uses":1,"wait_time":20, 231 | # "is_unique":true, 232 | # "unique_hash":"4N7P4vGK76XHW5cypGjLej", 233 | # "k1":"CcGJdDivSUiryXgvtGqPEB", 234 | # "open_time":1681561687, 235 | # "used":0, 236 | # "usescsv":"0", 237 | # "number":0, 238 | # "webhook_url":null,"webhook_headers":null,"webhook_body":null,"custom_url":null, 239 | # "lnurl":"LNURL1DP68GURN8GHJ7MRWVF..." 240 | # } 241 | 242 | if res_json.get("detail"): 243 | errormessage = res_json.get("detail") 244 | logger.error("LNURL withdraw failed (%s)" % errormessage) 245 | print("Error: " + errormessage) 246 | else: 247 | return res_json 248 | 249 | def wait_for_lnurlw_redemption(id, timeout=90): 250 | """ Waits until the lnurlw with the id (created by create_lnurlw) is used. 251 | timeouts after 90 seconds (default) 252 | returns True/False depending on success 253 | """ 254 | # compatibility with the way it's used for invoice-creation 255 | base_url = config.conf["lnbits"]["url"].replace("/api/v1", "") 256 | 257 | logger.info("Waiting to get lnurlw to be used: "+id) 258 | start_time = time.time() 259 | # loop while the user is redeeming the lnurlw 260 | while True and (time.time() < start_time + timeout): 261 | response = requests.get( 262 | url = base_url + "/withdraw/api/v1/links/" + id, 263 | headers={"X-Api-Key": str(config.conf["lnbits"]["apikey"])} 264 | ) 265 | res_json = response.json() 266 | # You get something like this: 267 | # { 268 | # "id":"4oDpCv", 269 | # "wallet":"someWalletId", 270 | # "title":"LightningATM withdraw", 271 | # "min_withdrawable":5, 272 | # "max_withdrawable":5, 273 | # "uses":1, 274 | # ... 275 | # "used":0, <-- this is the important one 276 | # ... 277 | # "lnurl":"LNURL1DP68GURN8GHJ7MRWVF5HGUEWV4EX26T8DE5HX6R0WF5H5MMWWSH8S7T69AMKJARGV3EXZAE0V9CXJTMKXYHKCMN4WFKZ7DENFF8RS4ENX4CKYUTRXFTXX5JKD4VXK6Z99AG8JWTEFEXRVJJTGDH5ZDPHG9ZK7VMHW3CH580AJCY"} 278 | if res_json.get("detail"): 279 | errormessage = res_json.get("detail") 280 | logger.error("LNURL withdraw failed (%s)" % errormessage) 281 | print("Error: " + errormessage) 282 | return False 283 | else: 284 | if res_json["used"] == 1: 285 | logger.info("LNURL withdraw was used") 286 | return True 287 | time.sleep(3) 288 | logger.error("LNURL withdraw failed (within timeout ) for lnurl " + res_json['lnurl']) 289 | return False 290 | 291 | -------------------------------------------------------------------------------- /lndrest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import json 4 | import os.path 5 | import logging 6 | import time 7 | import requests 8 | import math 9 | 10 | import config 11 | 12 | # import display 13 | display_config = config.conf["atm"]["display"] 14 | display = getattr(__import__("displays", fromlist=[display_config]), display_config) 15 | 16 | logger = logging.getLogger("LNDREST") 17 | 18 | verify = True 19 | if(config.conf["lnd"]["verify"].lower() == "off"): 20 | verify = False 21 | if(config.conf["lnd"]["verify"].lower() != "off"): 22 | verify = config.conf["lnd"]["verify"] 23 | 24 | # TODO: Remove display calls from here to app.py 25 | 26 | class InvoiceDecodeError(BaseException): 27 | pass 28 | 29 | 30 | def payout(amt, payment_request): 31 | """Attempts to pay a BOLT11 invoice 32 | """ 33 | data = { 34 | "payment_request": payment_request, 35 | "amt": math.floor(amt), 36 | } 37 | 38 | response = requests.post( 39 | str(config.conf["btcpay"]["url"]) + "/channels/transactions", 40 | headers={"Grpc-Metadata-macaroon": str(config.conf["lnd"]["macaroon"])}, 41 | data=json.dumps(data), 42 | verify=verify, 43 | ) 44 | res_json = response.json() 45 | 46 | if res_json.get("payment_error"): 47 | errormessage = res_json.get("payment_error") 48 | logger.error("Payment failed (%s)" % errormessage) 49 | print("Error: " + res_json.get("payment_error")) 50 | 51 | 52 | def last_payment(payment_request): 53 | """Returns whether the last payment attempt succeeded or failed 54 | """ 55 | url = str(config.conf["btcpay"]["url"]) + "/payments" 56 | 57 | data = { 58 | "include_incomplete": True, 59 | } 60 | 61 | response = requests.get( 62 | url, 63 | headers={"Grpc-Metadata-macaroon": str(config.conf["lnd"]["macaroon"])}, 64 | data=json.dumps(data), 65 | verify=verify, 66 | ) 67 | 68 | json_data = response.json() 69 | payment_data = json_data["payments"] 70 | _last_payment = payment_data[-1] 71 | 72 | if (_last_payment["payment_request"] == payment_request) and ( 73 | _last_payment["status"] == "SUCCEEDED" 74 | ): 75 | logger.info("Payment succeeded") 76 | print("Payment succeeded") 77 | return True 78 | else: 79 | logger.info("Payment failed") 80 | print("Payment failed") 81 | return False 82 | 83 | 84 | def decode_request(payment_request): 85 | """Decodes a BOLT11 invoice 86 | """ 87 | if payment_request: 88 | url = str(config.conf["btcpay"]["url"]) + "/payreq/" + str(payment_request) 89 | response = requests.get( 90 | url, 91 | headers={"Grpc-Metadata-macaroon": config.conf["lnd"]["macaroon"]}, 92 | verify=verify, 93 | ) 94 | # successful response 95 | if response.status_code != 200: 96 | raise InvoiceDecodeError( 97 | "Invoice {} got bad decode response {}".format( 98 | payment_request, response.text 99 | ) 100 | ) 101 | json_data = response.json() 102 | if "lnbc1" in payment_request: 103 | print("Zero sat invoice") 104 | return 0 105 | else: 106 | return int(json_data["num_satoshis"]) 107 | else: 108 | pass 109 | 110 | 111 | def handle_invoice(): 112 | """Decode a BOLT11 invoice. Ensure that amount is correct or 0, then attempt to 113 | make the payment. 114 | """ 115 | decode_req = decode_request(config.INVOICE) 116 | if decode_req in (math.floor(config.SATS), 0): 117 | payout(config.SATS, config.INVOICE) 118 | result = last_payment(config.INVOICE) 119 | 120 | if result: 121 | display.update_thankyou_screen() 122 | else: 123 | display.update_payment_failed() 124 | time.sleep(120) 125 | else: 126 | print("Please show correct invoice") 127 | 128 | 129 | def evaluate_scan(qrcode): 130 | """Evaluates the scanned qr code for Lightning invoices. 131 | """ 132 | if not qrcode: 133 | logger.error("QR code scanning failed") 134 | return False 135 | # check for a lightning invoice 136 | else: 137 | if "lnbc" in qrcode.lower(): 138 | logger.info("Lightning invoice detected") 139 | invoice = qrcode.lower() 140 | # if invoice preceded with "lightning:" then chop it off so that we can 141 | # handle it correctly 142 | if "lightning:" in invoice: 143 | invoice = invoice[10:] 144 | return invoice 145 | else: 146 | logger.error("This QR does not contain a Lightning invoice") 147 | return False 148 | -------------------------------------------------------------------------------- /lntxbot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import json 4 | import os 5 | import logging 6 | import time 7 | import requests 8 | import math 9 | import qr 10 | 11 | import config 12 | import utils 13 | 14 | # import display 15 | display_config = config.conf["atm"]["display"] 16 | display = getattr(__import__("displays", fromlist=[display_config]), display_config) 17 | 18 | logger = logging.getLogger("LNTXBOT") 19 | 20 | # TODO: Add variable to set certificate check to true or false 21 | # TODO: Add evaluation for credentials after scanning 22 | # TODO: Remove display calls from here to app.py 23 | 24 | 25 | def payout(amt, payment_request): 26 | """Attempts to pay a BOLT11 invoice 27 | """ 28 | data = { 29 | "invoice": payment_request, 30 | "amount": math.floor(amt), 31 | } 32 | response = requests.post( 33 | str(config.conf["lntxbot"]["url"]) + "/payinvoice", 34 | headers={"Authorization": "Basic %s" % config.conf["lntxbot"]["creds"]}, 35 | data=json.dumps(data), 36 | ) 37 | response = response.json() 38 | 39 | if response["payment_error"]: 40 | display.update_payment_failed() 41 | else: 42 | display.update_thankyou_screen() 43 | 44 | 45 | def request_lnurl(amt): 46 | """Request a new lnurl for 'amt' from the server 47 | """ 48 | data = { 49 | "satoshis": str(math.floor(amt)), 50 | } 51 | response = requests.post( 52 | str(config.conf["lntxbot"]["url"]) + "/generatelnurlwithdraw", 53 | headers={"Authorization": "Basic %s" % config.conf["lntxbot"]["creds"]}, 54 | data=json.dumps(data), 55 | ) 56 | return response.json() 57 | 58 | 59 | def get_lnurl_balance(): 60 | """Query the lnurl balance from the server and return the 61 | ["BTC"]["AvailableBalance"] value 62 | """ 63 | response = requests.post( 64 | str(config.conf["lntxbot"]["url"]) + "/balance", 65 | headers={"Authorization": "Basic %s" % config.conf["lntxbot"]["creds"]}, 66 | ) 67 | return response.json()["BTC"]["AvailableBalance"] 68 | 69 | 70 | def wait_for_balance_update(start_balance, timeout): 71 | """Loops waiting for any balance change on the lnurl and returns True if this 72 | happens before the timeout 73 | """ 74 | start_time = time.time() 75 | success = False 76 | # loop while we wait for the balance to get updated 77 | while True and (time.time() < start_time + timeout): 78 | new_balance = get_lnurl_balance() 79 | if start_balance == new_balance: 80 | print("Balance: " + str(start_balance) + " (no changes)") 81 | time.sleep(3) 82 | else: 83 | print( 84 | "Balance: " + str(start_balance) + " | New Balance:" + str(new_balance) 85 | ) 86 | success = True 87 | break 88 | return success 89 | 90 | def is_json_key_not_present(json, key): 91 | try: 92 | buf = json[key] 93 | except KeyError: 94 | return True 95 | 96 | return False 97 | 98 | def process_using_lnurl(amt): 99 | """Processes receiving an amount using the lnurl scheme 100 | """ 101 | # get the new lnurl 102 | display.update_lnurl_generation() 103 | logger.info("LNURL requested") 104 | lnurl = request_lnurl(amt) 105 | 106 | if (is_json_key_not_present(lnurl, "lnurl")) == True: 107 | if (is_json_key_not_present(lnurl, "message") == False): 108 | errormsg = "An error has occurred by requesting of LNURL. " + lnurl["message"] 109 | logger.error(errormsg) 110 | return 111 | else: 112 | logger.info("LNURL was created successfully") 113 | 114 | # Check EPD_SIZE is defined 115 | utils.check_epd_size() 116 | 117 | # create a qr code image and print it to terminal 118 | qr_img = utils.generate_lnurl_qr(lnurl["lnurl"]) 119 | 120 | # draw the qr code on the e-ink screen 121 | display.draw_lnurl_qr(qr_img) 122 | 123 | # get the balance? back from the bot 124 | start_balance = get_lnurl_balance() 125 | print(start_balance) 126 | 127 | # loop while we wait for a balance update or until timeout reached 128 | success = wait_for_balance_update(start_balance, timeout=90) 129 | 130 | if success: 131 | display.update_thankyou_screen() 132 | logger.info("LNURL withdrawal succeeded") 133 | return 134 | else: 135 | # TODO: I think we should handle a failure here 136 | logger.error("LNURL withdrawal failed (within 90 seconds)") 137 | -------------------------------------------------------------------------------- /qr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import zbarlight 4 | import logging 5 | import requests 6 | import time 7 | import config 8 | 9 | from PIL import Image 10 | from io import BytesIO 11 | from picamera import PiCamera 12 | 13 | logger = logging.getLogger("QR") 14 | 15 | 16 | def scan(): 17 | 18 | with PiCamera() as camera: 19 | try: 20 | camera.start_preview() 21 | time.sleep(1) 22 | logger.info("Start scanning for QR code") 23 | except: 24 | logger.error("PiCamera.start_preview() raised an exception") 25 | 26 | stream = BytesIO() 27 | qr_codes = None 28 | # Set timeout to 10 seconds 29 | timeout = time.time() + 10 30 | while qr_codes is None and (time.time() < timeout): 31 | stream.seek(0) 32 | # Start camera stream (make sure RaspberryPi camera is focused correctly 33 | # manually adjust it, if not) 34 | camera.capture(stream, "jpeg") 35 | qr_codes = zbarlight.scan_codes("qrcode", Image.open(stream)) 36 | time.sleep(0.05) 37 | camera.stop_preview() 38 | # break immediately if we didn't get a qr code scan 39 | if not qr_codes: 40 | logger.info("No QR within 10 seconds detected") 41 | return False 42 | 43 | # decode the first qr_code to get the data 44 | qr_code = qr_codes[0].decode() 45 | 46 | return qr_code 47 | 48 | 49 | def scan_attempts(target_attempts): 50 | """Scan and evaluate users credentials 51 | """ 52 | attempts = 0 53 | 54 | while attempts < target_attempts: 55 | qrcode = scan() 56 | if qrcode: 57 | logger.info("QR code successfuly detected.") 58 | return qrcode 59 | else: 60 | attempts += 1 61 | logger.error("{}. attempt!".format(attempts)) 62 | 63 | logger.error("{} failed scanning attempts.".format(target_attempts)) 64 | 65 | 66 | def scan_credentials(): 67 | credentials = scan_attempts(4) 68 | 69 | if credentials: 70 | if ("lnd-config" in credentials) and ("lnd.config" in credentials): 71 | logger.info("BTCPayServer LND Credentials detected.") 72 | try: 73 | r = requests.get(credentials.lstrip("config=")) 74 | data = r.json() 75 | data = data["configurations"][0] 76 | 77 | config.update_config("btcpay", "url", data["uri"] + "v1") 78 | config.update_config("lnd", "macaroon", data["adminMacaroon"]) 79 | config.update_config("atm", "activewallet", "btcpay_lnd") 80 | except: 81 | logger.error("QR not valid (they expire after 10 minutes)") 82 | 83 | elif ("lntxbot" in credentials) and ("@" in credentials): 84 | logger.info("Lntxbot Credentials detected.") 85 | 86 | config.update_config("lntxbot", "creds", credentials.split("@")[0]) 87 | config.update_config("lntxbot", "url", credentials.split("@")[1]) 88 | config.update_config("atm", "activewallet", "lntxbot") 89 | 90 | else: 91 | logger.error("No credentials to a known wallet detected.") 92 | else: 93 | logger.error("No credentials to a known wallet could be detected.") 94 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # 1. Install https://downloads.raspberrypi.org/raspbian/images/raspbian-2019-04-09/ 2 | # 2. Install Papirus display library with "curl -sSL https://pisupp.ly/papiruscode | sudo bash" 3 | # (https://github.com/PiSupply/PaPiRus) 4 | # (Note: Papirus library can't be easily installed in a venv, hence this process) 5 | # 3. Install libraries below with "pip3 install -r requirements.txt" 6 | 7 | zbarlight==3.0 8 | qrcode==6.1 9 | requests>=2.23.0 10 | -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/Back_Lid_NORMAL_v1_NoSupp.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/Back_Lid_NORMAL_v1_NoSupp.stl -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/Back_Lid_THICKER_v1_NoSupp.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/Back_Lid_THICKER_v1_NoSupp.stl -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/Front_Glass_v1_NoSupp.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/Front_Glass_v1_NoSupp.stl -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/Magnet Case/Case Magnet.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/Magnet Case/Case Magnet.stl -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/Magnet Case/Front Magnet.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/Magnet Case/Front Magnet.stl -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/Magnet Case/Logo Backplate Magnet.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/Magnet Case/Logo Backplate Magnet.stl -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/Magnet Case/Screen Backplate Magnet.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/Magnet Case/Screen Backplate Magnet.stl -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/Magnet Case/info.md: -------------------------------------------------------------------------------- 1 | I have added new 3D models, which can be supplemented with magnets so that you no longer have to attach the front panel to the case with a rubber band. You can find the procedure and more details in my Twitter Thread ... 2 | 3 | Magnets: 5x5mm round nickel plate: https://www.supermagnete.ch/scheibenmagnete-neodym/scheibenmagnet-5mm-5mm_S-05-05-N 4 | Metal plates: 2x10mm self-adhesive: https://www.supermagnete.ch/magnethaftgruende-selbstklebend/metallscheiben-selbstklebend-10mm_PAS-10 5 | 6 | For more information please DM me on Twitter or Telegram! @Nicolas_b58 7 | -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # 3D Printed L-ATM Case PE-Edition 4 | 5 | Below files can be used to print a case, which lowers your overall costs. The models can be printed without any supports. 6 | 7 | This is the first version and is designed to be used with inserts to mount the Coin-Acceptor as well as the Rasbperry-Pi Zero. For the Coin Acceptor use **M3** inserts [(Amazon)](https://amzn.to/35eNkgj), for the Raspberry-Pi **M2.5** [(Amazon)](https://amzn.to/2HjF0Uo) inserts are needed. 8 | The Lids holds by friction fit. 9 | 10 | 11 | For the case **needed** are 3 Parts: 12 | - **Shell** 13 | 14 | - **Back Lid** 15 | - two versions 16 | - normal with 2mm thick plate 17 | - thicker with 3mm thick plate 18 | - **Top Lid** 19 | - there are two separate versions: 20 | - with Lightning-Logo, *for dual color print add a **color change at Z 3.00 mm***. 21 | - without any logo 22 | - Mounting Rim 23 | - optional but *recommended* to properly glue the Top Lid onto the Back Lid. 24 | 25 | 26 | 1. Print Shell / Back Lid / Top Lid / Mounting Rim 27 | 2. Use soldering iron to: 28 | 1. Add M3 Inserts into Coin Acceptor holes 29 | 2. Add M2.5 Inserts into Raspberry holes 30 | 31 |

32 | 33 | 34 | 3. If you have printed the *Mounting Rim* put it around the BackLid 35 |

36 | 4. put glue on the Top Lid and use something to press both together 37 | 38 | 5. this is what it should look like without the rim 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/Shell_v1_NoSupp.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/Shell_v1_NoSupp.stl -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/Top_Lid_No_Logo_v1_NoSupp.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/Top_Lid_No_Logo_v1_NoSupp.stl -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/Top_Lid_with_Logo_v1_NoSupp.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/Top_Lid_with_Logo_v1_NoSupp.stl -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/images/3d_printed_case.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/images/3d_printed_case.jpeg -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/images/glued_lid.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/images/glued_lid.jpeg -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/images/lids_and_rim.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/images/lids_and_rim.jpeg -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/images/mounting_rim.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/images/mounting_rim.jpeg -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/images/rim_and_backlid.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/images/rim_and_backlid.jpeg -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/images/shell_inserted.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/images/shell_inserted.jpeg -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/images/shell_inserts.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/images/shell_inserts.jpeg -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/images/shell_m3_inserts.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/images/shell_m3_inserts.jpeg -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/images/shell_with_mounted_pi.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/images/shell_with_mounted_pi.jpeg -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/images/top_lid_with_logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/images/top_lid_with_logo.jpeg -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/just_logo.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/just_logo.stl -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM - Pocket Edition/STLs/optional_Mounting_Rim_v1_NoSupp.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM - Pocket Edition/STLs/optional_Mounting_Rim_v1_NoSupp.stl -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM/old_versions/LightningATM - V0.1 - Assembly Backside.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM/old_versions/LightningATM - V0.1 - Assembly Backside.png -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM/old_versions/LightningATM - V0.1 - Assembly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM/old_versions/LightningATM - V0.1 - Assembly.png -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM/old_versions/LightningATM - V0.1 - Video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM/old_versions/LightningATM - V0.1 - Video.mp4 -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM/old_versions/LightningATM - V0.3 - ATM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM/old_versions/LightningATM - V0.3 - ATM.png -------------------------------------------------------------------------------- /resources/3dmodels/LightningATM/old_versions/LightningATM - V0.3 - Compartments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/3dmodels/LightningATM/old_versions/LightningATM - V0.3 - Compartments.png -------------------------------------------------------------------------------- /resources/fonts/DOTMBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/fonts/DOTMBold.ttf -------------------------------------------------------------------------------- /resources/fonts/FreeMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/fonts/FreeMono.ttf -------------------------------------------------------------------------------- /resources/fonts/FreeMonoBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/fonts/FreeMonoBold.ttf -------------------------------------------------------------------------------- /resources/fonts/FreeMonoBoldOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/fonts/FreeMonoBoldOblique.ttf -------------------------------------------------------------------------------- /resources/fonts/FreeMonoOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/fonts/FreeMonoOblique.ttf -------------------------------------------------------------------------------- /resources/fonts/Sawasdee-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/fonts/Sawasdee-Bold.ttf -------------------------------------------------------------------------------- /resources/fonts/Sawasdee-BoldOblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/fonts/Sawasdee-BoldOblique.ttf -------------------------------------------------------------------------------- /resources/fonts/Sawasdee-Oblique.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/fonts/Sawasdee-Oblique.ttf -------------------------------------------------------------------------------- /resources/fonts/Sawasdee.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/fonts/Sawasdee.ttf -------------------------------------------------------------------------------- /resources/images/LightningATM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/LightningATM.png -------------------------------------------------------------------------------- /resources/images/LightningATM1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/LightningATM1.jpg -------------------------------------------------------------------------------- /resources/images/LightningATM1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/LightningATM1.png -------------------------------------------------------------------------------- /resources/images/LightningATM1_onstage2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/LightningATM1_onstage2.jpeg -------------------------------------------------------------------------------- /resources/images/LightningATM2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/LightningATM2.jpeg -------------------------------------------------------------------------------- /resources/images/LightningATM2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/LightningATM2.png -------------------------------------------------------------------------------- /resources/images/LightningATM_design_by_@cryptonobo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/LightningATM_design_by_@cryptonobo.jpeg -------------------------------------------------------------------------------- /resources/images/LightningATM_onstage1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/LightningATM_onstage1.jpeg -------------------------------------------------------------------------------- /resources/images/LightningATM_rev3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/LightningATM_rev3.jpg -------------------------------------------------------------------------------- /resources/images/inside/inside1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/inside/inside1.jpg -------------------------------------------------------------------------------- /resources/images/inside/inside2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/inside/inside2.jpg -------------------------------------------------------------------------------- /resources/images/inside/inside3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/inside/inside3.jpg -------------------------------------------------------------------------------- /resources/images/inside/inside4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/inside/inside4.jpg -------------------------------------------------------------------------------- /resources/images/inside/inside5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/inside/inside5.jpg -------------------------------------------------------------------------------- /resources/images/papirus_zero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/papirus_zero.jpg -------------------------------------------------------------------------------- /resources/images/startup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/startup.png -------------------------------------------------------------------------------- /resources/images/startup_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/resources/images/startup_screen.jpg -------------------------------------------------------------------------------- /resources/setup/rc.local: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | # 3 | # rc.local 4 | # 5 | # This script is executed at the end of each multiuser runlevel. 6 | # Make sure that the script will "exit 0" on success or any other 7 | # value on error. 8 | # 9 | # In order to enable or disable this script just change the execution 10 | # bits. 11 | # 12 | # By default this script does nothing. 13 | 14 | # Print the IP address 15 | _IP=$(hostname -I) || true 16 | if [ "$_IP" ]; then 17 | printf "My IP address is %s\n" "$_IP" 18 | fi 19 | 20 | if [ -e /boot/setup.sh ] 21 | then 22 | sh /boot/setup.sh 23 | rm /boot/setup.sh 24 | fi 25 | 26 | exit 0 27 | -------------------------------------------------------------------------------- /resources/setup/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | curl -sSL https://raw.githubusercontent.com/21isenough/PaPiRus/master/install | sudo bash 4 | wget https://raw.githubusercontent.com/21isenough/LightningATM/master/resources/setup/show_ip.py 5 | python3 show_ip.py 6 | sudo shutdown -r now 7 | -------------------------------------------------------------------------------- /resources/setup/show_ip.py: -------------------------------------------------------------------------------- 1 | from PIL import Image, ImageFont, ImageDraw 2 | from papirus import Papirus 3 | 4 | import socket 5 | import subprocess 6 | 7 | # Get FreeMono Font 8 | font = ImageFont.truetype('Pillow/Tests/fonts/FreeMono.ttf', 15) 9 | 10 | WHITE = 1 11 | BLACK = 0 12 | PAPIRUSROT = 0 13 | PAPIRUS = Papirus(rotation=PAPIRUSROT) 14 | 15 | def init_screen(color): 16 | """Prepare the screen for drawing and return the draw variables 17 | """ 18 | image = Image.new("1", PAPIRUS.size, color) 19 | # Set width and height of screen 20 | width, height = image.size 21 | # prepare for drawing 22 | draw = ImageDraw.Draw(image) 23 | return image, width, height, draw 24 | 25 | # Get SSID of connected network 26 | ssidoutput = subprocess.check_output(['sudo', 'iwgetid']) 27 | 28 | # Get local IP 29 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 30 | s.connect(("8.8.8.8", 80)) 31 | print(s.getsockname()[0]) 32 | 33 | 34 | image, width, height, draw = init_screen(color=WHITE) 35 | draw.text( 36 | (10, 10), 37 | "Connected Wifi SSID: ", 38 | fill=BLACK, 39 | font=font, 40 | ) 41 | draw.text( 42 | (35, 30), 43 | '• "' + ssidoutput.decode().split('"')[1] + '"', 44 | fill=BLACK, 45 | font=font, 46 | ) 47 | draw.text( 48 | (10, 50), 49 | "Local IP of RPi Zero: ", 50 | fill=BLACK, 51 | font=font, 52 | ) 53 | draw.text( 54 | (35, 70), 55 | '• ' + s.getsockname()[0], 56 | fill=BLACK, 57 | font=font, 58 | ) 59 | 60 | 61 | PAPIRUS.display(image) 62 | PAPIRUS.update() 63 | 64 | s.close() 65 | -------------------------------------------------------------------------------- /tests/LED_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # LED Test 4 | import RPi.GPIO as GPIO 5 | import time 6 | GPIO.setmode(GPIO.BCM) 7 | GPIO.setwarnings(False) 8 | LED = 13 9 | GPIO.setup(LED, GPIO.OUT) 10 | try: 11 | print("LED is now flashing..") 12 | print("Exit with CTRL+C") 13 | while True: 14 | GPIO.output(LED,1) 15 | time.sleep(0.5) 16 | GPIO.output(LED,0) 17 | time.sleep(0.5) 18 | except KeyboardInterrupt: 19 | GPIO.cleanup() 20 | print(" Bye Bye") 21 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/21isenough/LightningATM/d66ab4fa7be2e728a4d002c6cf2a58b5313f25fe/tests/__init__.py -------------------------------------------------------------------------------- /tests/acceptor_test.py: -------------------------------------------------------------------------------- 1 | import RPi.GPIO as GPIO 2 | import time 3 | 4 | # INIT VARIABLES 5 | lastImpulse = 0 6 | pulses = 0 7 | 8 | 9 | def main(): 10 | global pulses 11 | 12 | ## We're using BCM Mode 13 | GPIO.setmode(GPIO.BCM) 14 | 15 | ## Setup coin interrupt channel 16 | GPIO.setup(6, GPIO.IN, pull_up_down=GPIO.PUD_UP) 17 | # GPIO.setup(PIN_COIN_INTERRUPT,GPIO.IN) 18 | GPIO.add_event_detect(6, GPIO.FALLING, callback=coinEventHandler) 19 | 20 | while True: 21 | time.sleep(0.5) 22 | if (time.time() - lastImpulse > 0.5) and (pulses > 0): 23 | if pulses == 1: 24 | print("Coin 1") 25 | pulses = 0 26 | 27 | GPIO.cleanup() 28 | 29 | 30 | # handle the coin event 31 | def coinEventHandler(channel): 32 | global lastImpulse 33 | global pulses 34 | lastImpulse = time.time() 35 | pulses = pulses + 1 36 | 37 | 38 | if __name__ == "__main__": 39 | main() 40 | -------------------------------------------------------------------------------- /tests/app_without_c_acceptor.py: -------------------------------------------------------------------------------- 1 | ########################################## 2 | # This test makes use of the 5 buttons # 3 | # on the PaPiRus board to simulate user # 4 | # input / coin insertion. # 5 | ########################################## 6 | 7 | import os 8 | import sys 9 | import time 10 | 11 | import price 12 | import lndrest 13 | import qr 14 | 15 | import RPi.GPIO as GPIO 16 | 17 | from PIL import Image 18 | from time import sleep 19 | from PIL import ImageDraw 20 | from PIL import ImageFont 21 | from papirus import Papirus 22 | 23 | # Check EPD_SIZE is defined 24 | EPD_SIZE = 0.0 25 | if os.path.exists("/etc/default/epd-fuse"): 26 | exec(open("/etc/default/epd-fuse").read()) 27 | if EPD_SIZE == 0.0: 28 | print("Please select your screen size by running 'papirus-config'.") 29 | sys.exit() 30 | 31 | # set sat,fiat, currency value 32 | CURRENCY = "EUR" 33 | FIAT = 0 34 | SATS = 0 35 | INVOICE = "" 36 | 37 | WHITE = 1 38 | BLACK = 0 39 | SIZE = 27 40 | 41 | # Assign GPIO pins for PaPiRus Zero 42 | SW1 = 21 43 | SW2 = 16 44 | SW3 = 20 45 | SW4 = 19 46 | SW5 = 26 47 | 48 | 49 | def main(argv): 50 | global SIZE 51 | global FIAT 52 | global SATS 53 | 54 | GPIO.setmode(GPIO.BCM) 55 | 56 | GPIO.setup(SW1, GPIO.IN) 57 | GPIO.setup(SW2, GPIO.IN) 58 | GPIO.setup(SW3, GPIO.IN) 59 | GPIO.setup(SW4, GPIO.IN) 60 | GPIO.setup(SW5, GPIO.IN) 61 | 62 | papirus = Papirus(rotation=int(argv[0]) if len(sys.argv) > 1 else 0) 63 | 64 | # Use smaller font for smaller displays 65 | if papirus.height <= 96: 66 | SIZE = 18 67 | 68 | update_startup_screen() 69 | 70 | # papirus.clear() 71 | 72 | btcprice = price.getbtcprice(CURRENCY) 73 | satprice = round((1 / (btcprice * 100)) * 100000000, 2) 74 | 75 | while True: 76 | 77 | if (GPIO.input(SW1) == False) and (GPIO.input(SW4) == False): 78 | FIAT = 0 79 | SATS = 0 80 | update_startup_screen() 81 | 82 | if GPIO.input(SW1) == False: 83 | FIAT += 0.01 84 | SATS = FIAT * 100 * satprice 85 | update_amount_screen(papirus, SIZE) 86 | 87 | if GPIO.input(SW2) == False: 88 | FIAT += 0.02 89 | SATS = FIAT * 100 * satprice 90 | update_amount_screen(papirus, SIZE) 91 | 92 | if GPIO.input(SW3) == False: 93 | FIAT += 0.05 94 | SATS = FIAT * 100 * satprice 95 | update_amount_screen(papirus, SIZE) 96 | 97 | if GPIO.input(SW4) == False: 98 | FIAT += 0.1 99 | SATS = FIAT * 100 * satprice 100 | update_amount_screen(papirus, SIZE) 101 | 102 | if GPIO.input(SW5) == False: 103 | update_payout_screen(papirus, SIZE) 104 | 105 | sleep(0.1) 106 | 107 | 108 | def update_amount_screen(papirus, size): 109 | # initially set all white background 110 | image = Image.new("1", papirus.size, WHITE) 111 | 112 | # Set width and heigt of screen 113 | width, height = image.size 114 | 115 | # prepare for drawing 116 | draw = ImageDraw.Draw(image) 117 | 118 | # set font sizes 119 | font = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeMono.ttf", size) 120 | font1 = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeMono.ttf", 14) 121 | 122 | # set btc and sat price 123 | btcprice = price.getbtcprice(CURRENCY) 124 | satprice = round((1 / btcprice) * 10e5, 2) 125 | 126 | draw.rectangle((2, 2, width - 2, height - 2), fill=WHITE, outline=BLACK) 127 | draw.text((15, 10), str(round(SATS)) + " sats", fill=BLACK, font=font) 128 | draw.text( 129 | (15, 30), 130 | "(" + "%.2f" % round(FIAT, 2) + " " + CURRENCY + ")", 131 | fill=BLACK, 132 | font=font, 133 | ) 134 | draw.text((15, 70), "(1 cent = " + str(satprice) + " sats)", fill=BLACK, font=font1) 135 | 136 | papirus.display(image) 137 | papirus.partial_update() 138 | 139 | 140 | def update_payout_screen(papirus, size): 141 | global INVOICE 142 | 143 | # initially set all white background 144 | image = Image.new("1", papirus.size, WHITE) 145 | 146 | # Set width and heigt of screen 147 | width, height = image.size 148 | 149 | # prepare for drawing 150 | draw = ImageDraw.Draw(image) 151 | 152 | # set font sizes 153 | font = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeMono.ttf", 20) 154 | font1 = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeMono.ttf", 15) 155 | 156 | # set btc and sat price 157 | # btcprice = price.getbtcprice(CURRENCY) 158 | # satprice = round((1 / btcprice) * 10e5, 2) 159 | 160 | draw.rectangle((2, 2, width - 2, height - 2), fill=WHITE, outline=BLACK) 161 | draw.text((15, 30), str(round(SATS)) + " sats", fill=BLACK, font=font) 162 | draw.text((15, 50), "on the way!", fill=BLACK, font=font1) 163 | 164 | papirus.display(image) 165 | papirus.update() 166 | 167 | INVOICE = qr.scan() 168 | 169 | print(INVOICE) 170 | 171 | decodreq = lndrest.decode_request(INVOICE) 172 | 173 | print(decodreq, round(SATS)) 174 | 175 | if (decodreq == str(round(SATS))) or (decodreq == True): 176 | lndrest.payout(SATS, INVOICE) 177 | 178 | # time.sleep(5) 179 | result = lndrest.last_payment(INVOICE) 180 | 181 | draw.text((15, 70), str(result), fill=BLACK, font=font1) 182 | 183 | papirus.display(image) 184 | papirus.partial_update() 185 | else: 186 | print("Please show correct invoice") 187 | 188 | 189 | def update_startup_screen(): 190 | font1 = ImageFont.truetype( 191 | os.path.expanduser("~/LightningATM/resources/fonts/FreeMono.ttf"), 18 192 | ) 193 | font = ImageFont.truetype( 194 | os.path.expanduser("~/LightningATM/resources/fonts/Sawasdee-Bold.ttf"), 30 195 | ) 196 | font2 = ImageFont.truetype( 197 | os.path.expanduser("~/LightningATM/resources/fonts/FreeMono.ttf"), 14 198 | ) 199 | 200 | papirus = Papirus(rotation=int(argv[0]) if len(sys.argv) > 1 else 0) 201 | 202 | image = Image.new("1", papirus.size, WHITE) 203 | 204 | draw = ImageDraw.Draw(image) 205 | 206 | draw.text((20, 10), "Welcome to the", fill=BLACK, font=font1) 207 | draw.text((10, 20), "LightningATM", fill=BLACK, font=font) 208 | draw.text((7, 75), "- please insert coins -", fill=BLACK, font=font2) 209 | 210 | papirus.display(image) 211 | papirus.update() 212 | 213 | 214 | if __name__ == "__main__": 215 | try: 216 | main(sys.argv[1:]) 217 | except KeyboardInterrupt: 218 | sys.exit("interrupted") 219 | -------------------------------------------------------------------------------- /tests/bech32_encoding.py: -------------------------------------------------------------------------------- 1 | ####### Findings from encoding/decoding into bech32 ####### 2 | 3 | import bech32 4 | 5 | # Create a list with the decimal representation of a string 6 | # This is list is based on 8 bits 7 | data = list("https".encode("utf8")) 8 | 9 | # Convert from 8 bits to 5 bits with 10 | data = bech32.convertbits(data, 8, 5) 11 | 12 | # Use bech32_encode to encode the data into a bech32 address 13 | data = bech32.bech32_encode("lnurl", data) 14 | 15 | 16 | # Use decode to get the prefix and the data 17 | data = bech32.bech32_decode(data) 18 | 19 | # To decode a you need to convert back from 5 to 8 bits 20 | data = bech32.convertbits(data[1], 5, 8) 21 | 22 | # then create a string with the representation of the 8 bits 23 | "".join(map(chr, data)) 24 | -------------------------------------------------------------------------------- /tests/button_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Button Test 4 | import RPi.GPIO as GPIO 5 | import time 6 | switch = 5 7 | GPIO.setmode(GPIO.BCM) 8 | GPIO.setup(switch, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) 9 | def callback_function(switch): 10 | print("_______event________") 11 | 12 | try: 13 | print("Push the button..") 14 | print("Exit with CTRL+C") 15 | GPIO.add_event_detect(switch, GPIO.RISING, callback=callback_function, bouncetime=300) 16 | while True: 17 | print("_______#____________") 18 | time.sleep(1) 19 | print("_________#__________") 20 | time.sleep(1) 21 | print("___________#________") 22 | time.sleep(1) 23 | except KeyboardInterrupt: 24 | GPIO.remove_event_detect(switch) 25 | GPIO.cleanup() 26 | print(" Bye Bye") 27 | -------------------------------------------------------------------------------- /tests/coinacceptor.py: -------------------------------------------------------------------------------- 1 | import RPi.GPIO as GPIO 2 | import time 3 | from threading import Thread 4 | 5 | # GPIO.setwarnings(False) 6 | 7 | GPIO.setmode(GPIO.BCM) 8 | 9 | GPIO.setup(6, GPIO.IN, pull_up_down=GPIO.PUD_UP) 10 | 11 | global count 12 | global counting 13 | 14 | counting = 0 15 | 16 | 17 | def firstFunction(): 18 | global counter 19 | global ts 20 | global counting 21 | count = 1 22 | counter = 0 23 | ts = time.time() 24 | while True: 25 | if count == 1: 26 | GPIO.wait_for_edge(6, GPIO.FALLING) 27 | counting = 1 28 | counter += 1 29 | print(f"Pulse comming ! {counter}") 30 | ts = time.time() 31 | 32 | 33 | def secondFunction(): 34 | global count 35 | global counting 36 | global counter 37 | while True: 38 | cts = ts + 2 39 | if cts < time.time(): 40 | print(f"Counting looks like finished with {counter} pulses") 41 | count = 0 42 | counting = 0 43 | print("We process accepted the coins") 44 | 45 | # urllib2.urlopen is just for testing in my case, you can do whatever you want here, i just used this to test the functions 46 | if counter == 1: 47 | print("Counter 1") 48 | if counter == 2: 49 | print("Counter 2") 50 | if counter == 3: 51 | print("Counter 3") 52 | if counter == 4: 53 | print("Counter 4") 54 | if counter == 5: 55 | print("Counter 5") 56 | 57 | counter = 0 58 | count = 1 59 | print("Ready for the next coin") 60 | time.sleep(1) 61 | 62 | 63 | def thirdFunction(): 64 | while True: 65 | if counting == 0: 66 | global ts 67 | ts = time.time() 68 | time.sleep(1) 69 | 70 | 71 | try: 72 | t1 = Thread(target=firstFunction) 73 | t2 = Thread(target=secondFunction) 74 | t3 = Thread(target=thirdFunction) 75 | 76 | t1.start() 77 | t2.start() 78 | t3.start() 79 | 80 | except KeyboardInterrupt: 81 | t1.stop() 82 | t2.stop() 83 | t3.stop() 84 | GPIO.cleanup() 85 | -------------------------------------------------------------------------------- /tests/display.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | import RPi.GPIO as GPIO 5 | 6 | from PIL import Image 7 | from time import sleep 8 | from PIL import ImageDraw 9 | from PIL import ImageFont 10 | from papirus import Papirus 11 | 12 | # Check EPD_SIZE is defined 13 | EPD_SIZE = 0.0 14 | if os.path.exists("/etc/default/epd-fuse"): 15 | exec(open("/etc/default/epd-fuse").read()) 16 | if EPD_SIZE == 0.0: 17 | print("Please select your screen size by running 'papirus-config'.") 18 | sys.exit() 19 | 20 | # set sat,fiat, currency value 21 | CURRENCY = "EUR" 22 | FIAT = 0 23 | SATS = 0 24 | 25 | WHITE = 1 26 | BLACK = 0 27 | SIZE = 27 28 | 29 | # Assign GPIO pins for PaPiRus Zero 30 | SW1 = 21 31 | SW2 = 16 32 | SW3 = 20 33 | SW4 = 19 34 | SW5 = 26 35 | 36 | 37 | def main(argv): 38 | global SIZE 39 | global FIAT 40 | global SATS 41 | 42 | GPIO.setmode(GPIO.BCM) 43 | 44 | GPIO.setup(SW1, GPIO.IN) 45 | GPIO.setup(SW2, GPIO.IN) 46 | GPIO.setup(SW3, GPIO.IN) 47 | GPIO.setup(SW4, GPIO.IN) 48 | GPIO.setup(SW5, GPIO.IN) 49 | 50 | papirus = Papirus(rotation=int(argv[0]) if len(sys.argv) > 1 else 0) 51 | 52 | # Use smaller font for smaller displays 53 | if papirus.height <= 96: 54 | SIZE = 18 55 | 56 | papirus.clear() 57 | 58 | while True: 59 | 60 | if GPIO.input(SW1) == False: 61 | update_img_screen(papirus) 62 | 63 | if GPIO.input(SW2) == False: 64 | pass 65 | 66 | if GPIO.input(SW3) == False: 67 | pass 68 | 69 | if GPIO.input(SW4) == False: 70 | pass 71 | 72 | if GPIO.input(SW5) == False: 73 | pass 74 | 75 | sleep(0.1) 76 | 77 | 78 | def update_img_screen(papirus): 79 | image = Image.new("1", papirus.size, WHITE) 80 | papirus.display("~/LightningATM/resources/startup.png") 81 | # papirus.display(image) 82 | papirus.update() 83 | 84 | 85 | def update_amount_screen(papirus, size): 86 | # initially set all white background 87 | image = Image.new("1", papirus.size, WHITE) 88 | 89 | # Set width and heigt of screen 90 | width, height = image.size 91 | 92 | # prepare for drawing 93 | draw = ImageDraw.Draw(image) 94 | 95 | # set font sizes 96 | font = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeMono.ttf", size) 97 | font1 = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeMono.ttf", 14) 98 | 99 | # set btc and sat price 100 | btcprice = price.getbtcprice(CURRENCY) 101 | satprice = round((1 / btcprice) * 10e5, 2) 102 | 103 | draw.rectangle((2, 2, width - 2, height - 2), fill=WHITE, outline=BLACK) 104 | draw.text((15, 10), str(round(SATS)) + " sats", fill=BLACK, font=font) 105 | draw.text( 106 | (15, 30), 107 | "(" + "%.2f" % round(FIAT, 2) + " " + CURRENCY + ")", 108 | fill=BLACK, 109 | font=font, 110 | ) 111 | draw.text((15, 70), "(1 cent = " + str(satprice) + " sats)", fill=BLACK, font=font1) 112 | 113 | papirus.display(image) 114 | papirus.partial_update() 115 | 116 | 117 | def update_payout_screen(papirus, size): 118 | # initially set all white background 119 | image = Image.new("1", papirus.size, WHITE) 120 | 121 | # Set width and heigt of screen 122 | width, height = image.size 123 | 124 | # prepare for drawing 125 | draw = ImageDraw.Draw(image) 126 | 127 | # set font sizes 128 | font = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeMono.ttf", 20) 129 | font1 = ImageFont.truetype("/usr/share/fonts/truetype/freefont/FreeMono.ttf", 15) 130 | 131 | # set btc and sat price 132 | # btcprice = price.getbtcprice(CURRENCY) 133 | # satprice = round((1 / btcprice) * 10e5, 2) 134 | 135 | draw.rectangle((2, 2, width - 2, height - 2), fill=WHITE, outline=BLACK) 136 | draw.text((15, 30), str(round(SATS)) + " sats", fill=BLACK, font=font) 137 | draw.text((15, 50), "on the way!", fill=BLACK, font=font1) 138 | # draw.text((15, 30), '(' + '%.2f' % round(FIAT,2) + ' ' + CURRENCY + ')', fill=BLACK, font=font) 139 | # draw.text((15, 70), '(1 cent = ' + str(satprice) + ' sats)', fill=BLACK, font=font1) 140 | 141 | papirus.display(image) 142 | papirus.update() 143 | 144 | 145 | if __name__ == "__main__": 146 | try: 147 | main(sys.argv[1:]) 148 | except KeyboardInterrupt: 149 | sys.exit("interrupted") 150 | -------------------------------------------------------------------------------- /tests/displayalign.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import getopt 4 | import inspect 5 | import RPi.GPIO as GPIO 6 | import subprocess 7 | 8 | from time import sleep 9 | from PIL import Image 10 | 11 | currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) 12 | parentdir = os.path.dirname(currentdir) 13 | sys.path.insert(0, parentdir) 14 | 15 | import config 16 | import utils 17 | 18 | 19 | pwd = os.popen('pwd').read() 20 | pwd = pwd.strip('\n') 21 | 22 | if (pwd != '/home/pi/LightningATM') : 23 | print('This test must be executed in the directory /home/pi/LightningATM') 24 | print('This test is executed in the directory', pwd) 25 | sys.exit("Exit python script, because it has executed from the wrong path") 26 | 27 | display_config = config.conf["atm"]["display"] 28 | display = getattr(__import__("displays", fromlist=[display_config]), display_config) 29 | 30 | print('The config.ini was read out. The following display will be tested:') 31 | print(display) 32 | print() 33 | 34 | #systemctl is-active LightningATM.service -> 0 = active 768 = inactive 35 | #sudo systemctl stop LightningATM.service -> inactive 36 | 37 | # os.system will return 0 for service is active else inactive. 38 | status = os.system('systemctl is-active --quiet LightningATM.service') 39 | 40 | startService = 0 41 | 42 | if status == 0: 43 | print("LightningATM.service is started and will be stopped now.") 44 | os.system('sudo systemctl stop LightningATM.service') 45 | startService = 1 46 | else: 47 | print("LightningATM.service is not started, display test starts now.") 48 | 49 | 50 | def main(): 51 | 52 | print("1. display.error_screen(message=ERROR)") 53 | display.error_screen(message="ERROR") 54 | sleep(2) 55 | print("2. display.update_qr_request()") 56 | display.update_qr_request() 57 | sleep(3) 58 | print("3. display.update_qr_failed()") 59 | display.update_qr_failed() 60 | sleep(2) 61 | print("4. display.update_payout_screen()") 62 | display.update_payout_screen() 63 | sleep(2) 64 | print("5. display.update_payment_failed()") 65 | display.update_payment_failed() 66 | sleep(2) 67 | print("6. display.update_thankyou_screen()") 68 | display.update_thankyou_screen() 69 | sleep(2) 70 | print("7. display.update_nocoin_screen()") 71 | display.update_nocoin_screen() 72 | sleep(2) 73 | print("8. display.update_lnurl_generation()") 74 | display.update_lnurl_generation() 75 | sleep(2) 76 | print("9. display.update_shutdown_screen()") 77 | display.update_shutdown_screen() 78 | sleep(2) 79 | print("10. display.update_wallet_scan()") 80 | display.update_wallet_scan() 81 | sleep(2) 82 | print("11. display.update_lntxbot_balance(balance)") 83 | display.update_lntxbot_balance(123) 84 | sleep(2) 85 | print("12. display.update_btcpay_lnd()") 86 | display.update_btcpay_lnd() 87 | sleep(2) 88 | print("13. display.draw_lnurl_qr(qr_img)") 89 | qrImage = Image.new('1', (122, 122), 255) 90 | display.draw_lnurl_qr(qrImage) 91 | sleep(2) 92 | print("14. display.update_amount_screen()") 93 | display.update_amount_screen() 94 | sleep(2) 95 | print("15. display.update_lnurl_cancel_notice()") 96 | display.update_lnurl_cancel_notice() 97 | sleep(2) 98 | print("16. display.update_button_fault()") 99 | display.update_button_fault() 100 | sleep(2) 101 | print("17. display.update_wallet_fault()") 102 | display.update_wallet_fault() 103 | sleep(2) 104 | print("18. display.update_startup_screen()") 105 | display.update_startup_screen() 106 | sleep(2) 107 | print("19. display.update_blank_screen()") 108 | display.update_blank_screen() 109 | sleep(2) 110 | print("20. init_screen(0)") 111 | display.init_screen(0) 112 | sleep(2) 113 | 114 | if startService == 1: 115 | print("LightningATM.service will be started now.") 116 | os.system('sudo systemctl start LightningATM.service') 117 | 118 | if __name__ == "__main__": 119 | main() -------------------------------------------------------------------------------- /tests/lnbits_lnurlw.py: -------------------------------------------------------------------------------- 1 | # Run this test from the root directory of the project with: 2 | # python3 -m tests.lnbits_lnurlw 3 | # Before running this test, make sure you have a config.ini file in ~/.lightningATM/ 4 | # with a [lnbits] section and url and apikey values. 5 | # If you run this test on a non-raspi without a display, 6 | # set: 7 | # display = testing 8 | 9 | 10 | import config 11 | import lnbits 12 | import logging 13 | 14 | ch = logging.StreamHandler() 15 | ch.setLevel(logging.DEBUG) 16 | logger = logging.getLogger() 17 | # create formatter 18 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 19 | # add formatter to ch 20 | ch.setFormatter(formatter) 21 | logger.addHandler(ch) 22 | logger.setLevel(logging.DEBUG) 23 | 24 | print("config['lnbits']['url']: "+str(config.conf["lnbits"]["url"])) 25 | print("config['lnbits']['apikey']: "+str(config.conf["lnbits"]["apikey"])) 26 | 27 | config.SATS = 5 28 | resp_json = lnbits.create_lnurlw() 29 | print("lnurlw: "+str(resp_json["lnurl"])) 30 | print("Redeem within timeout!") 31 | success = lnbits.wait_for_lnurlw_redemption(resp_json["id"]) 32 | print("success: "+str(success)) -------------------------------------------------------------------------------- /tests/qr_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | ## Displays QR codes on the e-Ink 4 | 5 | import os 6 | import qrcode 7 | from PIL import Image, ImageFont, ImageDraw 8 | from papirus import Papirus 9 | 10 | def createfont(font, size): 11 | pathfreemono = os.path.expanduser('~/LightningATM/resources/fonts/FreeMono.ttf') 12 | pathfreemonob = os.path.expanduser('~/LightningATM/resources/fonts/FreeMonoBold.ttf') 13 | pathsawasdee = os.path.expanduser('~/LightningATM/resources/fonts/Sawasdee-Bold.ttf') 14 | 15 | if font == 'freemono': 16 | return ImageFont.truetype(pathfreemono, size) 17 | if font == 'freemonob': 18 | return ImageFont.truetype(pathfreemonob, size) 19 | if font == 'sawasdee': 20 | return ImageFont.truetype(pathsawasdee, size) 21 | else: 22 | print('Font not available') 23 | 24 | 25 | WHITE = 1 26 | BLACK = 0 27 | PAPIRUSROT = 0 28 | PAPIRUS = Papirus(rotation = PAPIRUSROT) 29 | 30 | if os.path.exists('/etc/default/epd-fuse'): 31 | exec(open('/etc/default/epd-fuse').read()) 32 | 33 | image = Image.new('1', PAPIRUS.size, BLACK) 34 | 35 | qr = qrcode.QRCode( 36 | version=1, 37 | error_correction=qrcode.constants.ERROR_CORRECT_L, 38 | box_size=2, 39 | border=2, 40 | ) 41 | qr.add_data('lnurl1dp68gurn8ghj7ctsdyhxc6t8dp6xu6twvuhxw6txw3ej7mrww4exctesxdnxyerrvvmk2e3svdjkye3hxuenyvtyx3jr2erzxcmrxvfj893xvd3cxu6xgdrxv56n2e3jvdnqs3t6l7'.upper()) 42 | 43 | 44 | img = qr.make_image() 45 | 46 | 47 | print(type(img)) 48 | print(img.size) 49 | 50 | img = img.resize((96,96), resample=0) 51 | 52 | print(img.size) 53 | 54 | draw = ImageDraw.Draw(image) 55 | draw.bitmap((0, 0), img, fill=WHITE) 56 | draw.text((110, 25), 'Scan to', fill=WHITE, font=createfont('freemonob',16)) 57 | draw.text((110, 45), 'receive', fill=WHITE, font=createfont('freemonob',16)) 58 | 59 | PAPIRUS.display(image) 60 | PAPIRUS.update() 61 | -------------------------------------------------------------------------------- /tests/relay_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | # Relay Test - Toggles every 2 seconds 4 | import RPi.GPIO as GPIO 5 | import time 6 | GPIO.setmode(GPIO.BCM) 7 | GPIO.setwarnings(False) 8 | relay = 12 9 | GPIO.setup(relay, GPIO.OUT) 10 | try: 11 | print("The relay now toggles every 2 seconds") 12 | print("Exit with CTRL+C") 13 | while True: 14 | GPIO.output(relay,1) 15 | time.sleep(2.0) 16 | GPIO.output(relay,0) 17 | time.sleep(2.0) 18 | except KeyboardInterrupt: 19 | GPIO.cleanup() 20 | print(" Bye Bye") 21 | -------------------------------------------------------------------------------- /tests/startup.py: -------------------------------------------------------------------------------- 1 | import PIL, os 2 | import sys 3 | 4 | from PIL import ImageDraw 5 | from PIL import ImageFont 6 | 7 | from papirus import Papirus 8 | 9 | BLACK = 0 10 | WHITE = 1 11 | 12 | font1 = ImageFont.truetype(os.path.expanduser('~/LightningATM/resources/fonts/FreeMono.ttf'), 18) 13 | font = ImageFont.truetype(os.path.expanduser('~/LightningATM/resources/fonts/Sawasdee-Bold.ttf'), 30) 14 | font2 = ImageFont.truetype(os.path.expanduser('~/LightningATM/resources/fonts/FreeMono.ttf'), 14) 15 | 16 | def startupdisplay(argv): 17 | papirus = Papirus(rotation = int(argv[0]) if len(sys.argv) > 1 else 0) 18 | 19 | image = PIL.Image.new('1', papirus.size, WHITE) 20 | 21 | draw = ImageDraw.Draw(image) 22 | 23 | draw.text((20, 10), 'Welcome to the', fill=BLACK, font=font1) 24 | draw.text((10, 20), 'LightningATM', fill=BLACK, font=font) 25 | draw.text((7, 75), '- please insert coins -', fill=BLACK, font=font2) 26 | 27 | papirus.display(image) 28 | papirus.update() 29 | 30 | if __name__ == '__main__': 31 | try: 32 | main(sys.argv[1:]) 33 | except KeyboardInterrupt: 34 | sys.exit('interrupted') 35 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import requests 4 | import sys 5 | import config 6 | import math 7 | import qrcode 8 | 9 | from PIL import ImageFont 10 | from pathlib import Path 11 | 12 | logger = logging.getLogger("UTILS") 13 | 14 | 15 | def check_epd_size(): 16 | """Check EPD_SIZE is defined 17 | """ 18 | # A pre-set default to avoid error messages at first start and if you run a new OS version without PaPiRus 19 | # Later we should leave the config.ini "display=papiruszero2in" inital empty to be able to bypass this "def" 20 | EPD_SIZE=2.0 21 | 22 | if os.path.exists("/etc/default/epd-fuse"): 23 | exec(open("/etc/default/epd-fuse").read(), globals()) 24 | 25 | if EPD_SIZE == 0.0: 26 | print("Please select your screen size by running 'papirus-config'.") 27 | sys.exit() 28 | 29 | 30 | def create_font(font, size): 31 | """Create fonts from resources 32 | """ 33 | # Construct paths to foder with fonts 34 | pathfreemono = Path.cwd().joinpath("resources", "fonts", "FreeMono.ttf") 35 | pathfreemonobold = Path.cwd().joinpath("resources", "fonts", "FreeMonoBold.ttf") 36 | pathsawasdee = Path.cwd().joinpath("resources", "fonts", "Sawasdee-Bold.ttf") 37 | pathdotmbold = Path.cwd().joinpath("resources", "fonts", "DOTMBold.ttf") 38 | 39 | if font == "freemono": 40 | return ImageFont.truetype(pathfreemono.as_posix(), size) 41 | if font == "freemonobold": 42 | return ImageFont.truetype(pathfreemonobold.as_posix(), size) 43 | if font == "sawasdee": 44 | return ImageFont.truetype(pathsawasdee.as_posix(), size) 45 | if font == "dotmbold": 46 | return ImageFont.truetype(pathdotmbold.as_posix(), size) 47 | else: 48 | print("Font not available") 49 | 50 | 51 | def get_btc_price(fiat_code): 52 | """Get BTC -> FIAT conversion 53 | """ 54 | url = config.COINGECKO_URL_BASE + "simple/price" 55 | price = requests.get( 56 | url, params={"ids": "bitcoin", "vs_currencies": fiat_code} 57 | ).json() 58 | return price["bitcoin"][fiat_code] 59 | 60 | 61 | def get_sats(): 62 | return math.floor(config.FIAT * 100 * config.SATPRICE) 63 | 64 | 65 | def get_sats_with_fee(): 66 | return math.floor(config.SATS * (float(config.conf["atm"]["fee"]) / 100)) 67 | 68 | 69 | def generate_lnurl_qr(lnurl): 70 | """Generate an lnurl qr code from a lnurl 71 | """ 72 | lnurlqr = qrcode.QRCode( 73 | version=1, 74 | error_correction=qrcode.constants.ERROR_CORRECT_L, 75 | box_size=2, 76 | border=1, 77 | ) 78 | lnurlqr.add_data(lnurl.upper()) 79 | logger.info("LNURL QR code generated") 80 | return lnurlqr.make_image() 81 | --------------------------------------------------------------------------------