├── .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 |  | 
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 | 
42 | 
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 |  | 
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 | 
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 | 
124 |
125 | config.ini part 2 (example)
126 | 
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 |  | 
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 | 
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 |  | 
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 |
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 |
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 | 
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 | 
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 | 
10 |
11 | Wiring - Pocket Version
12 | 
13 |
14 | Clamp for ribbon cable
15 |
16 |
17 |
18 | Pocket version adapter cable
19 | 
20 |
21 | Pocket version direct wiring
22 | 
23 |
24 | Pocket version wiring build-in
25 | 
26 |
27 |
28 | Pocket version wiring build-in with coin validator | Direct wiring, without adapter cable
29 | :-------------------------:|:-------------------------:
30 |  | 
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 |
--------------------------------------------------------------------------------