Merge pull request #812 from frennkie/add-pyqt-gui

Add PyQt5 TouchUI
This commit is contained in:
frennkie 2019-11-15 21:27:31 +01:00 committed by GitHub
commit 8f3674b4f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 39788 additions and 183 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
.DS_Store .DS_Store
home.admin/.DS_Store home.admin/.DS_Store
*.log *.log
*.pyc
__pycache__ __pycache__
rpc_pb2.pyc rpc_pb2.pyc
rpc_pb2_grpc.pyc rpc_pb2_grpc.pyc

View File

@ -599,6 +599,10 @@ echo "to switch between python2/3: sudo update-alternatives --config python"
sudo apt-get -f -y install virtualenv sudo apt-get -f -y install virtualenv
sudo chown -R admin /home/admin sudo chown -R admin /home/admin
sudo -u admin bash -c "cd; virtualenv python-env-lnd; source /home/admin/python-env-lnd/bin/activate; pip install grpcio grpcio-tools googleapis-common-protos pathlib2" sudo -u admin bash -c "cd; virtualenv python-env-lnd; source /home/admin/python-env-lnd/bin/activate; pip install grpcio grpcio-tools googleapis-common-protos pathlib2"
# This Python3 virtualenv includes the site-packages because access to the PyQt5
# libs - which are installed system-wide (via apt-get) - is needed for TouchUI.
sudo -u admin bash -c "cd; virtualenv -p python3 --system-site-packages python3-env-lnd"
echo "" echo ""
echo "" echo ""

View File

@ -1,161 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 00infoLCDTK.py
#
# called by #
# /home/pi/autostart.sh
# dev/test/run with:
# sudo -i -u pi DISPLAY=:0.0 /usr/bin/python3 /home/admin/00infoLCDTK.py
import os
import sys
import json
import logging
import logging.config
import tkinter as tk
COLOR = "black"
WINFO = None
log = logging.getLogger()
def setup_logging(default_path='00infoLCDw.json'):
"""Setup logging configuration"""
path = default_path
if os.path.exists(path):
with open(path, 'rt') as f:
config = json.load(f)
logging.config.dictConfig(config)
else: # if $default_path does not exist use the following default log setup
default_config_as_json = """
{
"version": 1,
"disable_existing_loggers": false,
"formatters": {
"simple": {
"format": "%(asctime)s - %(levelname)s - %(message)s"
},
"extended": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "simple",
"stream": "ext://sys.stdout"
},
"file_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "DEBUG",
"formatter": "extended",
"filename": "00infoLCDTK.log",
"maxBytes": 10485760,
"backupCount": 0,
"encoding": "utf8"
}
},
"loggers": {
"infoblitz": {
"level": "INFO",
"handlers": ["console", "file_handler"],
"propagate": "no"
}
},
"root": {
"level": "INFO",
"handlers": ["console", "file_handler"]
}
}
"""
config = json.loads(default_config_as_json)
logging.config.dictConfig(config)
def callback_b1():
global WINFO
log.info("clicked b1 - no action yet (placeholder)")
#if sys.platform != "win32":
# os.system("xterm -fn fixed -into %d +sb -hold /home/admin/00infoLCD.sh &" % WINFO)
def callback_b2():
global WINFO
log.info("clicked b2 - no action yet (placeholder)")
#if sys.platform != "win32":
# os.system("xterm -fn fixed -into %d +sb -hold /home/admin/XXbutton2.sh &" % WINFO)
def callback_b3():
global WINFO
log.info("clicked b3 - no action yet")
#if sys.platform != "win32":
# os.system("xterm -fn fixed -into %d +sb -hold /home/admin/XXbutton3.sh &" % WINFO)
def callback_b4():
global WINFO
log.info("clicked b4")
if sys.platform != "win32":
os.system("xterm -fn fixed -into %d +sb -hold /home/admin/XXshutdown.sh &" % WINFO)
def main():
global WINFO
setup_logging()
log.info("Starting 00infoLCDTK.py")
# LCD root
root = tk.Tk()
root.config(bg=COLOR)
root.overrideredirect(1)
root.geometry("480x320+0+0")
root.title("RaspiBlitz")
# but LCD on canvas
entry = tk.Entry(root)
entry.config(bg=COLOR, highlightbackground=COLOR)
entry.pack(side="bottom", fill="x")
# button frame
frame1 = tk.Frame(entry, width=80, background="black")
frame1.pack(side="left", fill="both", expand=True)
# button 1 - no action yet (placeholder)
button1 = tk.Button(frame1, text='\u002d', fg='black', command=callback_b1, height = 1, width = 1)
button1.pack(pady=24)
# button 2 - no action yet (placeholder)
button2 = tk.Button(frame1, text='\u002d', fg='black', command=callback_b2, height = 1, width = 1)
button2.pack(pady=24)
# button 3 - no action yet (placeholder)
button3 = tk.Button(frame1, text='\u002d', fg='black', command=callback_b3, height = 1, width = 1)
button3.pack(pady=24)
#label3 = tk.Label(frame1, text='1.3', bg=COLOR, fg='white')
#label3.pack(pady=24)
# button 4 - no action yet (power down)
button4 = tk.Button(frame1, text='\N{BLACK CIRCLE}', fg='red', command=callback_b4, height = 1, width = 1)
button4.pack(pady=24)
# content frame
frame2 = tk.Frame(entry, width=400, background="grey")
frame2.pack(side="right", fill="both", expand=True)
# run terminal in
WINFO = frame2.winfo_id()
if sys.platform != "win32":
os.system("xterm -fn fixed -into %d +sb -hold /home/admin/00infoLCD.sh &" % WINFO)
# run
root.mainloop()
if __name__ == '__main__':
main()

