add BlitzTUI files (based on PyQT5)

This commit is contained in:
Robert Habermann 2019-11-15 21:25:12 +01:00
parent a38ad7ee14
commit 029dcb9087
67 changed files with 41779 additions and 1099 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
.DS_Store
home.admin/.DS_Store
*.log
*.pyc
__pycache__

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 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"
# 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 ""

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 "OK - shell scripts and assests are synced"
echo "Reboot recommended"
echo ""
echo ""

View File

@ -15,10 +15,6 @@ if [ $# -eq 0 ] || [ "$1" = "-h" ] || [ "$1" = "-help" ]; then
exit 1
fi
# update install sources
echo "make sure dependencies are installed ..."
sudo apt-get install -y unclutter xterm
echo ""
###################
# SWITCH ON
@ -28,13 +24,37 @@ if [ "$1" = "1" ] || [ "$1" = "on" ]; then
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
sudo raspi-config nonint do_boot_behaviour B4
# 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 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-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 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
sudo mv /etc/xdg/lxsession/LXDE-pi/autostart /etc/xdg/lxsession/LXDE-pi/autostart.bak
@ -49,8 +69,9 @@ EOF
# editing autostart.sh
cat << EOF | sudo tee /home/pi/autostart.sh >/dev/null
#!/bin/sh
sleep 1
/usr/bin/python3 /home/admin/00infoLCDTK.py
unset QT_QPA_PLATFORMTHEME
/home/admin/python3-env-lnd/bin/blitz-tui
EOF
sudo chmod a+x /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
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
if [ "${lcdrotate}" = "1" ]; then
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
# 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
# add again 00infoLCD.sh to .bashrc of pi user

View File

@ -175,7 +175,7 @@ message UnlockWalletRequest {
/**
recovery_window is an optional argument specifying the address lookahead
when restoring a wallet seed. The recovery window applies to each
invdividual branch of the BIP44 derivation paths. Supplying a recovery
individual branch of the BIP44 derivation paths. Supplying a recovery
window of zero indicates that no addresses should be recovered, such after
the first initialization of the wallet.
*/
@ -212,7 +212,7 @@ service Lightning {
/** lncli: `walletbalance`
WalletBalance returns total unspent outputs(confirmed and unconfirmed), all
confirmed unspent outputs and all unconfirmed unspent outputs under control
of the wallet.
of the wallet.
*/
rpc WalletBalance (WalletBalanceRequest) returns (WalletBalanceResponse) {
option (google.api.http) = {
@ -389,7 +389,7 @@ service Lightning {
};
}
/** lncli: `subscribechannelevents`
/**
SubscribeChannelEvents creates a uni-directional stream from the server to
the client in which any updates relevant to the state of the channels are
sent over. Events include new active channels, inactive channels, and closed
@ -398,7 +398,7 @@ service Lightning {
rpc SubscribeChannelEvents (ChannelEventSubscription) returns (stream ChannelEventUpdate);
/** lncli: `closedchannels`
ClosedChannels returns a description of all the closed channels that
ClosedChannels returns a description of all the closed channels that
this node was a participant in.
*/
rpc ClosedChannels (ClosedChannelsRequest) returns (ClosedChannelsResponse) {
@ -430,6 +430,15 @@ service Lightning {
*/
rpc OpenChannel (OpenChannelRequest) returns (stream OpenStatusUpdate);
/**
ChannelAcceptor dispatches a bi-directional streaming RPC in which
OpenChannel requests are sent to the client and the client responds with
a boolean that tells LND whether or not to accept the channel. This allows
node operators to specify their own criteria for accepting inbound channels
through a single persistent connection.
*/
rpc ChannelAcceptor (stream ChannelAcceptResponse) returns (stream ChannelAcceptRequest);
/** lncli: `closechannel`
CloseChannel attempts to close an active channel identified by its channel
outpoint (ChannelPoint). The actions of this method can additionally be
@ -621,7 +630,7 @@ service Lightning {
/** lncli: `queryroutes`
QueryRoutes attempts to query the daemon's Channel Router for a possible
route to a target destination capable of carrying a specific amount of
satoshis. The retuned route contains the full details required to craft and
satoshis. The returned route contains the full details required to craft and
send an HTLC, also including the necessary information that should be
present within the Sphinx packet encapsulated within the HTLC.
*/
@ -768,6 +777,18 @@ service Lightning {
*/
rpc SubscribeChannelBackups(ChannelBackupSubscription) returns (stream ChanBackupSnapshot) {
};
/** lncli: `bakemacaroon`
BakeMacaroon allows the creation of a new macaroon with custom read and
write permissions. No first-party caveats are added since this can be done
offline.
*/
rpc BakeMacaroon(BakeMacaroonRequest) returns (BakeMacaroonResponse) {
option (google.api.http) = {
post: "/v1/macaroon"
body: "*"
};
};
}
message Utxo {
@ -814,6 +835,9 @@ message Transaction {
/// Addresses that received funds for this transaction
repeated string dest_addresses = 8 [ json_name = "dest_addresses" ];
/// The raw transaction hex.
string raw_tx_hex = 9 [ json_name = "raw_tx_hex" ];
}
message GetTransactionsRequest {
}
@ -873,13 +897,21 @@ message SendRequest {
The channel id of the channel that must be taken to the first hop. If zero,
any channel may be used.
*/
uint64 outgoing_chan_id = 9;
uint64 outgoing_chan_id = 9 [jstype = JS_STRING];
/**
An optional maximum total time lock for the route. If zero, there is no
maximum enforced.
An optional maximum total time lock for the route. This should not exceed
lnd's `--max-cltv-expiry` setting. If zero, then the value of
`--max-cltv-expiry` is enforced.
*/
uint32 cltv_limit = 10;
/**
An optional field that can be used to pass an arbitrary set of TLV records
to a peer which understands the new records. This can be used to pass
application specific data during the payment attempt.
*/
map<uint64, bytes> dest_tlv = 11;
}
message SendResponse {
@ -896,18 +928,64 @@ message SendToRouteRequest {
/// An optional hex-encoded payment hash to be used for the HTLC.
string payment_hash_string = 2;
/**
Deprecated. The set of routes that should be used to attempt to complete the
payment. The possibility to pass in multiple routes is deprecated and
instead the single route field below should be used in combination with the
streaming variant of SendToRoute.
*/
repeated Route routes = 3 [deprecated = true];
reserved 3;
/// Route that should be used to attempt to complete the payment.
Route route = 4;
}
message ChannelAcceptRequest {
/// The pubkey of the node that wishes to open an inbound channel.
bytes node_pubkey = 1;
/// The hash of the genesis block that the proposed channel resides in.
bytes chain_hash = 2;
/// The pending channel id.
bytes pending_chan_id = 3;
/// The funding amount in satoshis that initiator wishes to use in the channel.
uint64 funding_amt = 4;
/// The push amount of the proposed channel in millisatoshis.
uint64 push_amt = 5;
/// The dust limit of the initiator's commitment tx.
uint64 dust_limit = 6;
/// The maximum amount of coins in millisatoshis that can be pending in this channel.
uint64 max_value_in_flight = 7;
/// The minimum amount of satoshis the initiator requires us to have at all times.
uint64 channel_reserve = 8;
/// The smallest HTLC in millisatoshis that the initiator will accept.
uint64 min_htlc = 9;
/// The initial fee rate that the initiator suggests for both commitment transactions.
uint64 fee_per_kw = 10;
/**
The number of blocks to use for the relative time lock in the pay-to-self output
of both commitment transactions.
*/
uint32 csv_delay = 11;
/// The total number of incoming HTLC's that the initiator will accept.
uint32 max_accepted_htlcs = 12;
/// A bit-field which the initiator uses to specify proposed channel behavior.
uint32 channel_flags = 13;
}
message ChannelAcceptResponse {
/// Whether or not the client accepts the channel.
bool accept = 1;
/// The pending channel id to which this response applies.
bytes pending_chan_id = 2;
}
message ChannelPoint {
oneof funding_txid {
/// Txid of the funding transaction
@ -1098,7 +1176,7 @@ message Channel {
height, the next 3 the index within the block, and the last 2 bytes are the
output index for the channel.
*/
uint64 chan_id = 4 [json_name = "chan_id"];
uint64 chan_id = 4 [json_name = "chan_id", jstype = JS_STRING];
/// The total amount of funds held in this channel
int64 capacity = 5 [json_name = "capacity"];
@ -1162,8 +1240,37 @@ message Channel {
/// True if we were the ones that created the channel.
bool initiator = 18 [json_name = "initiator"];
/// A set of flags showing the current state of the cahnnel.
/// A set of flags showing the current state of the channel.
string chan_status_flags = 19 [json_name = "chan_status_flags"];
/// The minimum satoshis this node is required to reserve in its balance.
int64 local_chan_reserve_sat = 20 [json_name = "local_chan_reserve_sat"];
/**
The minimum satoshis the other node is required to reserve in its balance.
*/
int64 remote_chan_reserve_sat = 21 [json_name = "remote_chan_reserve_sat"];
/**
If true, then this channel uses the modern commitment format where the key
in the output of the remote party does not change each state. This makes
back up and recovery easier as when the channel is closed, the funds go
directly to that key.
*/
bool static_remote_key = 22 [json_name = "static_remote_key"];
/**
The number of seconds that the channel has been monitored by the channel
scoring system. Scores are currently not persisted, so this value may be
less than the lifetime of the channel [EXPERIMENTAL].
*/
int64 lifetime = 23 [json_name = "lifetime"];
/**
The number of seconds that the remote peer has been observed as being online
by the channel scoring system over the lifetime of the channel [EXPERIMENTAL].
*/
int64 uptime = 24 [json_name = "uptime"];
}
@ -1183,7 +1290,7 @@ message ChannelCloseSummary {
string channel_point = 1 [json_name = "channel_point"];
/// The unique channel ID for the channel.
uint64 chan_id = 2 [json_name = "chan_id"];
uint64 chan_id = 2 [json_name = "chan_id", jstype = JS_STRING];
/// The hash of the genesis block that this channel resides within.
string chain_hash = 3 [json_name = "chain_hash"];
@ -1335,6 +1442,12 @@ message GetInfoResponse {
/// A list of active chains the node is connected to
repeated Chain chains = 16 [json_name = "chains"];
/// The color of the current node in hex code format
string color = 17 [json_name = "color"];
// Whether we consider ourselves synced with the public channel graph.
bool synced_to_graph = 18 [json_name = "synced_to_graph"];
}
message Chain {
@ -1468,6 +1581,15 @@ message PendingChannelsResponse {
int64 local_balance = 4 [ json_name = "local_balance" ];
int64 remote_balance = 5 [ json_name = "remote_balance" ];
/// The minimum satoshis this node is required to reserve in its balance.
int64 local_chan_reserve_sat = 6 [json_name = "local_chan_reserve_sat"];
/**
The minimum satoshis the other node is required to reserve in its
balance.
*/
int64 remote_chan_reserve_sat = 7 [json_name = "remote_chan_reserve_sat"];
}
message PendingOpenChannel {
@ -1523,7 +1645,7 @@ message PendingChannelsResponse {
/// The balance in satoshis encumbered in this pending channel
int64 limbo_balance = 3 [ json_name = "limbo_balance" ];
/// The height at which funds can be sweeped into the wallet
/// The height at which funds can be swept into the wallet
uint32 maturity_height = 4 [ json_name = "maturity_height" ];
/*
@ -1606,11 +1728,7 @@ message QueryRoutesRequest {
/// The amount to send expressed in satoshis
int64 amt = 2;
/**
Deprecated. The max number of routes to return. In the future, QueryRoutes
will only return a single route.
*/
int32 num_routes = 3 [deprecated = true];
reserved 3;
/// An optional CLTV delta from the current height that should be used for the timelock of the final hop
int32 final_cltv_delta = 4;
@ -1629,20 +1747,46 @@ message QueryRoutesRequest {
repeated bytes ignored_nodes = 6;
/**
A list of edges to ignore during path finding.
Deprecated. A list of edges to ignore during path finding.
*/
repeated EdgeLocator ignored_edges = 7;
repeated EdgeLocator ignored_edges = 7 [deprecated = true];
/**
The source node where the request route should originated from. If empty,
self is assumed.
*/
string source_pub_key = 8;
/**
If set to true, edge probabilities from mission control will be used to get
the optimal route.
*/
bool use_mission_control = 9;
/**
A list of directed node pairs that will be ignored during path finding.
*/
repeated NodePair ignored_pairs = 10;
/**
An optional maximum total time lock for the route. If the source is empty or
ourselves, this should not exceed lnd's `--max-cltv-expiry` setting. If
zero, then the value of `--max-cltv-expiry` is used as the limit.
*/
uint32 cltv_limit = 11;
}
message NodePair {
/// The sending node of the pair.
bytes from = 1;
/// The receiving node of the pair.
bytes to = 2;
}
message EdgeLocator {
/// The short channel id of this edge.
uint64 channel_id = 1;
uint64 channel_id = 1 [jstype = JS_STRING];
/**
The direction of this edge. If direction_reverse is false, the direction
@ -1654,7 +1798,17 @@ message EdgeLocator {
}
message QueryRoutesResponse {
/**
The route that results from the path finding operation. This is still a
repeated field to retain backwards compatibility.
*/
repeated Route routes = 1 [json_name = "routes"];
/**
The success probability of the returned route based on the current mission
control state. [EXPERIMENTAL]
*/
double success_prob = 2 [json_name = "success_prob"];
}
message Hop {
@ -1663,7 +1817,7 @@ message Hop {
height, the next 3 the index within the block, and the last 2 bytes are the
output index for the channel.
*/
uint64 chan_id = 1 [json_name = "chan_id"];
uint64 chan_id = 1 [json_name = "chan_id", jstype = JS_STRING];
int64 chan_capacity = 2 [json_name = "chan_capacity"];
int64 amt_to_forward = 3 [json_name = "amt_to_forward", deprecated = true];
int64 fee = 4 [json_name = "fee", deprecated = true];
@ -1676,6 +1830,12 @@ message Hop {
can be executed without relying on a copy of the channel graph.
*/
string pub_key = 8 [json_name = "pub_key"];
/**
If set to true, then this hop will be encoded using the new variable length
TLV format.
*/
bool tlv_payload = 9 [json_name = "tlv_payload"];
}
/**
@ -1698,7 +1858,7 @@ message Route {
/**
The sum of the fees paid at each hop within the final route. In the case
of a one-hop payment, this value will be zero as we don't need to pay a fee
it ourself.
to ourselves.
*/
int64 total_fees = 2 [json_name = "total_fees", deprecated = true];
@ -1730,6 +1890,9 @@ message Route {
message NodeInfoRequest {
/// The 33-byte hex-encoded compressed public of the target node
string pub_key = 1;
/// If true, will include all known channels associated with the node.
bool include_channels = 2;
}
message NodeInfo {
@ -1742,8 +1905,14 @@ message NodeInfo {
*/
LightningNode node = 1 [json_name = "node"];
/// The total number of channels for the node.
uint32 num_channels = 2 [json_name = "num_channels"];
/// The sum of all channels capacity for the node, denominated in satoshis.
int64 total_capacity = 3 [json_name = "total_capacity"];
/// A list of all public channels for the node.
repeated ChannelEdge channels = 4 [json_name = "channels"];
}
/**
@ -1772,6 +1941,7 @@ message RoutingPolicy {
int64 fee_rate_milli_msat = 4 [json_name = "fee_rate_milli_msat"];
bool disabled = 5 [json_name = "disabled"];
uint64 max_htlc_msat = 6 [json_name = "max_htlc_msat"];
uint32 last_update = 7 [json_name = "last_update"];
}
/**
@ -1788,10 +1958,10 @@ message ChannelEdge {
height, the next 3 the index within the block, and the last 2 bytes are the
output index for the channel.
*/
uint64 channel_id = 1 [json_name = "channel_id"];
uint64 channel_id = 1 [json_name = "channel_id", jstype = JS_STRING];
string chan_point = 2 [json_name = "chan_point"];
uint32 last_update = 3 [json_name = "last_update"];
uint32 last_update = 3 [json_name = "last_update", deprecated = true];
string node1_pub = 4 [json_name = "node1_pub"];
string node2_pub = 5 [json_name = "node2_pub"];
@ -1826,7 +1996,7 @@ message ChanInfoRequest {
height, the next 3 the index within the block, and the last 2 bytes are the
output index for the channel.
*/
uint64 chan_id = 1;
uint64 chan_id = 1 [jstype = JS_STRING];
}
message NetworkInfoRequest {
@ -1846,6 +2016,9 @@ message NetworkInfo {
int64 max_channel_size = 9 [json_name = "max_channel_size"];
int64 median_channel_size_sat = 10 [json_name = "median_channel_size_sat"];
// The number of edges marked as zombies.
uint64 num_zombie_chans = 11 [json_name = "num_zombie_chans"];
// TODO(roasbeef): fee rate info, expiry
// * also additional RPC for tracking fee info once in
}
@ -1864,6 +2037,7 @@ message NodeUpdate {
string identity_key = 2;
bytes global_features = 3;
string alias = 4;
string color = 5;
}
message ChannelEdgeUpdate {
/**
@ -1871,7 +2045,7 @@ message ChannelEdgeUpdate {
height, the next 3 the index within the block, and the last 2 bytes are the
output index for the channel.
*/
uint64 chan_id = 1;
uint64 chan_id = 1 [jstype = JS_STRING];
ChannelPoint chan_point = 2;
@ -1888,7 +2062,7 @@ message ClosedChannelUpdate {
height, the next 3 the index within the block, and the last 2 bytes are the
output index for the channel.
*/
uint64 chan_id = 1;
uint64 chan_id = 1 [jstype = JS_STRING];
int64 capacity = 2;
uint32 closed_height = 3;
ChannelPoint chan_point = 4;
@ -1899,7 +2073,7 @@ message HopHint {
string node_id = 1 [json_name = "node_id"];
/// The unique identifier of the channel.
uint64 chan_id = 2 [json_name = "chan_id"];
uint64 chan_id = 2 [json_name = "chan_id", jstype = JS_STRING];
/// The base fee of the channel denominated in millisatoshis.
uint32 fee_base_msat = 3 [json_name = "fee_base_msat"];
@ -2039,6 +2213,42 @@ message Invoice {
The state the invoice is in.
*/
InvoiceState state = 21 [json_name = "state"];
/// List of HTLCs paying to this invoice [EXPERIMENTAL].
repeated InvoiceHTLC htlcs = 22 [json_name = "htlcs"];
}
enum InvoiceHTLCState {
ACCEPTED = 0;
SETTLED = 1;
CANCELED = 2;
}
/// Details of an HTLC that paid to an invoice
message InvoiceHTLC {
/// Short channel id over which the htlc was received.
uint64 chan_id = 1 [json_name = "chan_id", jstype = JS_STRING];
/// Index identifying the htlc on the channel.
uint64 htlc_index = 2 [json_name = "htlc_index"];
/// The amount of the htlc in msat.
uint64 amt_msat = 3 [json_name = "amt_msat"];
/// Block height at which this htlc was accepted.
int32 accept_height = 4 [json_name = "accept_height"];
/// Time at which this htlc was accepted.
int64 accept_time = 5 [json_name = "accept_time"];
/// Time at which this htlc was settled or canceled.
int64 resolve_time = 6 [json_name = "resolve_time"];
/// Block height at which this htlc expires.
int32 expiry_height = 7 [json_name = "expiry_height"];
/// Current state the htlc is in.
InvoiceHTLCState state = 8 [json_name = "state"];
}
message AddInvoiceResponse {
@ -2141,8 +2351,8 @@ message Payment {
/// The path this payment took
repeated string path = 4 [ json_name = "path" ];
/// The fee paid for this payment in satoshis
int64 fee = 5 [json_name = "fee"];
/// Deprecated, use fee_sat or fee_msat.
int64 fee = 5 [json_name = "fee", deprecated = true];
/// The payment preimage
string payment_preimage = 6 [json_name = "payment_preimage"];
@ -2152,9 +2362,34 @@ message Payment {
/// The value of the payment in milli-satoshis
int64 value_msat = 8 [json_name = "value_msat"];
/// The optional payment request being fulfilled.
string payment_request = 9 [json_name = "payment_request"];
enum PaymentStatus {
UNKNOWN = 0;
IN_FLIGHT = 1;
SUCCEEDED = 2;
FAILED = 3;
}
// The status of the payment.
PaymentStatus status = 10 [json_name = "status"];
/// The fee paid for this payment in satoshis
int64 fee_sat = 11 [json_name = "fee_sat"];
/// The fee paid for this payment in milli-satoshis
int64 fee_msat = 12 [json_name = "fee_msat"];
}
message ListPaymentsRequest {
/**
If true, then return payments that have not yet fully completed. This means
that pending payments, as well as failed payments will show up if this
field is set to True.
*/
bool include_incomplete = 1;
}
message ListPaymentsResponse {
@ -2246,6 +2481,9 @@ message PolicyUpdateRequest {
/// The required timelock delta for HTLCs forwarded over the channel.
uint32 time_lock_delta = 5 [json_name = "time_lock_delta"];
/// If set, the maximum HTLC size in milli-satoshis. If unset, the maximum HTLC will be unchanged.
uint64 max_htlc_msat = 6 [json_name = "max_htlc_msat"];
}
message PolicyUpdateResponse {
}
@ -2268,10 +2506,10 @@ message ForwardingEvent {
uint64 timestamp = 1 [json_name = "timestamp"];
/// The incoming channel ID that carried the HTLC that created the circuit.
uint64 chan_id_in = 2 [json_name = "chan_id_in"];
uint64 chan_id_in = 2 [json_name = "chan_id_in", jstype = JS_STRING];
/// The outgoing channel ID that carried the preimage that completed the circuit.
uint64 chan_id_out = 4 [json_name = "chan_id_out"];
uint64 chan_id_out = 4 [json_name = "chan_id_out", jstype = JS_STRING];
/// The total amount (in satoshis) of the incoming HTLC that created half the circuit.
uint64 amt_in = 5 [json_name = "amt_in"];
@ -2285,6 +2523,13 @@ message ForwardingEvent {
/// The total fee (in milli-satoshis) that this payment circuit carried.
uint64 fee_msat = 8 [json_name = "fee_msat"];
/// The total amount (in milli-satoshis) of the incoming HTLC that created half the circuit.
uint64 amt_in_msat = 9 [json_name = "amt_in_msat"];
/// The total amount (in milli-satoshis) of the outgoing HTLC that created the second half of the circuit.
uint64 amt_out_msat = 10 [json_name = "amt_out_msat"];
// TODO(roasbeef): add settlement latency?
// * use FPE on the chan id?
// * also list failures?
@ -2298,7 +2543,7 @@ message ForwardingHistoryResponse {
}
message ExportChannelBackupRequest {
/// The target chanenl point to obtain a back up for.
/// The target channel point to obtain a back up for.
ChannelPoint chan_point = 1;
}
@ -2310,7 +2555,7 @@ message ChannelBackup {
/**
Is an encrypted single-chan backup. this can be passed to
RestoreChannelBackups, or the WalletUnlocker Innit and Unlock methods in
RestoreChannelBackups, or the WalletUnlocker Init and Unlock methods in
order to trigger the recovery protocol.
*/
bytes chan_backup = 2 [ json_name = "chan_backup" ];
@ -2365,3 +2610,19 @@ message ChannelBackupSubscription {}
message VerifyChanBackupResponse {
}
message MacaroonPermission {
/// The entity a permission grants access to.
string entity = 1 [json_name = "entity"];
/// The action that is granted.
string action = 2 [json_name = "action"];
}
message BakeMacaroonRequest {
/// The list of permissions the new macaroon should grant.
repeated MacaroonPermission permissions = 1 [json_name = "permissions"];
}
message BakeMacaroonResponse {
/// The hex encoded macaroon, serialized in binary format.
string macaroon = 1 [json_name = "macaroon"];
}

File diff suppressed because one or more lines are too long

View File

@ -280,6 +280,11 @@ class LightningStub(object):
request_serializer=rpc__pb2.OpenChannelRequest.SerializeToString,
response_deserializer=rpc__pb2.OpenStatusUpdate.FromString,
)
self.ChannelAcceptor = channel.stream_stream(
'/lnrpc.Lightning/ChannelAcceptor',
request_serializer=rpc__pb2.ChannelAcceptResponse.SerializeToString,
response_deserializer=rpc__pb2.ChannelAcceptRequest.FromString,
)
self.CloseChannel = channel.unary_stream(
'/lnrpc.Lightning/CloseChannel',
request_serializer=rpc__pb2.CloseChannelRequest.SerializeToString,
@ -425,6 +430,11 @@ class LightningStub(object):
request_serializer=rpc__pb2.ChannelBackupSubscription.SerializeToString,
response_deserializer=rpc__pb2.ChanBackupSnapshot.FromString,
)
self.BakeMacaroon = channel.unary_unary(
'/lnrpc.Lightning/BakeMacaroon',
request_serializer=rpc__pb2.BakeMacaroonRequest.SerializeToString,
response_deserializer=rpc__pb2.BakeMacaroonResponse.FromString,
)
class LightningServicer(object):
@ -435,7 +445,7 @@ class LightningServicer(object):
"""* lncli: `walletbalance`
WalletBalance returns total unspent outputs(confirmed and unconfirmed), all
confirmed unspent outputs and all unconfirmed unspent outputs under control
of the wallet.
of the wallet.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
@ -600,7 +610,7 @@ class LightningServicer(object):
raise NotImplementedError('Method not implemented!')
def SubscribeChannelEvents(self, request, context):
"""* lncli: `subscribechannelevents`
"""*
SubscribeChannelEvents creates a uni-directional stream from the server to
the client in which any updates relevant to the state of the channels are
sent over. Events include new active channels, inactive channels, and closed
@ -612,7 +622,7 @@ class LightningServicer(object):
def ClosedChannels(self, request, context):
"""* lncli: `closedchannels`
ClosedChannels returns a description of all the closed channels that
ClosedChannels returns a description of all the closed channels that
this node was a participant in.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
@ -642,6 +652,18 @@ class LightningServicer(object):
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def ChannelAcceptor(self, request_iterator, context):
"""*
ChannelAcceptor dispatches a bi-directional streaming RPC in which
OpenChannel requests are sent to the client and the client responds with
a boolean that tells LND whether or not to accept the channel. This allows
node operators to specify their own criteria for accepting inbound channels
through a single persistent connection.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def CloseChannel(self, request, context):
"""* lncli: `closechannel`
CloseChannel attempts to close an active channel identified by its channel
@ -822,7 +844,7 @@ class LightningServicer(object):
"""* lncli: `queryroutes`
QueryRoutes attempts to query the daemon's Channel Router for a possible
route to a target destination capable of carrying a specific amount of
satoshis. The retuned route contains the full details required to craft and
satoshis. The returned route contains the full details required to craft and
send an HTLC, also including the necessary information that should be
present within the Sphinx packet encapsulated within the HTLC.
"""
@ -967,6 +989,16 @@ class LightningServicer(object):
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def BakeMacaroon(self, request, context):
"""* lncli: `bakemacaroon`
BakeMacaroon allows the creation of a new macaroon with custom read and
write permissions. No first-party caveats are added since this can be done
offline.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def add_LightningServicer_to_server(servicer, server):
rpc_method_handlers = {
@ -1075,6 +1107,11 @@ def add_LightningServicer_to_server(servicer, server):
request_deserializer=rpc__pb2.OpenChannelRequest.FromString,
response_serializer=rpc__pb2.OpenStatusUpdate.SerializeToString,
),
'ChannelAcceptor': grpc.stream_stream_rpc_method_handler(
servicer.ChannelAcceptor,
request_deserializer=rpc__pb2.ChannelAcceptResponse.FromString,
response_serializer=rpc__pb2.ChannelAcceptRequest.SerializeToString,
),
'CloseChannel': grpc.unary_stream_rpc_method_handler(
servicer.CloseChannel,
request_deserializer=rpc__pb2.CloseChannelRequest.FromString,
@ -1220,6 +1257,11 @@ def add_LightningServicer_to_server(servicer, server):
request_deserializer=rpc__pb2.ChannelBackupSubscription.FromString,
response_serializer=rpc__pb2.ChanBackupSnapshot.SerializeToString,
),
'BakeMacaroon': grpc.unary_unary_rpc_method_handler(
servicer.BakeMacaroon,
request_deserializer=rpc__pb2.BakeMacaroonRequest.FromString,
response_serializer=rpc__pb2.BakeMacaroonResponse.SerializeToString,
),
}
generic_handler = grpc.method_handlers_generic_handler(
'lnrpc.Lightning', rpc_method_handlers)