3
home.admin/BlitzTUI/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
BlitzTUI.egg-info/*
build/*
dist/*

View File

@ -0,0 +1,30 @@
# Changelog
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.41.0] - 2019-11-15
### Added
- reduce default channel check interval to 40
- increase invoice monitor time to 1 hour
## [0.39.0] - 2019-11-04
### Added
- fix logging
- update blitz.touchscreen.sh scripts
## [0.36.0] - 2019-11-03
### Added
- require at least gRPC (grpcio) version 1.24.3 (to address atomic_exchange_8 issue)
- fix issue on "not-default" setup (not bitcoin/mainnet)
## [0.29.0] - 2019-11-02
### Added
- almost all must-have features have been implemented
## [0.22.2] - 2019-10-27
### Added
- initial creation

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018-2019 The RaspiBlitz developers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1 @@
include CHANGELOG.md

View File

@ -0,0 +1,27 @@
# Makefile
build:
python setup.py sdist bdist_wheel
build-ui:
pyuic5 -x --import-from "." -o blitztui/ui/qcode.py designer/qcode.ui
pyuic5 -x --import-from "." -o blitztui/ui/home.py designer/home.ui
pyuic5 -x --import-from "." -o blitztui/ui/off.py designer/off.ui
pyuic5 -x --import-from "." -o blitztui/ui/invoice.py designer/invoice.ui
pyrcc5 -o blitztui/ui/resources_rc.py resources.qrc
clean:
rm -rf .eggs .tox .coverage .coverage.data .cache build
rm -rf blitz-tui.log BlitzTUI.egg-info
find ./ -iname "*.pyc" -delete
find ./ -type d -iname "__pycache__" -delete
test:
tox
upload:
twine upload --skip-existing dist/* -r pypi
upload-test:
twine upload --skip-existing dist/* -r pypitest

View File

@ -0,0 +1,65 @@
# BlitzTUI
[![VersionBadge](https://badge.fury.io/py/BlitzTUI.svg)](https://badge.fury.io/)
[![LicenseBadge](https://img.shields.io/badge/license-MIT-blue.svg)](https://shields.io/)
[![PythonVersions](https://img.shields.io/badge/python-3.4%2C%203.5%2C%203.6%2C%203.7%2C%203.8-blue.svg)](https://shields.io/)
BlitzTUI is a part of the RaspiBlitz project and implements a Touch User Interface in PyQt5.
## Installation
### Prerequisite
QT is needed. Please install PyQt5 (see below).
### Dependencies
#### Debian/Ubuntu (and similar)
```
apt-get install python3-pyqt5
```
#### PIP
The PIP dependencies are installed automatically - this listing is "FYI"
* grpcio
* googleapis-common-protos
* inotify
* psutil
* pyqtspinner
* qrcode
### Install BlitzTUI
```
pip install BlitzTUI
```
**or** consider using a virtual environment
```
virtualenv -p python3 --system-site-packages venv
source venv/bin/activate
pip install BlitzTUI
```
## Error Messages
For now the following warning/error/info messages can be ignored. If anybody knows how to suppress
or fix them please send a PR (or open an issue).
```
libEGL warning: DRI2: failed to authenticate
QStandardPaths: XDG_RUNTIME_DIR not set, defaulting to '/tmp/runtime-pi'
2019-11-02 20:01:21,504 - root - INFO - main:214 - /usr/bin/xterm: cannot load font "-Misc-Fixed-medium-R-*-*-13-120-75-75-C-120-ISO10646-1"
```
## License
[MIT License](http://en.wikipedia.org/wiki/MIT_License)

View File

@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
import logging
from blitztui.version import __version__
from blitztui.file_logger import setup_logging
log = logging.getLogger()
setup_logging()
log.info("Starting BlitzTUI v{}".format(__version__))

View File

@ -0,0 +1,273 @@
# -*- coding: utf-8 -*-
import base64
import codecs
import logging
import os
import sys
from os.path import isfile
import grpc
log = logging.getLogger(__name__)
IS_WIN32_ENV = sys.platform == "win32"
if IS_WIN32_ENV:
cur_path = os.path.abspath(os.path.curdir)
config_script1 = os.path.join(cur_path, "home.admin", "config.scripts")
config_script2 = os.path.abspath(os.path.join(cur_path, "..", "..", "home.admin", "config.scripts"))
sys.path.insert(1, config_script1)
sys.path.insert(1, config_script2)
else:
sys.path.insert(1, '/home/admin/config.scripts')
from lndlibs import rpc_pb2 as ln
try:
from lndlibs import rpc_pb2_grpc as lnrpc
except ModuleNotFoundError as err:
log.error("ModuleNotFoundError - most likely an issue with incompatible Python3 import.\n"
"Please run the following two lines to fix this: \n"
"\n"
"sed -i -E '1 a from __future__ import absolute_import' "
"/home/admin/config.scripts/lndlibs/rpc_pb2_grpc.py\n"
"sed -i -E 's/^(import.*_pb2)/from . \\1/' /home/admin/config.scripts/lndlibs/rpc_pb2_grpc.py")
sys.exit(1)
if not IS_WIN32_ENV:
import psutil
MACAROON_LIST = ["admin", "readonly", "invoice"]
class AdminStub(lnrpc.LightningStub):
def __init__(self, network="bitcoin", chain="main"):
self.channel = get_rpc_channel(macaroon_path=build_macaroon_path("admin", network=network, chain=chain))
super().__init__(self.channel)
class ReadOnlyStub(lnrpc.LightningStub):
def __init__(self, network="bitcoin", chain="main"):
self.channel = get_rpc_channel(macaroon_path=build_macaroon_path("readonly", network=network, chain=chain))
super().__init__(self.channel)
class InvoiceStub(lnrpc.LightningStub):
def __init__(self, network="bitcoin", chain="main"):
self.channel = get_rpc_channel(macaroon_path=build_macaroon_path("invoice", network=network, chain=chain))
super().__init__(self.channel)
def convert_r_hash(r_hash):
""" convert_r_hash
>>> convert_r_hash("+eMo9YTaZIjkJacclb6LYUocwa0q7cgVOBPf/0aclYQ=")
'f9e328f584da6488e425a71c95be8b614a1cc1ad2aedc8153813dfff469c9584'
"""
r_hash_bytes = codecs.decode(r_hash.encode(), 'base64')
r_hash_hex_bytes = codecs.encode(r_hash_bytes, 'hex')
return r_hash_hex_bytes.decode()
def convert_r_hash_hex(r_hash_hex):
""" convert_r_hash_hex
>>> convert_r_hash_hex("f9e328f584da6488e425a71c95be8b614a1cc1ad2aedc8153813dfff469c9584")
'+eMo9YTaZIjkJacclb6LYUocwa0q7cgVOBPf/0aclYQ='
"""
r_hash = codecs.decode(r_hash_hex, 'hex')
r_hash_b64_bytes = base64.b64encode(r_hash)
return r_hash_b64_bytes.decode()
def convert_r_hash_hex_bytes(r_hash_hex_bytes):
""" convert_r_hash_hex_bytes
>>> convert_r_hash_hex_bytes(b'\xf9\xe3(\xf5\x84\xdad\x88\xe4%\xa7\x1c\x95\xbe\x8baJ\x1c\xc1\xad*\xed\xc8\x158\x13\xdf\xffF\x9c\x95\x84')
'f9e328f584da6488e425a71c95be8b614a1cc1ad2aedc8153813dfff469c9584'
"""
r_hash_hex_bytes = codecs.encode(r_hash_hex_bytes, 'hex')
return r_hash_hex_bytes.decode()
def get_rpc_channel(host="localhost", port="10009", cert_path=None, macaroon_path=None):
if not macaroon_path:
raise Exception("need to specify a macaroon path!")
def metadata_callback(context, callback):
# for more info see grpc docs
callback([('macaroon', macaroon)], None)
# Due to updated ECDSA generated tls.cert we need to let gprc know that
# we need to use that cipher suite otherwise there will be a handshake
# error when we communicate with the lnd rpc server.
os.environ["GRPC_SSL_CIPHER_SUITES"] = 'HIGH+ECDSA'
if not cert_path:
cert_path = os.path.expanduser('~/.lnd/tls.cert')
assert isfile(cert_path) and os.access(cert_path, os.R_OK), \
"File {} doesn't exist or isn't readable".format(cert_path)
cert = open(cert_path, 'rb').read()
with open(macaroon_path, 'rb') as f:
macaroon_bytes = f.read()
macaroon = codecs.encode(macaroon_bytes, 'hex')
# build ssl credentials using the cert the same as before
cert_creds = grpc.ssl_channel_credentials(cert)
# now build meta data credentials
auth_creds = grpc.metadata_call_credentials(metadata_callback)
# combine the cert credentials and the macaroon auth credentials
# such that every call is properly encrypted and authenticated
combined_creds = grpc.composite_channel_credentials(cert_creds, auth_creds)
# finally pass in the combined credentials when creating a channel
return grpc.secure_channel('{}:{}'.format(host, port), combined_creds)
def build_macaroon_path(name=None, network="bitcoin", chain="main"):
if not name.lower() in MACAROON_LIST:
raise Exception("name must be one of: {}".format(", ".join(MACAROON_LIST)))
macaroon_path = os.path.expanduser('~/.lnd/data/chain/{}/{}net/{}.macaroon'.format(network, chain, name.lower()))
assert isfile(macaroon_path) and os.access(macaroon_path, os.R_OK), \
"File {} doesn't exist or isn't readable".format(macaroon_path)
return macaroon_path
def check_lnd(stub, proc_name="lnd", rpc_listen_ports=None):
if not rpc_listen_ports:
rpc_listen_ports = [10009]
pid_ok = False
listen_ok = False
unlocked = False
synced_to_chain = False
synced_to_graph = False
if IS_WIN32_ENV:
return pid_ok, listen_ok, unlocked, synced_to_chain, synced_to_graph
if not [p.info for p in psutil.process_iter(attrs=['pid', 'name']) if proc_name in p.info['name']]:
return pid_ok, listen_ok, unlocked, synced_to_chain, synced_to_graph
else:
pid_ok = True
if not [net_con for net_con in psutil.net_connections(kind='inet')
if (net_con.status == psutil.CONN_LISTEN and net_con.laddr[1] in rpc_listen_ports)]:
return pid_ok, listen_ok, unlocked, synced_to_chain, synced_to_graph
else:
listen_ok = True
try:
get_info = stub.GetInfo(ln.GetInfoRequest())
unlocked = True
synced_to_chain = get_info.synced_to_chain
synced_to_graph = get_info.synced_to_graph
except grpc.RpcError as err:
if err._state.__dict__['code'] == grpc.StatusCode.UNIMPLEMENTED:
log.debug("wallet is 'locked'")
else:
log.warning("an unknown RpcError occurred")
log.warning(err)
except Exception as err:
log.warning("an error occurred: {}".format(err))
return pid_ok, listen_ok, unlocked, synced_to_chain, synced_to_graph
def check_lnd_channels(stub):
"""let's assume that check_lnd() was called just before calling this"""
total_active_channels = 0
total_remote_balance_sat = 0
try:
request = ln.ListChannelsRequest(
active_only=True,
inactive_only=False,
public_only=False,
private_only=False,
)
response = stub.ListChannels(request)
total_active_channels = len(response.channels)
for channel in response.channels:
# log.debug(channel)
total_remote_balance_sat += channel.remote_balance
except grpc.RpcError as err:
if err._state.__dict__['code'] == grpc.StatusCode.UNIMPLEMENTED:
log.debug("wallet is 'locked'")
else:
log.warning("an unknown RpcError occurred")
log.warning(err)
except Exception as err:
log.warning("an error occurred: {}".format(err))
return total_active_channels, total_remote_balance_sat
def check_invoice_paid(stub, invoice_r_hash, num_max_invoices=3):
# ToDo error handling
request = ln.ListInvoiceRequest(num_max_invoices=num_max_invoices, reversed=True)
response = stub.ListInvoices(request)
for invoice in response.invoices:
hex_str = convert_r_hash_hex_bytes(invoice.r_hash)
if hex_str == invoice_r_hash:
if invoice.settled:
log.debug("found - and settled: {}".format(invoice))
amt_paid_sat = invoice.amt_paid_sat
return True, amt_paid_sat
else:
log.debug("found - but NOT settled.")
return False, None
else:
log.warning("invoice NOT found")
return False, None
def create_invoice(stub, memo="", value=0):
# ToDo error handling
request = ln.Invoice(memo=memo, value=value)
response = stub.AddInvoice(request)
return response
def get_node_uri(stub):
# ToDo error handling
response = stub.GetInfo(ln.GetInfoRequest())
if response.uris:
return response.uris[0]
def main():
network = "bitcoin"
chain = "main"
stub_readonly = ReadOnlyStub(network=network, chain=chain)
pid_ok, listen_ok, unlocked, synced_to_chain, synced_to_graph = check_lnd(stub_readonly)
print(pid_ok, listen_ok, unlocked, synced_to_chain, synced_to_graph)
if pid_ok and listen_ok and unlocked:
node_uri = get_node_uri(stub_readonly)
print("Node URI: {}".format(node_uri))
num, sats = check_lnd_channels(stub_readonly)
print("Total Channels: {}".format(num))
print("Total Remote Capacity: {}".format(sats))
if __name__ == "__main__":
main()

View File

@ -0,0 +1,195 @@
# -*- coding: utf-8 -*-
import logging
import os
from configparser import ConfigParser, DEFAULTSECT
log = logging.getLogger(__name__)
class LndConfig(object):
def __init__(self, abs_path="/mnt/hdd/lnd/lnd.conf"):
self.abs_path = abs_path
# default values for LND Configuration
self.rpc_listen = ""
@property
def rpc_listen_host(self):
return self.rpc_listen.split(":")[0]
@property
def rpc_listen_port(self):
try:
return int(self.rpc_listen.split(":")[1])
except (IndexError, TypeError, ValueError):
return 0
def reload(self):
"""load config from file"""
parser = ConfigParser()
log.debug("loading config from file: {}".format(self.abs_path))
with open(self.abs_path) as f:
parser.read_string(f.read())
app_options = parser["Application Options"]
self.rpc_listen = get_str_clean(app_options, "rpclisten", self.rpc_listen)
class RaspiBlitzConfig(object):
def __init__(self, abs_path="/mnt/hdd/raspiblitz.conf"):
self.abs_path = abs_path
# default values for RaspiBlitz Configuration
self.auto_nat_discovery = False
self.auto_pilot = False
self.auto_unlock = False
self.chain = ""
self.dynDomain = ""
self.dyn_update_url = ""
self.hostname = ""
self.invoice_allow_donations = False
self.invoice_default_amount = 402
self.lcd_rotate = False
self.lnd_address = ""
self.lnd_port = ""
self.network = ""
self.public_ip = ""
self.rtl_web_interface = False
self.run_behind_tor = False
self.ssh_tunnel = ""
self.touchscreen = False
self.version = ""
def reload(self):
"""load config from file"""
parser = ConfigParser()
log.debug("loading config from file: {}".format(self.abs_path))
with open(self.abs_path) as f:
parser.read_string("[{}]\n".format(DEFAULTSECT) + f.read())
default_s = parser[DEFAULTSECT]
self.auto_nat_discovery = default_s.getboolean("autoNatDiscovery", self.auto_nat_discovery)
self.auto_pilot = default_s.getboolean("autoPilot", self.auto_pilot)
self.auto_unlock = default_s.getboolean("autoUnlock", self.auto_unlock)
self.chain = get_str_clean(default_s, "chain", self.chain)
self.dynDomain = get_str_clean(default_s, "dynDomain", self.dynDomain)
self.dyn_update_url = get_str_clean(default_s, "dynUpdateUrl", self.dyn_update_url)
self.hostname = get_str_clean(default_s, "hostname", self.hostname)
self.invoice_allow_donations = default_s.getboolean("invoiceAllowDonations", self.invoice_allow_donations)
self.invoice_default_amount = get_int_safe(default_s, "invoiceDefaultAmount", self.invoice_default_amount)
self.lcd_rotate = default_s.getboolean("lcdrotate", self.lcd_rotate)
self.lnd_address = get_str_clean(default_s, "lndAddress", self.lnd_address)
self.lnd_port = get_str_clean(default_s, "lndPort", self.lnd_port)
self.network = get_str_clean(default_s, "network", self.network)
self.public_ip = get_str_clean(default_s, "publicIP", self.public_ip)
self.rtl_web_interface = default_s.getboolean("rtlWebinterface", self.rtl_web_interface)
self.run_behind_tor = default_s.getboolean("runBehindTor", self.run_behind_tor)
self.ssh_tunnel = get_str_clean(default_s, "sshtunnel", self.ssh_tunnel)
self.touchscreen = default_s.getboolean("touchscreen", self.touchscreen)
self.version = get_str_clean(default_s, "raspiBlitzVersion", self.version)
class RaspiBlitzInfo(object):
def __init__(self, abs_path="/home/admin/raspiblitz.info"):
self.abs_path = abs_path
# default values for RaspiBlitz Info
self.base_image = ""
self.chain = ""
self.message = ""
self.network = ""
self.setup_step = 0
self.state = ""
self.undervoltage_reports = 0
def reload(self):
"""load config from file"""
parser = ConfigParser()
log.debug("loading config from file: {}".format(self.abs_path))
with open(self.abs_path) as f:
parser.read_string("[{}]\n".format(DEFAULTSECT) + f.read())
default_s = parser[DEFAULTSECT]
self.base_image = get_str_clean(default_s, "baseimage", self.base_image)
self.chain = get_str_clean(default_s, "chain", self.chain)
self.message = get_str_clean(default_s, "message", self.message)
self.network = get_str_clean(default_s, "network", self.network)
self.setup_step = get_int_safe(default_s, "setupStep", self.setup_step)
self.state = get_str_clean(default_s, "state", self.state)
self.undervoltage_reports = get_int_safe(default_s, "undervoltageReports", self.undervoltage_reports)
def get_int_safe(cp_section, key, default_value):
"""take a ConfigParser section, get key that might be string encoded int and return int"""
try:
value = cp_section.getint(key, default_value)
except ValueError:
_value = cp_section.get(key)
value = int(_value.strip("'").strip('"')) # this will raise an Exception if int() fails!
return value
def get_str_clean(cp_section, key, default_value):
"""take a ConfigParser section, get key and strip leading and trailing \' and \" chars"""
value = cp_section.get(key, default_value)
if not value:
return ""
return value.lstrip('"').lstrip("'").rstrip('"').rstrip("'")
def main():
lnd_cfg = LndConfig()
if os.path.exists(lnd_cfg.abs_path):
lnd_cfg.reload()
print("=======\n= LND =\n=======")
print("rpc_list: \t\t{}".format(lnd_cfg.rpc_listen))
print("rpc_list_host: \t\t{}".format(lnd_cfg.rpc_listen_host))
print("rpc_list_port: \t\t{}".format(lnd_cfg.rpc_listen_port))
print("")
rb_cfg = RaspiBlitzConfig()
if os.path.exists(rb_cfg.abs_path):
rb_cfg.reload()
print("====================\n= RaspiBlitzConfig =\n====================")
print("auto_nat_discovery: \t\t{}".format(rb_cfg.auto_nat_discovery))
print("auto_pilot: \t\t\t{}".format(rb_cfg.auto_pilot))
print("auto_unlock: \t\t\t{}".format(rb_cfg.auto_unlock))
print("chain: \t\t\t\t{}".format(rb_cfg.chain))
print("dynDomain: \t\t\t{}".format(rb_cfg.dynDomain))
print("dyn_update_url: \t\t{}".format(rb_cfg.dyn_update_url))
print("hostname: \t\t\t{}".format(rb_cfg.hostname))
print("invoice_allow_donations: \t{}".format(rb_cfg.invoice_allow_donations))
print("invoice_default_amount: \t{}".format(rb_cfg.invoice_default_amount))
print("lcd_rotate: \t\t\t{}".format(rb_cfg.lcd_rotate))
print("lnd_address: \t\t\t{}".format(rb_cfg.lnd_address))
print("lnd_port: \t\t\t{}".format(rb_cfg.lnd_port))
print("network: \t\t\t{}".format(rb_cfg.network))
print("public_ip: \t\t\t{}".format(rb_cfg.public_ip))
print("rtl_web_interface: \t\t{}".format(rb_cfg.rtl_web_interface))
print("run_behind_tor: \t\t{}".format(rb_cfg.run_behind_tor))
print("ssh_tunnel: \t\t\t{}".format(rb_cfg.ssh_tunnel))
print("touchscreen: \t\t\t{}".format(rb_cfg.touchscreen))
print("version: \t\t\t{}".format(rb_cfg.version))
print("")
rb_info = RaspiBlitzInfo()
if os.path.exists(rb_info.abs_path):
rb_info.reload()
print("==================\n= RaspiBlitzInfo =\n==================")
print("state: \t\t{}".format(rb_info.state))
print("")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,48 @@
import json
import logging
import logging.config
import os
import sys
IS_WIN32_ENV = sys.platform == "win32"
def setup_logging(default_path=os.path.abspath(os.path.expanduser('~/.blitz-tui.json'))):
"""Setup logging configuration"""
path = default_path
if os.path.exists(path):
with open(path, 'rt') as f:
config = json.load(f)
logging.config.dictConfig(config)
else: # if $default_path does not exist use the following default log setup
if IS_WIN32_ENV:
log_file = "blitz-tui.log"
else:
log_file = os.path.abspath(os.path.expanduser('~/blitz-tui.log'))
default_config_as_dict = dict(
version=1,
disable_existing_loggers=False,
formatters={'simple': {'format': '%(asctime)s - %(levelname)s - %(message)s'},
'extended': {
'format': '%(asctime)s - %(name)s - %(levelname)s - %(module)s:%(lineno)d - %(message)s'}},
handlers={'console': {'class': 'logging.StreamHandler',
'level': 'INFO',
'formatter': 'extended',
'stream': 'ext://sys.stdout'},
'file_handler': {'class': 'logging.handlers.RotatingFileHandler',
'level': 'DEBUG',
'formatter': 'extended',
'filename': log_file,
'maxBytes': 10485760,
'backupCount': 0,
'encoding': 'utf8'}},
loggers={'infoblitz': {'level': 'DEBUG',
'handlers': ['console', 'file_handler'],
'propagate': 'no'}},
root={'level': 'DEBUG', 'handlers': ['console', 'file_handler']}
)
logging.config.dictConfig(default_config_as_dict)

View File

@ -0,0 +1,45 @@
import logging
import sys
from PyQt5.QtCore import QThread, pyqtSignal
log = logging.getLogger(__name__)
if sys.platform == "win32":
log.info("skipping inotify on win32 as it is not supported")
else:
import inotify.adapters
import inotify.constants
class FileWatcherThread(QThread):
signal = pyqtSignal()
def __init__(self, dir_names, file_names, *args, **kwargs):
QThread.__init__(self, *args, **kwargs)
self.dir_names = dir_names
self.file_names = file_names
def run(self):
# run method gets called when we start the thread
if sys.platform == "win32":
log.info("skipping inotify on win32 as it is not supported")
return
log.info("starting config watcher")
i = inotify.adapters.Inotify()
mask = inotify.constants.IN_MODIFY | inotify.constants.IN_CLOSE_WRITE
for dir_name in self.dir_names:
i.add_watch(dir_name, mask=mask)
for event in i.event_gen(yield_nones=False):
_, type_names, path, filename = event
log.debug("PATH=[{}] FILENAME=[{}] EVENT_TYPES={}".format(
path, filename, type_names))
if path in self.dir_names and filename in self.file_names:
log.info("watched file was modified/touched")
self.signal.emit()

View File

@ -0,0 +1,646 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
import itertools
import logging
import os
import signal
import sys
import time
from argparse import RawTextHelpFormatter
from functools import lru_cache
from io import BytesIO
from threading import Event
import qrcode
from PyQt5.QtCore import Qt, QProcess, QThread, pyqtSignal, QCoreApplication, QTimer, QEventLoop
from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QMainWindow, QApplication, QDialog, QDialogButtonBox
from blitztui.client import ReadOnlyStub, InvoiceStub
from blitztui.client import check_lnd, check_lnd_channels
from blitztui.client import check_invoice_paid, create_invoice, get_node_uri
from blitztui.client import convert_r_hash_hex_bytes
from blitztui.config import LndConfig, RaspiBlitzConfig, RaspiBlitzInfo
from blitztui.file_watcher import FileWatcherThread
from blitztui.memo import adjective_noun_pair
from blitztui.version import __version__
from blitztui.ui.home import Ui_MainWindow
from blitztui.ui.invoice import Ui_DialogSelectInvoice
from blitztui.ui.off import Ui_DialogConfirmOff
from blitztui.ui.qcode import Ui_DialogShowQrCode
from pyqtspinner.spinner import WaitingSpinner
log = logging.getLogger()
IS_DEV_ENV = os.getenv('RASPIBLITZ_DEV', '0').lower() in ['1', 'true', 't', 'y', 'yes', 'on']
IS_WIN32_ENV = sys.platform == "win32"
SCREEN_HEIGHT = 318
LND_CONF = "/mnt/hdd/lnd/lnd.conf"
RB_CONF = "/mnt/hdd/raspiblitz.conf"
RB_INFO = "/home/admin/raspiblitz.info"
STATUS_INTERVAL_LND = 30
STATUS_INTERVAL_LND_CHANNELS = 120
INVOICE_CHECK_TIMEOUT = 1800
INVOICE_CHECK_INTERVAL = 2.0 # 1800*2.0s == 3600s == 1 Hour during which the invoice is monitored
SCREEN_NODE_URI = "Node URI"
SCREEN_INVOICE = "Invoice"
class AppWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(AppWindow, self).__init__(*args, **kwargs)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
# translations..?!
self._translate = QCoreApplication.translate
if IS_WIN32_ENV:
log.info("using dummy config on win32")
lnd_cfg_abs_path = os.path.join(os.path.dirname(__file__), "..", "data", os.path.basename(LND_CONF))
rb_cfg_abs_path = os.path.join(os.path.dirname(__file__), "..", "data", os.path.basename(RB_CONF))
rb_info_abs_path = os.path.join(os.path.dirname(__file__), "..", "data", os.path.basename(RB_INFO))
else:
lnd_cfg_abs_path = LND_CONF
rb_cfg_abs_path = RB_CONF
rb_info_abs_path = RB_INFO
# read config and info files
if not os.path.exists(lnd_cfg_abs_path):
raise Exception("file does not exist: {}".format(lnd_cfg_abs_path))
if not os.path.exists(rb_cfg_abs_path):
raise Exception("file does not exist: {}".format(rb_cfg_abs_path))
if not os.path.exists(rb_info_abs_path):
raise Exception("file does not exist: {}".format(rb_info_abs_path))
self.lnd_cfg = LndConfig(lnd_cfg_abs_path)
self.lnd_cfg.reload()
self.rb_cfg = RaspiBlitzConfig(rb_cfg_abs_path)
self.rb_cfg.reload()
self.rb_info = RaspiBlitzInfo(rb_info_abs_path)
self.rb_info.reload()
# initialize attributes
self.invoice_to_check = None
self.invoice_to_check_flag = None
self.uptime = 0
self.status_lnd_due = 0
self.status_lnd_interval = STATUS_INTERVAL_LND
self.status_lnd_pid_ok = False
self.status_lnd_listen_ok = False
self.status_lnd_unlocked = False
self.status_lnd_synced_to_chain = False
self.status_lnd_synced_to_graph = False
self.status_lnd_channel_due = 0
self.status_lnd_channel_interval = STATUS_INTERVAL_LND_CHANNELS
self.status_lnd_channel_total_active = 0
self.status_lnd_channel_total_remote_balance = 0
# initial updates
self.update_uptime()
self.update_status_lnd()
self.update_status_lnd_channels()
# initial update of Main Window Title Bar
self.update_title_bar()
# Align Main Window Top Left
self.move(0, 0)
# set as maximized (unless on Windows dev host)
if IS_WIN32_ENV:
log.info("not maximizing window on win32")
else:
self.setWindowState(Qt.WindowMaximized)
# Bindings: buttons
self.ui.pushButton_1.clicked.connect(self.on_button_1_clicked)
self.ui.pushButton_2.clicked.connect(self.on_button_2_clicked)
self.ui.pushButton_3.clicked.connect(self.on_button_3_clicked)
self.ui.pushButton_4.clicked.connect(self.on_button_4_clicked)
# disable button 1 for now
self.ui.pushButton_1.setEnabled(False)
# connect error dismiss button and hide for start
self.ui.buttonBox_close.button(QDialogButtonBox.Close).setText("Ok")
self.ui.buttonBox_close.button(QDialogButtonBox.Close).clicked.connect(self.hide_error)
self.hide_error()
# Show QR Code Dialog Windows
self.w_qr_code = QDialog(flags=(Qt.Dialog | Qt.FramelessWindowHint))
self.ui_qr_code = Ui_DialogShowQrCode()
self.ui_qr_code.setupUi(self.w_qr_code)
self.w_qr_code.move(0, 0)
# SPINNER for CR Code Dialog Window
self.ui_qr_code.spinner = WaitingSpinner(self.w_qr_code)
self.beat_thread = BeatThread()
self.beat_thread.signal.connect(self.process_beat)
self.beat_thread.start()
self.generate_qr_code_thread = GenerateQrCodeThread()
self.generate_qr_code_thread.signal.connect(self.generate_qr_code_finished)
self.file_watcher = FileWatcherThread(
dir_names=[os.path.dirname(LND_CONF), os.path.dirname(RB_CONF), os.path.dirname(RB_INFO)],
file_names=[os.path.basename(LND_CONF), os.path.basename(RB_CONF), os.path.basename(RB_INFO)],
)
self.file_watcher.signal.connect(self.update_watched_attr)
self.file_watcher.start()
# finally start 00infoBlitz.sh in dedicated xterm frame
self.start_info_lcd()
self.show()
def start_info_lcd(self, pause=12):
# if system has been running for more than 90 seconds then skip pause
if self.uptime > 90:
pause = 0
process = QProcess(self)
process.setProcessChannelMode(QProcess.MergedChannels)
# connect the stdout_item to the Process StandardOutput
# it gets constantly update as the process emit std output
process.readyReadStandardOutput.connect(
lambda: log.info(str(process.readAllStandardOutput().data().decode('utf-8'))))
process.start('xterm', ['-fn', 'fixed', '-into', str(int(self.ui.widget.winId())),
'+sb', '-hold', '-e', 'bash -c \"/home/admin/00infoLCD.sh --pause {}\"'.format(pause)])
def check_invoice(self, flag, tick=0):
log.info("checking invoice paid (Tick: {})".format(tick))
self.invoice_to_check_flag = flag
if tick >= INVOICE_CHECK_TIMEOUT:
log.debug("canceled checking invoice paid")
flag.set()
if IS_DEV_ENV:
res = False
amt_paid_sat = 123123402
if tick == 5:
res = True
else:
stub_readonly = ReadOnlyStub(network=self.rb_cfg.network, chain=self.rb_cfg.chain)
res, amt_paid_sat = check_invoice_paid(stub_readonly, self.invoice_to_check)
log.debug("result of invoice check: {}".format(res))
if res:
log.debug("paid!")
self.ui_qr_code.qcode.setMargin(8)
self.ui_qr_code.qcode.setPixmap(QPixmap(":/RaspiBlitz/images/Paid_Stamp.png"))
if amt_paid_sat:
self.ui_qr_code.status_value.setText("Paid")
self.ui_qr_code.amt_paid_value.setText("{}".format(amt_paid_sat))
else:
self.ui_qr_code.status_value.setText("Paid")
flag.set()
def update_status_lnd(self):
if IS_WIN32_ENV:
return
# log.debug("update_status_lnd due: {}".format(self.status_lnd_due))
if self.status_lnd_due <= self.uptime:
log.debug("updating status_lnd")
stub_readonly = ReadOnlyStub(network=self.rb_cfg.network, chain=self.rb_cfg.chain)
pid_ok, listen_ok, unlocked, synced_to_chain, synced_to_graph = check_lnd(stub_readonly)
self.status_lnd_pid_ok = pid_ok
self.status_lnd_listen_ok = listen_ok
self.status_lnd_unlocked = unlocked
self.status_lnd_synced_to_chain = synced_to_chain
self.status_lnd_synced_to_graph = synced_to_graph
# set next due time
self.status_lnd_due = self.uptime + self.status_lnd_interval
def update_status_lnd_channels(self):
if IS_WIN32_ENV:
return
# log.debug("update_status_lnd_channel due: {}".format(self.status_lnd_channel_due))
if self.status_lnd_channel_due <= self.uptime:
log.debug("updating status_lnd_channels")
stub_readonly = ReadOnlyStub(network=self.rb_cfg.network, chain=self.rb_cfg.chain)
self.status_lnd_channel_total_active, self.status_lnd_channel_total_remote_balance = \
check_lnd_channels(stub_readonly)
# set next due time
self.status_lnd_channel_due = self.uptime + self.status_lnd_channel_interval
def update_title_bar(self):
log.debug("updating: Main Window Title Bar")
self.setWindowTitle(self._translate("MainWindow", "RaspiBlitz v{} - {} - {}net".format(self.rb_cfg.version,
self.rb_cfg.network,
self.rb_cfg.chain)))
def update_uptime(self):
if IS_WIN32_ENV:
self.uptime += 1
else:
with open('/proc/uptime', 'r') as f:
self.uptime = float(f.readline().split()[0])
# log.info("Uptime: {}".format(self.uptime))
def process_beat(self, _):
self.update_uptime()
self.update_status_lnd()
self.update_status_lnd_channels()
def update_watched_attr(self):
log.debug("updating: watched attributes")
self.lnd_cfg.reload()
self.rb_cfg.reload()
self.rb_info.reload()
# add anything here that should be updated now too
self.update_title_bar()
def hide_error(self):
self.ui.error_label.hide()
self.ui.buttonBox_close.hide()
def show_qr_code(self, data, screen=None, memo=None, status=None, inv_amt=None, amt_paid="N/A"):
log.debug("show_qr_code: {}".format(data))
# reset to logo and set text
self.ui_qr_code.qcode.setMargin(48)
self.ui_qr_code.qcode.setPixmap(QPixmap(":/RaspiBlitz/images/RaspiBlitz_Logo_Stacked.png"))
if screen == SCREEN_NODE_URI:
self.ui_qr_code.memo_key.show()
self.ui_qr_code.memo_key.setText("Node URI")
_tmp = data.split("@")
pub = _tmp[0]
_tmp2 = _tmp[1].split(":")
host = _tmp2[0]
port = _tmp2[1]
n = 16
pub = [(pub[i:i + n]) for i in range(0, len(pub), n)]
host = [(host[i:i + n]) for i in range(0, len(host), n)]
self.ui_qr_code.memo_value.show()
self.ui_qr_code.memo_value.setText("{} \n@\n{} \n:{}".format(" ".join(pub), " ".join(host), port))
self.ui_qr_code.status_key.hide()
self.ui_qr_code.status_value.hide()
self.ui_qr_code.inv_amt_key.hide()
self.ui_qr_code.inv_amt_value.hide()
self.ui_qr_code.amt_paid_key.hide()
self.ui_qr_code.amt_paid_value.hide()
if screen == SCREEN_INVOICE:
self.ui_qr_code.memo_key.show()
self.ui_qr_code.memo_key.setText("Invoice Memo")
self.ui_qr_code.memo_value.show()
self.ui_qr_code.memo_value.setText(memo)
self.ui_qr_code.status_key.show()
self.ui_qr_code.status_value.show()
self.ui_qr_code.status_value.setText(status)
self.ui_qr_code.inv_amt_key.show()
self.ui_qr_code.inv_amt_value.show()
self.ui_qr_code.inv_amt_value.setText("{}".format(inv_amt))
self.ui_qr_code.amt_paid_key.show()
self.ui_qr_code.amt_paid_value.show()
self.ui_qr_code.amt_paid_value.setText("{}".format(amt_paid))
# set function and start thread
self.generate_qr_code_thread.data = data
self.generate_qr_code_thread.start()
self.ui_qr_code.spinner.start()
self.w_qr_code.activateWindow()
self.w_qr_code.show()
rsp = self.w_qr_code.exec_()
if rsp == QDialog.Accepted:
log.info("QR: pressed OK - canceling invoice check")
if self.invoice_to_check_flag:
self.invoice_to_check_flag.set()
def generate_qr_code_finished(self, img):
buf = BytesIO()
img.save(buf, "PNG")
qt_pixmap = QPixmap()
qt_pixmap.loadFromData(buf.getvalue(), "PNG")
self.ui_qr_code.spinner.stop()
self.ui_qr_code.qcode.setMargin(2)
self.ui_qr_code.qcode.setPixmap(qt_pixmap)
def on_button_1_clicked(self):
log.debug("clicked: B1: {}".format(self.winId()))
# self.start_info_lcd(pause=0)
def on_button_2_clicked(self):
log.debug("clicked: B2: {}".format(self.winId()))
if not (self.status_lnd_pid_ok and self.status_lnd_listen_ok):
log.warning("LND is not ready")
self.ui.error_label.show()
self.ui.error_label.setText("Err: LND is not ready!")
self.ui.buttonBox_close.show()
return
if not self.status_lnd_unlocked:
log.warning("LND is locked")
self.ui.error_label.show()
self.ui.error_label.setText("Err: LND is locked")
self.ui.buttonBox_close.show()
return
data = self.get_node_uri()
if data:
self.show_qr_code(data, SCREEN_NODE_URI)
else:
log.warning("Node URI is none!")
# TODO(frennkie) inform user
def on_button_3_clicked(self):
log.debug("clicked: B3: {}".format(self.winId()))
if not (self.status_lnd_pid_ok and self.status_lnd_listen_ok):
log.warning("LND is not ready")
self.ui.error_label.show()
self.ui.error_label.setText("Err: LND is not ready!")
self.ui.buttonBox_close.show()
return
if not self.status_lnd_unlocked:
log.warning("LND is locked")
self.ui.error_label.show()
self.ui.error_label.setText("Err: LND is locked")
self.ui.buttonBox_close.show()
return
if not self.status_lnd_channel_total_active:
log.warning("not creating invoice: unable to receive - no open channels")
self.ui.error_label.show()
self.ui.error_label.setText("Err: No open channels!")
self.ui.buttonBox_close.show()
return
if not self.status_lnd_channel_total_remote_balance:
log.warning("not creating invoice: unable to receive - no remote capacity on any channel")
self.ui.error_label.show()
self.ui.error_label.setText("Err: No remote capacity!")
self.ui.buttonBox_close.show()
return
dialog_b1 = QDialog(flags=(Qt.Dialog | Qt.FramelessWindowHint))
ui = Ui_DialogSelectInvoice()
ui.setupUi(dialog_b1)
dialog_b1.move(0, 0)
ui.buttonBox.button(QDialogButtonBox.Yes).setText("{} SAT".format(self.rb_cfg.invoice_default_amount))
ui.buttonBox.button(QDialogButtonBox.Ok).setText("Donation")
if self.rb_cfg.invoice_allow_donations:
ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True)
else:
ui.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)
ui.buttonBox.button(QDialogButtonBox.Cancel).setText("Cancel")
ui.buttonBox.button(QDialogButtonBox.Yes).clicked.connect(self.b3_invoice_set_amt)
ui.buttonBox.button(QDialogButtonBox.Ok).clicked.connect(self.b3_invoice_custom_amt)
dialog_b1.show()
rsp = dialog_b1.exec_()
if not rsp == QDialog.Accepted:
log.info("B3: pressed is: Cancel")
def b3_invoice_set_amt(self):
log.info("b1 option: set amount")
check_invoice_thread = ClockStoppableThread(Event(), interval=INVOICE_CHECK_INTERVAL)
check_invoice_thread.signal.connect(self.check_invoice)
check_invoice_thread.start()
a, n = adjective_noun_pair()
inv_memo = "RB-{}-{}".format(a.capitalize(), n.capitalize())
new_invoice = self.create_new_invoice(inv_memo, amt=self.rb_cfg.invoice_default_amount)
data = new_invoice.payment_request
self.show_qr_code(data, SCREEN_INVOICE, memo=inv_memo, status="Open",
inv_amt=self.rb_cfg.invoice_default_amount)
def b3_invoice_custom_amt(self):
log.info("b1 option: custom amount")
check_invoice_thread = ClockStoppableThread(Event(), interval=INVOICE_CHECK_INTERVAL)
check_invoice_thread.signal.connect(self.check_invoice)
check_invoice_thread.start()
a, n = adjective_noun_pair()
inv_memo = "RB-{}-{}".format(a.capitalize(), n.capitalize())
new_invoice = self.create_new_invoice(inv_memo, amt=0)
data = new_invoice.payment_request
self.show_qr_code(data, SCREEN_INVOICE, memo=inv_memo, status="Open", inv_amt="Donation")
def on_button_4_clicked(self):
log.debug("clicked: B4: {}".format(self.winId()))
dialog_b4 = QDialog(flags=(Qt.Dialog | Qt.FramelessWindowHint))
ui = Ui_DialogConfirmOff()
ui.setupUi(dialog_b4)
dialog_b4.move(0, 0)
ui.buttonBox.button(QDialogButtonBox.Yes).setText("Shutdown")
ui.buttonBox.button(QDialogButtonBox.Retry).setText("Restart")
ui.buttonBox.button(QDialogButtonBox.Cancel).setText("Cancel")
ui.buttonBox.button(QDialogButtonBox.Yes).clicked.connect(self.b4_shutdown)
ui.buttonBox.button(QDialogButtonBox.Retry).clicked.connect(self.b4_restart)
dialog_b4.show()
rsp = dialog_b4.exec_()
if rsp == QDialog.Accepted:
log.info("B4: pressed is: Accepted - Shutdown or Restart")
else:
log.info("B4: pressed is: Cancel")
def b4_shutdown(self):
log.info("shutdown")
if IS_WIN32_ENV:
log.info("skipping on win32")
return
process = QProcess(self)
process.start('xterm', ['-fn', 'fixed', '-into', str(int(self.ui.widget.winId())),
'+sb', '-hold', '-e', 'bash -c \"sudo /home/admin/XXshutdown.sh\"'])
def b4_restart(self):
log.info("restart")
if IS_WIN32_ENV:
log.info("skipping on win32")
return
process = QProcess(self)
process.start('xterm', ['-fn', 'fixed', '-into', str(int(self.ui.widget.winId())),
'+sb', '-hold', '-e', 'bash -c \"sudo /home/admin/XXreboot.sh\"'])
def create_new_invoice(self, memo="Pay to RaspiBlitz", amt=0):
if IS_DEV_ENV:
# Fake an invoice for dev
class FakeAddInvoiceResponse(object):
def __init__(self):
self.add_index = 145
self.payment_request = "lnbc47110n1pwmfqcdpp5k55n5erv60mg6u4c8s3qggnw3dsn267e80ypjxxp6gj593" \
"p3c25sdq9vehk7cqzpgprn0ytv6ukxc2vclgag38nmsmlyggmd4zand9qay2l3gc5at" \
"ecxjynydyzhvxsysam9d46y5lgezh2nkufvn23403t3tz3lyhd070dgq625xp0"
self.r_hash = b'\xf9\xe3(\xf5\x84\xdad\x88\xe4%\xa7\x1c\x95\xbe\x8baJ\x1c\xc1\xad*\xed\xc8' \
b'\x158\x13\xdf\xffF\x9c\x95\x84'
new_invoice = FakeAddInvoiceResponse()
else:
stub_invoice = InvoiceStub(network=self.rb_cfg.network, chain=self.rb_cfg.chain)
new_invoice = create_invoice(stub_invoice, memo, amt)
log.info("#{}: {}".format(new_invoice.add_index, new_invoice.payment_request))
invoice_r_hash_hex_str = convert_r_hash_hex_bytes(new_invoice.r_hash)
self.invoice_to_check = invoice_r_hash_hex_str
log.info("noting down for checking: {}".format(invoice_r_hash_hex_str))
return new_invoice
def get_node_uri(self):
if IS_DEV_ENV:
return "535f209faaea75427949e3e6c1fc9edafbf751f08706506bb873fdc93ffc2d4e2c@pqcjuc47eqcv6mk2.onion:9735"
stub_readonly = ReadOnlyStub(network=self.rb_cfg.network, chain=self.rb_cfg.chain)
res = get_node_uri(stub_readonly)
log.info("Node URI: : {}".format(res))
return res
class ClockStoppableThread(QThread):
signal = pyqtSignal('PyQt_PyObject', 'PyQt_PyObject')
def __init__(self, event, interval=0.5, *args, **kwargs):
QThread.__init__(self, *args, **kwargs)
self.stopped = event
self.interval = interval
# atomic (?!) counter
self.ctr = itertools.count()
def run(self):
log.info("starting stoppable clock")
while not self.stopped.wait(self.interval):
self.signal.emit(self.stopped, next(self.ctr))
class GenerateQrCodeThread(QThread):
signal = pyqtSignal('PyQt_PyObject')
def __init__(self):
QThread.__init__(self)
self.data = None
def run(self):
# run method gets called when we start the thread
img = get_qr_img(self.data)
# done, now inform the main thread with the output
self.signal.emit(img)
class BeatThread(QThread):
signal = pyqtSignal('PyQt_PyObject')
def __init__(self, interval=5000, *args, **kwargs):
QThread.__init__(self, *args, **kwargs)
self.interval = interval
self.beat_timer = QTimer()
self.beat_timer.moveToThread(self)
self.beat_timer.timeout.connect(self.tick)
def tick(self):
# log.debug("beat")
self.signal.emit(0)
def run(self):
log.info("starting beat")
self.beat_timer.start(self.interval)
loop = QEventLoop()
loop.exec_()
@lru_cache(maxsize=32)
def get_qr_img(data):
for i in range(6, 1, -1):
time.sleep(1.0)
qr_img = qrcode.make(data, box_size=i)
log.info("Box Size: {}, Image Size: {}".format(i, qr_img.size[0]))
if qr_img.size[0] <= SCREEN_HEIGHT:
break
else:
raise Exception("none found")
return qr_img
def main():
# make sure CTRL+C works
signal.signal(signal.SIGINT, signal.SIG_DFL)
description = """BlitzTUI - the Touch-User-Interface for the RaspiBlitz project
Keep on stacking SATs..! :-D"""
parser = argparse.ArgumentParser(description=description, formatter_class=RawTextHelpFormatter)
parser.add_argument("-V", "--version",
help="print version", action="version",
version=__version__)
#
# parser.add_argument("-g", "--game",
# help="game binary", type=str)
#
# parser.add_argument("-s", "--skip",
# help="skip", action="store_true")
# parse args
args = parser.parse_args()
# initialize app
app = QApplication(sys.argv)
w = AppWindow()
w.show()
# run app
sys.exit(app.exec_())
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,417 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'designer/home.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(480, 300)
MainWindow.setMinimumSize(QtCore.QSize(0, 0))
MainWindow.setAutoFillBackground(False)
MainWindow.setStyleSheet("background-color: black")
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.splitter = QtWidgets.QSplitter(self.centralwidget)
self.splitter.setGeometry(QtCore.QRect(6, 5, 80, 280))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.splitter.sizePolicy().hasHeightForWidth())
self.splitter.setSizePolicy(sizePolicy)
self.splitter.setOrientation(QtCore.Qt.Vertical)
self.splitter.setObjectName("splitter")
self.pushButton_1 = QtWidgets.QPushButton(self.splitter)
palette = QtGui.QPalette()
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush)
self.pushButton_1.setPalette(palette)
font = QtGui.QFont()
font.setFamily("Arial")
font.setBold(True)
font.setWeight(75)
self.pushButton_1.setFont(font)
self.pushButton_1.setStyleSheet("background-color: rgb(0, 0, 70);\n"
"color: rgb(255, 255, 255)")
self.pushButton_1.setObjectName("pushButton_1")
self.pushButton_2 = QtWidgets.QPushButton(self.splitter)
palette = QtGui.QPalette()
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush)
self.pushButton_2.setPalette(palette)
font = QtGui.QFont()
font.setFamily("Arial")
font.setBold(True)
font.setWeight(75)
self.pushButton_2.setFont(font)
self.pushButton_2.setStyleSheet("background-color: rgb(0, 0, 70);\n"
"color: rgb(255, 255, 255)")
self.pushButton_2.setObjectName("pushButton_2")
self.pushButton_3 = QtWidgets.QPushButton(self.splitter)
palette = QtGui.QPalette()
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush)
self.pushButton_3.setPalette(palette)
font = QtGui.QFont()
font.setFamily("Arial")
font.setBold(True)
font.setUnderline(False)
font.setWeight(75)
self.pushButton_3.setFont(font)
self.pushButton_3.setStyleSheet("background-color: rgb(0, 0, 70);\n"
"color: rgb(255, 255, 255)")
self.pushButton_3.setObjectName("pushButton_3")
self.pushButton_4 = QtWidgets.QPushButton(self.splitter)
palette = QtGui.QPalette()
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 70))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush)
self.pushButton_4.setPalette(palette)
font = QtGui.QFont()
font.setFamily("Arial")
font.setBold(True)
font.setWeight(75)
self.pushButton_4.setFont(font)
self.pushButton_4.setStyleSheet("background-color: rgb(0, 0, 70);\n"
"color: rgb(255, 255, 255)")
self.pushButton_4.setObjectName("pushButton_4")
self.widget = QtWidgets.QWidget(self.centralwidget)
self.widget.setGeometry(QtCore.QRect(92, 5, 380, 270))
self.widget.setStyleSheet("background-color: darkblue")
self.widget.setObjectName("widget")
self.error_label = QtWidgets.QLabel(self.centralwidget)
self.error_label.setGeometry(QtCore.QRect(112, 252, 301, 44))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.error_label.sizePolicy().hasHeightForWidth())
self.error_label.setSizePolicy(sizePolicy)
palette = QtGui.QPalette()
brush = QtGui.QBrush(QtGui.QColor(255, 0, 127))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 0, 127))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 0, 127))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 0, 127))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 0, 127))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 0, 127))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 0, 127))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 0, 127))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush)
brush = QtGui.QBrush(QtGui.QColor(255, 0, 127))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush)
brush = QtGui.QBrush(QtGui.QColor(0, 0, 0))
brush.setStyle(QtCore.Qt.SolidPattern)
palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush)
self.error_label.setPalette(palette)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(12)
font.setBold(False)
font.setItalic(False)
font.setWeight(50)
self.error_label.setFont(font)
self.error_label.setStyleSheet("background-color: rgb(0, 0, 0);\n"
"font: 12pt \"Arial\";\n"
"color: rgb(255, 0, 127);")
self.error_label.setScaledContents(False)
self.error_label.setAlignment(QtCore.Qt.AlignCenter)
self.error_label.setWordWrap(False)
self.error_label.setObjectName("error_label")
self.buttonBox_close = QtWidgets.QDialogButtonBox(self.centralwidget)
self.buttonBox_close.setGeometry(QtCore.QRect(420, 260, 56, 32))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.buttonBox_close.sizePolicy().hasHeightForWidth())
self.buttonBox_close.setSizePolicy(sizePolicy)
self.buttonBox_close.setMinimumSize(QtCore.QSize(0, 0))
self.buttonBox_close.setMaximumSize(QtCore.QSize(16777215, 16777215))
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(14)
font.setBold(False)
font.setItalic(False)
font.setWeight(50)
self.buttonBox_close.setFont(font)
self.buttonBox_close.setStyleSheet("background-color: lightgrey;\n"
"font: 14pt \"Arial\";")
self.buttonBox_close.setOrientation(QtCore.Qt.Vertical)
self.buttonBox_close.setStandardButtons(QtWidgets.QDialogButtonBox.Close)
self.buttonBox_close.setObjectName("buttonBox_close")
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "RaspiBlitz"))
self.pushButton_1.setText(_translate("MainWindow", "Info"))
self.pushButton_2.setText(_translate("MainWindow", "Node"))
self.pushButton_3.setText(_translate("MainWindow", "Invoice"))
self.pushButton_4.setText(_translate("MainWindow", "Off"))
self.error_label.setText(_translate("MainWindow", "Error Text\n"
"Foobar"))
from . import resources_rc
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())

View File

@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'designer/invoice.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_DialogSelectInvoice(object):
def setupUi(self, DialogSelectInvoice):
DialogSelectInvoice.setObjectName("DialogSelectInvoice")
DialogSelectInvoice.resize(480, 320)
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(DialogSelectInvoice.sizePolicy().hasHeightForWidth())
DialogSelectInvoice.setSizePolicy(sizePolicy)
DialogSelectInvoice.setStyleSheet("")
self.buttonBox = QtWidgets.QDialogButtonBox(DialogSelectInvoice)
self.buttonBox.setGeometry(QtCore.QRect(102, 110, 320, 340))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.buttonBox.sizePolicy().hasHeightForWidth())
self.buttonBox.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(28)
font.setBold(False)
font.setItalic(False)
font.setWeight(50)
self.buttonBox.setFont(font)
self.buttonBox.setStyleSheet("background-color: lightgrey;\n"
"font: 28pt \"Arial\";")
self.buttonBox.setOrientation(QtCore.Qt.Vertical)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Yes)
self.buttonBox.setObjectName("buttonBox")
self.label = QtWidgets.QLabel(DialogSelectInvoice)
self.label.setGeometry(QtCore.QRect(102, 30, 320, 64))
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(20)
font.setBold(False)
font.setItalic(False)
font.setWeight(50)
self.label.setFont(font)
self.label.setStyleSheet("")
self.label.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop)
self.label.setObjectName("label")
self.label_2 = QtWidgets.QLabel(DialogSelectInvoice)
self.label_2.setGeometry(QtCore.QRect(0, 0, 47, 318))
self.label_2.setText("")
self.label_2.setPixmap(QtGui.QPixmap(":/RaspiBlitz/images/RaspiBlitz_Logo_Main_270.png"))
self.label_2.setScaledContents(True)
self.label_2.setObjectName("label_2")
self.retranslateUi(DialogSelectInvoice)
self.buttonBox.accepted.connect(DialogSelectInvoice.accept)
self.buttonBox.rejected.connect(DialogSelectInvoice.reject)
QtCore.QMetaObject.connectSlotsByName(DialogSelectInvoice)
def retranslateUi(self, DialogSelectInvoice):
_translate = QtCore.QCoreApplication.translate
DialogSelectInvoice.setWindowTitle(_translate("DialogSelectInvoice", "Dialog"))
self.label.setText(_translate("DialogSelectInvoice", "Select Invoice"))
from . import resources_rc
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
DialogSelectInvoice = QtWidgets.QDialog()
ui = Ui_DialogSelectInvoice()
ui.setupUi(DialogSelectInvoice)
DialogSelectInvoice.show()
sys.exit(app.exec_())

View File

@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'designer/off.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_DialogConfirmOff(object):
def setupUi(self, DialogConfirmOff):
DialogConfirmOff.setObjectName("DialogConfirmOff")
DialogConfirmOff.resize(480, 320)
DialogConfirmOff.setStyleSheet("background-color: rgb(255, 128, 128)")
self.label_2 = QtWidgets.QLabel(DialogConfirmOff)
self.label_2.setGeometry(QtCore.QRect(9, 9, 16, 16))
self.label_2.setMaximumSize(QtCore.QSize(110, 320))
self.label_2.setText("")
self.label_2.setPixmap(QtGui.QPixmap(":/RaspiBlitz/images/RaspiBlitz_Logo_Main_rotate.png"))
self.label_2.setScaledContents(True)
self.label_2.setIndent(-4)
self.label_2.setObjectName("label_2")
self.label_3 = QtWidgets.QLabel(DialogConfirmOff)
self.label_3.setGeometry(QtCore.QRect(0, 0, 47, 318))
self.label_3.setText("")
self.label_3.setPixmap(QtGui.QPixmap(":/RaspiBlitz/images/RaspiBlitz_Logo_Main_270.png"))
self.label_3.setScaledContents(True)
self.label_3.setObjectName("label_3")
self.label = QtWidgets.QLabel(DialogConfirmOff)
self.label.setGeometry(QtCore.QRect(102, 30, 320, 64))
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(20)
font.setBold(False)
font.setItalic(False)
font.setWeight(50)
self.label.setFont(font)
self.label.setStyleSheet("")
self.label.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop)
self.label.setObjectName("label")
self.buttonBox = QtWidgets.QDialogButtonBox(DialogConfirmOff)
self.buttonBox.setGeometry(QtCore.QRect(102, 110, 320, 340))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.buttonBox.sizePolicy().hasHeightForWidth())
self.buttonBox.setSizePolicy(sizePolicy)
self.buttonBox.setMinimumSize(QtCore.QSize(0, 0))
self.buttonBox.setMaximumSize(QtCore.QSize(16777215, 16777215))
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(28)
font.setBold(False)
font.setItalic(False)
font.setWeight(50)
self.buttonBox.setFont(font)
self.buttonBox.setStyleSheet("background-color: lightgrey;\n"
"font: 28pt \"Arial\";")
self.buttonBox.setOrientation(QtCore.Qt.Vertical)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Retry|QtWidgets.QDialogButtonBox.Yes)
self.buttonBox.setObjectName("buttonBox")
self.retranslateUi(DialogConfirmOff)
self.buttonBox.rejected.connect(DialogConfirmOff.reject)
self.buttonBox.accepted.connect(DialogConfirmOff.accept)
QtCore.QMetaObject.connectSlotsByName(DialogConfirmOff)
def retranslateUi(self, DialogConfirmOff):
_translate = QtCore.QCoreApplication.translate
DialogConfirmOff.setWindowTitle(_translate("DialogConfirmOff", "Dialog"))
self.label.setText(_translate("DialogConfirmOff", "Shutdown RaspiBlitz?"))
from . import resources_rc
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
DialogConfirmOff = QtWidgets.QDialog()
ui = Ui_DialogConfirmOff()
ui.setupUi(DialogConfirmOff)
DialogConfirmOff.show()
sys.exit(app.exec_())

View File

@ -0,0 +1,207 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'designer/qcode.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_DialogShowQrCode(object):
def setupUi(self, DialogShowQrCode):
DialogShowQrCode.setObjectName("DialogShowQrCode")
DialogShowQrCode.resize(480, 320)
self.buttonBox = QtWidgets.QDialogButtonBox(DialogShowQrCode)
self.buttonBox.setGeometry(QtCore.QRect(326, 268, 150, 50))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.buttonBox.sizePolicy().hasHeightForWidth())
self.buttonBox.setSizePolicy(sizePolicy)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(24)
self.buttonBox.setFont(font)
self.buttonBox.setStyleSheet("background-color: lightgrey;\n"
"font: 24pt \"Arial\";")
self.buttonBox.setOrientation(QtCore.Qt.Vertical)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
self.top_right_logo = QtWidgets.QLabel(DialogShowQrCode)
self.top_right_logo.setGeometry(QtCore.QRect(430, 2, 40, 60))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.top_right_logo.sizePolicy().hasHeightForWidth())
self.top_right_logo.setSizePolicy(sizePolicy)
self.top_right_logo.setText("")
self.top_right_logo.setPixmap(QtGui.QPixmap(":/RaspiBlitz/images/RaspiBlitz_Logo_Berry.png"))
self.top_right_logo.setScaledContents(True)
self.top_right_logo.setAlignment(QtCore.Qt.AlignCenter)
self.top_right_logo.setObjectName("top_right_logo")
self.frame = QtWidgets.QFrame(DialogShowQrCode)
self.frame.setGeometry(QtCore.QRect(0, 0, 320, 320))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.frame.sizePolicy().hasHeightForWidth())
self.frame.setSizePolicy(sizePolicy)
self.frame.setStyleSheet("background-color: rgb(255, 255, 255);")
self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
self.frame.setObjectName("frame")
self.qcode = QtWidgets.QLabel(self.frame)
self.qcode.setGeometry(QtCore.QRect(1, 1, 318, 318))
self.qcode.setStyleSheet("background-color: white")
self.qcode.setText("")
self.qcode.setPixmap(QtGui.QPixmap(":/RaspiBlitz/images/RaspiBlitz_Logo_Stacked.png"))
self.qcode.setScaledContents(True)
self.qcode.setAlignment(QtCore.Qt.AlignCenter)
self.qcode.setObjectName("qcode")
self.label = QtWidgets.QLabel(DialogShowQrCode)
self.label.setGeometry(QtCore.QRect(330, 4, 88, 60))
self.label.setText("")
self.label.setPixmap(QtGui.QPixmap(":/RaspiBlitz/images/RaspiBlitz_Logo_Stacked.png"))
self.label.setScaledContents(True)
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setObjectName("label")
self.horizontalLayoutWidget = QtWidgets.QWidget(DialogShowQrCode)
self.horizontalLayoutWidget.setGeometry(QtCore.QRect(320, 70, 161, 191))
self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.horizontalLayoutWidget)
self.verticalLayout.setContentsMargins(6, 0, 6, 0)
self.verticalLayout.setObjectName("verticalLayout")
self.line = QtWidgets.QFrame(self.horizontalLayoutWidget)
self.line.setFrameShape(QtWidgets.QFrame.HLine)
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
self.line.setObjectName("line")
self.verticalLayout.addWidget(self.line)
self.memo_key = QtWidgets.QLabel(self.horizontalLayoutWidget)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(11)
font.setBold(True)
font.setItalic(False)
font.setUnderline(False)
font.setWeight(75)
self.memo_key.setFont(font)
self.memo_key.setScaledContents(False)
self.memo_key.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.memo_key.setWordWrap(True)
self.memo_key.setObjectName("memo_key")
self.verticalLayout.addWidget(self.memo_key)
self.memo_value = QtWidgets.QLabel(self.horizontalLayoutWidget)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(11)
self.memo_value.setFont(font)
self.memo_value.setScaledContents(False)
self.memo_value.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTop|QtCore.Qt.AlignTrailing)
self.memo_value.setWordWrap(True)
self.memo_value.setObjectName("memo_value")
self.verticalLayout.addWidget(self.memo_value)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.status_key = QtWidgets.QLabel(self.horizontalLayoutWidget)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(11)
font.setBold(True)
font.setUnderline(False)
font.setWeight(75)
self.status_key.setFont(font)
self.status_key.setScaledContents(False)
self.status_key.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.status_key.setWordWrap(True)
self.status_key.setObjectName("status_key")
self.horizontalLayout.addWidget(self.status_key)
self.status_value = QtWidgets.QLabel(self.horizontalLayoutWidget)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(11)
self.status_value.setFont(font)
self.status_value.setScaledContents(False)
self.status_value.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTop|QtCore.Qt.AlignTrailing)
self.status_value.setWordWrap(True)
self.status_value.setObjectName("status_value")
self.horizontalLayout.addWidget(self.status_value)
self.verticalLayout.addLayout(self.horizontalLayout)
self.inv_amt_key = QtWidgets.QLabel(self.horizontalLayoutWidget)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(11)
font.setBold(True)
font.setWeight(75)
self.inv_amt_key.setFont(font)
self.inv_amt_key.setObjectName("inv_amt_key")
self.verticalLayout.addWidget(self.inv_amt_key)
self.inv_amt_value = QtWidgets.QLabel(self.horizontalLayoutWidget)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(11)
font.setBold(False)
font.setWeight(50)
self.inv_amt_value.setFont(font)
self.inv_amt_value.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.inv_amt_value.setObjectName("inv_amt_value")
self.verticalLayout.addWidget(self.inv_amt_value)
self.amt_paid_key = QtWidgets.QLabel(self.horizontalLayoutWidget)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(11)
font.setBold(True)
font.setWeight(75)
self.amt_paid_key.setFont(font)
self.amt_paid_key.setObjectName("amt_paid_key")
self.verticalLayout.addWidget(self.amt_paid_key)
self.amt_paid_value = QtWidgets.QLabel(self.horizontalLayoutWidget)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(11)
self.amt_paid_value.setFont(font)
self.amt_paid_value.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.amt_paid_value.setObjectName("amt_paid_value")
self.verticalLayout.addWidget(self.amt_paid_value)
self.spinner = QtWidgets.QWidget(DialogShowQrCode)
self.spinner.setGeometry(QtCore.QRect(440, 0, 40, 40))
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.spinner.sizePolicy().hasHeightForWidth())
self.spinner.setSizePolicy(sizePolicy)
self.spinner.setObjectName("spinner")
self.spinner.raise_()
self.buttonBox.raise_()
self.top_right_logo.raise_()
self.frame.raise_()
self.label.raise_()
self.horizontalLayoutWidget.raise_()
self.retranslateUi(DialogShowQrCode)
self.buttonBox.accepted.connect(DialogShowQrCode.accept)
QtCore.QMetaObject.connectSlotsByName(DialogShowQrCode)
def retranslateUi(self, DialogShowQrCode):
_translate = QtCore.QCoreApplication.translate
DialogShowQrCode.setWindowTitle(_translate("DialogShowQrCode", "Dialog"))
self.memo_key.setText(_translate("DialogShowQrCode", "Memo"))
self.memo_value.setText(_translate("DialogShowQrCode", "RB-Vivid-Badger"))
self.status_key.setText(_translate("DialogShowQrCode", "Status"))
self.status_value.setText(_translate("DialogShowQrCode", "Open/Paid"))
self.inv_amt_key.setText(_translate("DialogShowQrCode", "Invoice Amount"))
self.inv_amt_value.setText(_translate("DialogShowQrCode", "123456798"))
self.amt_paid_key.setText(_translate("DialogShowQrCode", "Amount Paid"))
self.amt_paid_value.setText(_translate("DialogShowQrCode", "N/A"))
from . import resources_rc
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
DialogShowQrCode = QtWidgets.QDialog()
ui = Ui_DialogShowQrCode()
ui.setupUi(DialogShowQrCode)
DialogShowQrCode.show()
sys.exit(app.exec_())

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
""" Store the version here so:
# 1) we don't load dependencies by storing it in __init__.py
# 2) we can import it in setup.py for the same reason
# 3) we can import it into your module module
"""
__version_info__ = ('0', '41', '0')
__version__ = '.'.join(__version_info__)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
# lnd configuration
[Application Options]
rpclisten=0.0.0.0:10009

View File

@ -0,0 +1,19 @@
# RASPIBLITZ CONFIG FILE
autoNatDiscovery=off
autoPilot=off
autoUnlock=on
chain=test
dynDomain=''
dynUpdateUrl=''
hostname=raspiblitz
invoiceAllowDonations=off
invoiceDefaultAmount=402
lcdrotate=1
lndAddress=''
lndPort='9735'
network=bitcoin
publicIP='1.2.3.4'
raspiBlitzVersion='1.3'
rtlWebinterface=off
runBehindTor=off
touchscreen=1

View File

@ -0,0 +1,7 @@
state=stresstest
message='Testing Hardware 60s'
network=bitcoin
chain=main
setupStep=100
baseimage=raspbian
undervoltageReports=0

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DialogSelectInvoice</class>
<widget class="QDialog" name="DialogSelectInvoice">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>320</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="geometry">
<rect>
<x>102</x>
<y>110</y>
<width>320</width>
<height>340</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Arial</family>
<pointsize>28</pointsize>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">background-color: lightgrey;
font: 28pt &quot;Arial&quot;;</string>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Yes</set>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>102</x>
<y>30</y>
<width>320</width>
<height>64</height>
</rect>
</property>
<property name="font">
<font>
<family>Arial</family>
<pointsize>20</pointsize>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string>Select Invoice</string>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>47</width>
<height>318</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../resources.qrc">:/RaspiBlitz/images/RaspiBlitz_Logo_Main_270.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
</widget>
<resources>
<include location="../resources.qrc"/>
<include location="../resources.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DialogSelectInvoice</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>DialogSelectInvoice</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,180 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DialogConfirmOff</class>
<widget class="QDialog" name="DialogConfirmOff">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>320</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgb(255, 128, 128)</string>
</property>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>9</x>
<y>9</y>
<width>16</width>
<height>16</height>
</rect>
</property>
<property name="maximumSize">
<size>
<width>110</width>
<height>320</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap>:/RaspiBlitz/images/RaspiBlitz_Logo_Main_rotate.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="indent">
<number>-4</number>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>47</width>
<height>318</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../resources.qrc">:/RaspiBlitz/images/RaspiBlitz_Logo_Main_270.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>102</x>
<y>30</y>
<width>320</width>
<height>64</height>
</rect>
</property>
<property name="font">
<font>
<family>Arial</family>
<pointsize>20</pointsize>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="text">
<string>Shutdown RaspiBlitz?</string>
</property>
<property name="alignment">
<set>Qt::AlignHCenter|Qt::AlignTop</set>
</property>
</widget>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="geometry">
<rect>
<x>102</x>
<y>110</y>
<width>320</width>
<height>340</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<family>Arial</family>
<pointsize>28</pointsize>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">background-color: lightgrey;
font: 28pt &quot;Arial&quot;;</string>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Retry|QDialogButtonBox::Yes</set>
</property>
</widget>
</widget>
<resources>
<include location="../resources.qrc"/>
<include location="../resources.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>DialogConfirmOff</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DialogConfirmOff</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,384 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DialogShowQrCode</class>
<widget class="QDialog" name="DialogShowQrCode">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>480</width>
<height>320</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="geometry">
<rect>
<x>326</x>
<y>268</y>
<width>150</width>
<height>50</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Arial</family>
<pointsize>24</pointsize>
</font>
</property>
<property name="styleSheet">
<string notr="true">background-color: lightgrey;
font: 24pt &quot;Arial&quot;;</string>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QLabel" name="top_right_logo">
<property name="geometry">
<rect>
<x>430</x>
<y>2</y>
<width>40</width>
<height>60</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../resources.qrc">:/RaspiBlitz/images/RaspiBlitz_Logo_Berry.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QFrame" name="frame">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>320</width>
<height>320</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgb(255, 255, 255);</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<widget class="QLabel" name="qcode">
<property name="geometry">
<rect>
<x>1</x>
<y>1</y>
<width>318</width>
<height>318</height>
</rect>
</property>
<property name="styleSheet">
<string notr="true">background-color: white</string>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../resources.qrc">:/RaspiBlitz/images/RaspiBlitz_Logo_Stacked.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="margin">
<number>16</number>
</property>
</widget>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>330</x>
<y>4</y>
<width>88</width>
<height>60</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="../resources.qrc">:/RaspiBlitz/images/RaspiBlitz_Logo_Stacked.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QWidget" name="horizontalLayoutWidget">
<property name="geometry">
<rect>
<x>320</x>
<y>70</y>
<width>161</width>
<height>191</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="memo_key">
<property name="font">
<font>
<family>Arial</family>
<pointsize>11</pointsize>
<weight>75</weight>
<italic>false</italic>
<bold>true</bold>
<underline>false</underline>
</font>
</property>
<property name="text">
<string>Memo</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="memo_value">
<property name="font">
<font>
<family>Arial</family>
<pointsize>11</pointsize>
</font>
</property>
<property name="text">
<string>RB-Vivid-Badger</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="status_key">
<property name="font">
<font>
<family>Arial</family>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
<underline>false</underline>
</font>
</property>
<property name="text">
<string>Status</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="status_value">
<property name="font">
<font>
<family>Arial</family>
<pointsize>11</pointsize>
</font>
</property>
<property name="text">
<string>Open/Paid</string>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="inv_amt_key">
<property name="font">
<font>
<family>Arial</family>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Invoice Amount</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="inv_amt_value">
<property name="font">
<font>
<family>Arial</family>
<pointsize>11</pointsize>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>123456798</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="amt_paid_key">
<property name="font">
<font>
<family>Arial</family>
<pointsize>11</pointsize>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Amount Paid</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="amt_paid_value">
<property name="font">
<font>
<family>Arial</family>
<pointsize>11</pointsize>
</font>
</property>
<property name="text">
<string>N/A</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="spinner" native="true">
<property name="geometry">
<rect>
<x>440</x>
<y>0</y>
<width>40</width>
<height>40</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
<zorder>spinner</zorder>
<zorder>buttonBox</zorder>
<zorder>top_right_logo</zorder>
<zorder>frame</zorder>
<zorder>label</zorder>
<zorder>horizontalLayoutWidget</zorder>
</widget>
<resources>
<include location="../resources.qrc"/>
<include location="../resources.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DialogShowQrCode</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,166 @@
# BlitzTUI Documentation (mainly for developers)
BlitzTUI is a part of the RaspiBlitz project and implements a Touch User Interface in PyQt5.
Make sure that PyQt5 is installed on the system
```
apt-get install python3-pyqt5
```
## Required tools
### for UI development
* QT Designer (GUI application for Linux, Mac and Windows)
### for compiling the .ui and .qrc files to python3
* pyuic5
* pyrcc5
`sudo apt-get install pyqt5-dev-tools`
### for building and uploading PyPI packages
* setuptools
* wheel
* twine
`python3 -m pip install --upgrade setuptools wheel twine`
## Mini-Tutorial
Have a look at the [Mini-Tutorial](tutorial.md)
## Release workflow
* `make build-ui` - in case there were any changes to the *.ui or *.qrc files
* make sure you have all changes added and commited (consider re-basing)
* update the version in `blitztui/version.py`
* update the `CHANGELOG.md` file (reflect the new version!)
* `git add CHANGELOG.md blitztui/version.py`
* `git commit` and set a proper commit message
* `make build`
* `make upload`
## Uploading to PyPI
Please use `twine` for uploading files to PyPI. You will need credentials for the BlitzTUI account.
```
$ cat ~/.pypirc
[distutils]
index-servers=
pypi
pypitest
[pypi]
username = RaspiBlitz
password = <REDACTED>
[pypitest]
repository = https://test.pypi.org/legacy/
username = RaspiBlitz-Test
password = <REDACTED>
```
## PRELOAD-What?!
**Update: This seems to be fixed since grpcio==1.24.3!**
What's the reason for this long `LD_PRELOAD` line?!
Apparently there is an incompatibility with the current version (as of writing this: **grpcio==1.24.1**) of
**gRPC** for Python on ARM (Raspberry Pi) that was released by Google. Running without `LD_PRELOAD` gives
an error regarding `undefined symbol: __atomic_exchange_8`:
```
(python3-env-lnd) admin@raspiblitz:~/raspiblitz/home.admin/BlitzTUI $ python3
Python 3.7.3 (default, Apr 3 2019, 05:39:12)
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import grpc
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/admin/python3-env-lnd/lib/python3.7/site-packages/grpc/__init__.py", line 23, in <module>
from grpc._cython import cygrpc as _cygrpc
ImportError: /home/admin/python3-env-lnd/lib/python3.7/site-packages/grpc/_cython/cygrpc.cpython-37m-arm-linux-gnueabihf.so: undefined symbol: __atomic_exchange_8
```
It is expected that this is resolved soon-ish.
## Directory tree
```
admin@raspiblitz:~/raspiblitz/home.admin/BlitzTUI $ tree
.
├── blitztui
│   ├── client.py
│   ├── config.py
│   ├── file_logger.py
│   ├── file_watcher.py
│   ├── __init__.py
│   ├── main.py
│   ├── memo.py
│   ├── ui
│   │   ├── home.py
│   │   ├── __init__.py
│   │   ├── invoice.py
│   │   ├── off.py
│   │   ├── qcode.py
│   │   └── resources_rc.py
│   └── version.py
├── CHANGELOG.md
├── data
│   ├── lnd.conf
│   ├── raspiblitz.conf
│   ├── raspiblitz.info
│   ├── Wordlist-Adjectives-Common-Audited-Len-3-6.txt
│   └── Wordlist-Nouns-Common-Audited-Len-3-6.txt
├── designer
│   ├── home.ui
│   ├── invoice.ui
│   ├── off.ui
│   └── qcode.ui
├── dist
├── docs
│   ├── images
│   │   └── QtDesigner.png
│   ├── README.md
│   └── tutorial.md
├── images
│   ├── blank_318x318.png
│   ├── Paid_Stamp.png
│   ├── RaspiBlitz_Logo_Berry.png
│   ├── RaspiBlitz_Logo_Condensed_270.png
│   ├── RaspiBlitz_Logo_Condensed_90.png
│   ├── RaspiBlitz_Logo_Condensed_Negative.png
│   ├── RaspiBlitz_Logo_Condensed.png
│   ├── RaspiBlitz_Logo_Icon_Negative.png
│   ├── RaspiBlitz_Logo_Icon.png
│   ├── RaspiBlitz_Logo_Main_270.png
│   ├── RaspiBlitz_Logo_Main_90.png
│   ├── RaspiBlitz_Logo_Main_Negative.png
│   ├── RaspiBlitz_Logo_Main.png
│   ├── RaspiBlitz_Logo_Stacked_270.png
│   ├── RaspiBlitz_Logo_Stacked_90.png
│   ├── RaspiBlitz_Logo_Stacked_Negative_270.png
│   ├── RaspiBlitz_Logo_Stacked_Negative_90.png
│   ├── RaspiBlitz_Logo_Stacked_Negative.png
│   └── RaspiBlitz_Logo_Stacked.png
├── LICENSE
├── make.cmd
├── Makefile
├── MANIFEST.in
├── README.md
├── requirements.txt
├── resources.qrc
├── setup.cfg
└── setup.py
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View File

@ -0,0 +1,47 @@
# BlitzTUI Mini-Tutorial
This *Mini-Tutorial* shows the basic workflow for doing changes to the Blitz-Touch-User-Interface.
## What you need
* A physical RaspiBlitz and SSH access to it (to verify your changes on the real screen)
* A Computer (Mac and Windows should work)
* The "Qt Designer" software (https://build-system.fman.io/qt-designer-download)
* A copy of the current RaspiBlitz codebase (`git clone https://github.com/rootzoll/raspiblitz.git`)
## Scenario
Let's assume you want to reduce the width of the button row on the left side.
* Open Qt Designer
* Load the .ui file: `home.admin/BlitzTUI/designer/home.ui`
Your screen should look similar to this:
![Qt Designer](images/QtDesigner.png)
* (1) for this scenario go to the **Object Inspector** and select **splitter**
* (2) change the **Width** (e.g. from 80 to 60)
* (3) this should be reflected in the preview Window
* (4) save your changes
The next step is to transfer (use scp or WinSCP) the updated `home.ui` to the RaspiBlitz.
Login to your RaspiBlitz as **admin** (Password A) and change the directory to `~/raspiblitz/home.admin/BlitzTUI`.
Your updated `home.ui` file should be in `designer/` (confirm timestamp with `ls -l designer/home.ui`).
Run `make build-ui`
To quickly check the result run
```
sudo -u pi DISPLAY=:0.0 LD_PRELOAD=/usr/lib/arm-linux-gnueabihf/libatomic.so.1.2.0 /home/admin/python3-env-lnd/bin/python3 /home/admin/raspiblitz/home.admin/BlitzTUI/blitztui/main.py
```
You can also install the current directory as a python package using `pip install -e .` and the run
```
sudo -u pi DISPLAY=:0.0 LD_PRELOAD=/usr/lib/arm-linux-gnueabihf/libatomic.so.1.2.0 /home/admin/python3-env-lnd/bin/blitz-tui
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 B

View File

@ -0,0 +1,10 @@
REM run this from BlitzTUI directory
REM convert ui files to python code
pyuic5 -x --import-from "." -o blitztui/ui/qcode.py designer/qcode.ui
pyuic5 -x --import-from "." -o blitztui/ui/home.py designer/home.ui
pyuic5 -x --import-from "." -o blitztui/ui/off.py designer/off.ui
pyuic5 -x --import-from "." -o blitztui/ui/invoice.py designer/invoice.ui
REM resources
pyrcc5 -o blitztui/ui/resources_rc.py resources.qrc

View File

@ -0,0 +1,6 @@
grpcio>=1.24.3
googleapis-common-protos>=1.6.0
inotify>=0.2
psutil>=5.6
pyqtspinner>=0.1
qrcode>=6.1

View File

@ -0,0 +1,23 @@
<RCC>
<qresource prefix="RaspiBlitz">
<file>images/blank_318x318.png</file>
<file>images/Paid_Stamp.png</file>
<file>images/RaspiBlitz_Logo_Berry.png</file>
<file>images/RaspiBlitz_Logo_Condensed.png</file>
<file>images/RaspiBlitz_Logo_Condensed_90.png</file>
<file>images/RaspiBlitz_Logo_Condensed_270.png</file>
<file>images/RaspiBlitz_Logo_Condensed_Negative.png</file>
<file>images/RaspiBlitz_Logo_Icon.png</file>
<file>images/RaspiBlitz_Logo_Icon_Negative.png</file>
<file>images/RaspiBlitz_Logo_Main.png</file>
<file>images/RaspiBlitz_Logo_Main_90.png</file>
<file>images/RaspiBlitz_Logo_Main_270.png</file>
<file>images/RaspiBlitz_Logo_Main_Negative.png</file>
<file>images/RaspiBlitz_Logo_Stacked.png</file>
<file>images/RaspiBlitz_Logo_Stacked_90.png</file>
<file>images/RaspiBlitz_Logo_Stacked_270.png</file>
<file>images/RaspiBlitz_Logo_Stacked_Negative.png</file>
<file>images/RaspiBlitz_Logo_Stacked_Negative_90.png</file>
<file>images/RaspiBlitz_Logo_Stacked_Negative_270.png</file>
</qresource>
</RCC>

View File

@ -0,0 +1,6 @@
# content of setup.cfg
[tool:pytest]
addopts = -ra -q
[bdist_wheel]
universal = 1

View File

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
import setuptools
with open("README.md", "r") as fh:
long_description = fh.read()
with open("blitztui/version.py") as f:
__version__ = ""
exec(f.read()) # set __version__
setuptools.setup(
name="BlitzTUI",
version=__version__,
author="RaspiBlitz Developers",
author_email="raspiblitz@rhab.de",
description="Touch User Interface for RaspiBlitz",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/rootzoll/raspiblitz",
packages=setuptools.find_packages(exclude=("tests", "docs")),
classifiers=[
# How mature is this project? Common values are
# 3 - Alpha
# 4 - Beta
# 5 - Production/Stable
"Development Status :: 4 - Beta",
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX :: Linux",
],
python_requires='>=3.4',
install_requires=[
"grpcio", "googleapis-common-protos", "inotify", "psutil", "pyqtspinner", "qrcode",
],
entry_points={
'console_scripts': ['blitz-tui=blitztui.main:main'],
},
)

View File

@ -1,11 +0,0 @@
#!/bin/bash
echo ""
echo "Thanks"
echo "You pressed B2"
echo "-----------------------------------------------"
echo "Sleep for 10"
sleep 10
echo "Goodbye"
echo "-----------------------------------------------"
exit 0

View File

@ -70,4 +70,4 @@ sudo -u admin chmod +x /home/admin/config.scripts/*.py
echo "******************************************" echo "******************************************"
echo "OK - shell scripts and assests are synced" echo "OK - shell scripts and assests are synced"
echo "Reboot recommended" echo "Reboot recommended"
echo "" echo ""

View File

@ -15,10 +15,6 @@ if [ $# -eq 0 ] || [ "$1" = "-h" ] || [ "$1" = "-help" ]; then
exit 1 exit 1
fi fi
# update install sources
echo "make sure dependencies are installed ..."
sudo apt-get install -y unclutter xterm
echo ""
################### ###################
# SWITCH ON # SWITCH ON
@ -28,13 +24,37 @@ if [ "$1" = "1" ] || [ "$1" = "on" ]; then
echo "Turn ON: Touchscreen" echo "Turn ON: Touchscreen"
# update install sources
echo "make sure dependencies are installed ..."
sudo apt-get update
sudo apt-get install -y unclutter xterm
# TODO(frennkie) should this be removed when running "off"?
sudo apt-get install -y python3-pyqt5
echo ""
echo "installing BlitzTUI (including dependencies)"
/home/admin/python3-env-lnd/bin/pip install BlitzTUI
echo ""
# patch lndlibs for Python3
if ! grep -Fxq "from __future__ import absolute_import" /home/admin/config.scripts/lndlibs/rpc_pb2_grpc.py; then
sed -i -E '1 a from __future__ import absolute_import' /home/admin/config.scripts/lndlibs/rpc_pb2_grpc.py
fi
if ! grep -Eq "^from . import.*" /home/admin/config.scripts/lndlibs/rpc_pb2_grpc.py; then
sed -i -E 's/^(import.*_pb2)/from . \1/' /home/admin/config.scripts/lndlibs/rpc_pb2_grpc.py
fi
# switch to desktop login # switch to desktop login
sudo raspi-config nonint do_boot_behaviour B4 sudo raspi-config nonint do_boot_behaviour B4
# set user pi user for autostart # set user pi user for autostart
sudo sed -i "s/^autologin-user=.*/autologin-user=pi/g" /etc/lightdm/lightdm.conf sudo sed -i 's/^autologin-user=.*/autologin-user=pi/g' /etc/lightdm/lightdm.conf
sudo sed -i s'/--autologin root/--autologin pi/' /etc/systemd/system/getty@tty1.service.d/autologin.conf sudo sed -i 's/--autologin root/--autologin pi/' /etc/systemd/system/getty@tty1.service.d/autologin.conf
sudo sed -i s'/--autologin admin/--autologin pi/' /etc/systemd/system/getty@tty1.service.d/autologin.conf sudo sed -i 's/--autologin admin/--autologin pi/' /etc/systemd/system/getty@tty1.service.d/autologin.conf
# remove welcome wizard
sudo rm -rf /etc/xdg/autostart/piwiz.desktop
# write new LXDE autostart config # write new LXDE autostart config
sudo mv /etc/xdg/lxsession/LXDE-pi/autostart /etc/xdg/lxsession/LXDE-pi/autostart.bak sudo mv /etc/xdg/lxsession/LXDE-pi/autostart /etc/xdg/lxsession/LXDE-pi/autostart.bak
@ -49,8 +69,9 @@ EOF
# editing autostart.sh # editing autostart.sh
cat << EOF | sudo tee /home/pi/autostart.sh >/dev/null cat << EOF | sudo tee /home/pi/autostart.sh >/dev/null
#!/bin/sh #!/bin/sh
sleep 1
/usr/bin/python3 /home/admin/00infoLCDTK.py unset QT_QPA_PLATFORMTHEME
/home/admin/python3-env-lnd/bin/blitz-tui
EOF EOF
sudo chmod a+x /home/pi/autostart.sh sudo chmod a+x /home/pi/autostart.sh
sudo chown pi:pi /home/pi/autostart.sh sudo chown pi:pi /home/pi/autostart.sh
@ -58,6 +79,24 @@ EOF
# Remove 00infoLCD.sh from .bashrc of pi user # Remove 00infoLCD.sh from .bashrc of pi user
sudo sed -i s'/exec $SCRIPT/#exec $SCRIPT/' /home/pi/.bashrc sudo sed -i s'/exec $SCRIPT/#exec $SCRIPT/' /home/pi/.bashrc
# adapt design by changing openbox settings
sudo sed -i -E 's/<weight>Normal</<weight>Bold</g' /etc/xdg/openbox/lxde-pi-rc.xml
sudo sed -i -E 's/<name>PibotoLt</<name>Arial</g' /etc/xdg/openbox/lxde-pi-rc.xml
sudo sed -i -E 's/window.active.title.bg.color: #87919B/window.active.title.bg.color: #000046/' /usr/share/themes/PiXflat/openbox-3/themerc
sudo sed -i -E 's/window.inactive.title.bg.color: #EEEFEE/window.inactive.title.bg.color: #000046/' /usr/share/themes/PiXflat/openbox-3/themerc
# remove minimize, maximize, close from titlebar
sudo sed -i -E 's/titleLayout>LIMC/titleLayout>L/g' /etc/xdg/openbox/lxde-pi-rc.xml
# Copy over the macaroons
sudo mkdir -p /home/pi/.lnd/data/chain/bitcoin/mainnet/
sudo chmod 700 /home/pi/.lnd/
sudo ln -s /home/admin/.lnd/tls.cert /home/pi/.lnd/
sudo cp /home/admin/.lnd/data/chain/bitcoin/mainnet/readonly.macaroon /home/pi/.lnd/data/chain/bitcoin/mainnet/
sudo cp /home/admin/.lnd/data/chain/bitcoin/mainnet/invoice.macaroon /home/pi/.lnd/data/chain/bitcoin/mainnet/
sudo chmod 600 /home/pi/.lnd/data/chain/bitcoin/mainnet/*.macaroon
sudo chown -R pi:pi /home/pi/.lnd/
# rotate touchscreen based on if LCD is rotated # rotate touchscreen based on if LCD is rotated
if [ "${lcdrotate}" = "1" ]; then if [ "${lcdrotate}" = "1" ]; then
echo "LCD is rotated into default - no touchscreen rotate" echo "LCD is rotated into default - no touchscreen rotate"
@ -100,7 +139,7 @@ if [ "$1" = "0" ] || [ "$1" = "off" ]; then
sudo sed -i s'/--autologin admin/--autologin pi/' /etc/systemd/system/getty@tty1.service.d/autologin.conf sudo sed -i s'/--autologin admin/--autologin pi/' /etc/systemd/system/getty@tty1.service.d/autologin.conf
# move back old LXDE autostart config # move back old LXDE autostart config
sudp rm /etc/xdg/lxsession/LXDE-pi/autostart sudo rm /etc/xdg/lxsession/LXDE-pi/autostart
sudo mv /etc/xdg/lxsession/LXDE-pi/autostart.bak /etc/xdg/lxsession/LXDE-pi/autostart sudo mv /etc/xdg/lxsession/LXDE-pi/autostart.bak /etc/xdg/lxsession/LXDE-pi/autostart
# add again 00infoLCD.sh to .bashrc of pi user # add again 00infoLCD.sh to .bashrc of pi user