raspiblitz/home.admin/BlitzTUI/blitztui/main.py
/rootzoll 074c9d8549
merging pre-1.7.1 (#2462)
* fix copychain returns

* typo in sync loop

* stop services on inconsistent state

* calling correct provisioning

* apply bitcoin and lncli aliases in all scripts

* network.aliases: add CLNETWORK

* make cln default plugin dir: cln-plugins-enabled

similar to the nginx model make 2 directories for plugins:
cln-plugins-enabled - symlinked to ~/.lightning/plugins
plugins from here are loaded automatically on cln start

cln-plugins-available: plugins are downloaded here to
be run until the next cln restart (or stopped with runonce)

note the disk is mounted with noexec so plugins can't
run from there

discuss in: https://github.com/rootzoll/raspiblitz/issues/2295

* move shutdown script

* change all place where shutdown script is used

* change notify & release

* moved shutdown script

* moved shutdown scripts

* add more debug info

* moving github script

* remove chain in sync

* no longer needed chain in sync

* move debug script

* patch patch command

* make sure setup file is sourced

* remove debug output

* make sure lnd is put behind tor

* change indent

* get fresh sync progress

* avoid scrolling in menus

* use new selfsignedcert if no lnd tls.cert present

* sparko: add info and connect menu with own cert

https://github.com/rootzoll/raspiblitz/issues/2295

* cln.rest: add connect option for Zeus

https://github.com/rootzoll/raspiblitz/issues/2295

* cln: add the backup plugin + options

Usage options:
cln-plugin.backup.sh [on] [testnet|mainnet|signet]
cln-plugin.backup.sh [restore] [testnet|mainnet|signet] [force]
cln-plugin.backup.sh [backup-compact] [testnet|mainnet|signet]
https://github.com/lightningd/plugins/tree/master/backup

Discussed in: https://github.com/rootzoll/raspiblitz/issues/2295

* cln: add cln-plugin.standard.python.sh

Install and show the output of the chosen plugin for C-lightning
Usage:
cln-plugin.standard-python.sh on [plugin-name] [testnet|mainnet|signet] [runonce]
tested plugins:
summary | helpme | feeadjuster

find more at:
https://github.com/lightningd/plugins
discussed in:
https://github.com/rootzoll/raspiblitz/issues/2295

* shellcheck: change all `egrep` to `grep -E`

https://github.com/koalaman/shellcheck/wiki/SC2196

* do not resolve aliases, use as variables

* lnd: fix lnd.conf for parallel networks

discussed in: https://github.com/rootzoll/raspiblitz/issues/2290

* lnd: add LND option for parallel networks

* deprecate Testnet in SETTINGS
keysend and autopilot only for mainnet

due to: https://github.com/rootzoll/raspiblitz/issues/2290

* lnd: autopilot and autounlock for testnet

* fix comments

* add the SYSTEM menu for parallel chains

* RTL update to v0.11.0

make chain specific directory for the config: /home/rtl/${netprefix}RTL/
use ${netprefix}lnd.conf in config
override Environmen tvaribales for cln in the systemd service:
/etc/systemd/system/${netprefix}${typeprefix}RTL.service

discussed in: https://github.com/rootzoll/raspiblitz/issues/2384

* lnd.setname.sh for testnet

* display ${CHAIN} in the SYSTEM menu options

* keep _aliases file when live patches are applied

* all lncli_aliases to be used as variables

* default to KIllMode=control-group in services

https://www.man7.org/linux/man-pages/man5/systemd.kill.5.html
discussed in: https://github.com/rootzoll/raspiblitz/issues/1901

* add cln.hsmtool.sh for  hsm_secret handling

encrypt | decrypt | autounlock the hsm_secret for C-lightning
usage:
cln.hsmtool.sh [unlock] [testnet|mainnet|signet]
cln.hsmtool.sh [encrypt|decrypt] [testnet|mainnet|signet]
cln.hsmtool.sh [autounlock-on|autounlock-off] [testnet|mainnet|signet]

discussed in: https://github.com/rootzoll/raspiblitz/issues/2295

* add cln.install-service.sh to set up cln with systemd

script to set up or update the CLN systemd service
checks for hsm_secret encryption, autounlock and the sparko plugin
usage:
/home/admin/config.scripts/cln.install-service.sh $CHAIN

discussed in: https://github.com/rootzoll/raspiblitz/issues/2295

* use symlink to cln-plugins-enabled for all plugins

* keep lnd autopilot and autounlock mainnet only

mainnet only settings:
lnd autopilot
lnd keysend
circuibreaker
lnd autounlock
StaticChannelBackup to DropBox and USB

* cln FUNDING fix parsing address

* cln.hsmtool: add change-password and lock options

* always set password A

* cached peer info

* fix printing cache

* fix check for existing files

* handle bitcoind not running

* result with newline

* test line break

* test new line

* test new line

* two vars on output

* #2388 improve online check (less pinging)

* used cached peer status

* move chache

* cach file permissions

* allow sudo call

* fix cache

* remove double scan info

* add conf info to sync screen

* reorder info

* add space

* add space

* order info

* internet suppress error messages

* order info

* fix offering Blockchain copy

* fix hostname

* final ready state info

* lnd unlock after provision

* remove debug exit

* harmonize ready state

* add status to lnd unlock

* update lnd unlock script

* edit the unlock

* remove debug echo

* add debug

* add debug

* fix if statement

* debug output

* switch position of source setupdata

* #1126 preparing new setup with new c-lightning  (#2396)

* move debug script

* patch patch command

* make sure setup file is sourced

* remove debug output

* make sure lnd is put behind tor

* change indent

* get fresh sync progress

* always set password A

* cached peer info

* fix printing cache

* fix check for existing files

* handle bitcoind not running

* result with newline

* test line break

* test new line

* test new line

* two vars on output

* #2388 improve online check (less pinging)

* used cached peer status

* move chache

* cach file permissions

* allow sudo call

* fix cache

* remove double scan info

* add conf info to sync screen

* reorder info

* add space

* add space

* order info

* internet suppress error messages

* order info

* fix offering Blockchain copy

* fix hostname

* final ready state info

* lnd unlock after provision

* remove debug exit

* harmonize ready state

* add status to lnd unlock

* update lnd unlock script

* edit the unlock

* remove debug echo

* add debug

* add debug

* fix if statement

* debug output

* switch position of source setupdata

* lnd.unlock: fix typo

* netwok.monitor.sh debug

* cln-plugin.summary: fix paths

* rtl: fix permission of config on copy

* CASHOUT: use aliases for lnd

* rtl: install correctly for paralell chains

* use CHAIN in CLN and LND menu

* cln: add  CASHOUT option

* CLOSEALL and  CASHOUT: Improve labels and comments

Explaining CASHOUT in the label as discussed in:
https://github.com/rootzoll/raspiblitz/issues/2358

* cln.install: fix tor config

* cln: installthe  latest master until the next release

* _commands: source _aliases only if exists

* network aliases: fall back to 'main' for 'chain'

* new setup: keep testnet3 blocks and chainstate

* new setup: improve capitalization in menu

* improve help and comments

* cln: install Sparko if configured, but not present

* cln: add new wallet and import seed options

* fix peernum

* make sure that aliases get created on lnd setup

* no error if aliases not yet exist

* debug state

* fix network alias when not set

* fix syntax error

* add debug error info

* mute unlocking echos

* add debug wait

* add debug wait

* make sure info is uptodate

* make alias info as defaults

* rename option

* update sync info for no lightning

* add action string

* update sync info

* move name dialog

* wait for sync progress info

* wait for syncprogress info

* fix syntax

* get fresh data

* make sure to disable lnd

* add c-lightning to debug

* add setup logs to debug output

* fix syntax error

* add new-force wallet

* try fix call hsmtool

* hsm output tool

* fix output

* add seed-force

* refactor blitz.mnemonic.py

* test seed

* debug info

* dump object

* try check

* correct putput

* fix syntax

* check lnd for valid seed

* fix gui

* add Suez install script

discussed in:
https://github.com/rootzoll/raspiblitz/issues/2366

* cln rescue file export

* get correct version

* add cln export gui

* cln.backup.sh cln-import

* correct bytesize

* generate cln wallet with passwordc

* fix syntax

* fix syntax

* mute not needed error msg

* PEERING: correct message on success

* cln.install-service: fix sparko check

* add Suez to menu for CLN and LND

needs to be installed with the bitcoin user to be able to interact with CLN
related: https://github.com/rootzoll/raspiblitz/issues/2366

* debug _provison.setup.sh

stop bitcoind and restart with new config to avoid rpc password error
disable and enable service instead of daemon-reload
CLN: don't use passwordC  as seedPassword

* add cln.setname.sh

make lnd.setname.sh work with parallel wallets

* improve comments

* SYSTEM: add CLNLOG and CLNCONF options

* SYSTEM menu fixes

* cln: add more aliases cln, clnlog, clnconf

* cln: activate the backup plugin on every install

* SERVICES menu: fix chantools/CLN switch

* cln: load plugins from ${netprefix}cln-plugins-enabled

changed the config paths to $lightning-dir/config or /networkname/config

plugins are downloaded to the SDcard:
/home/bitcoin/cln-plugins-available/
symlinked and loaded automatically from:
/home/bitcoin/${netprefix}cln-plugins-enabled

Related: #2295

* sparko: don't show logs after install

* #2425 Adding experimental Blitz WebUI & API (#2426)

* no password C & D when cln

* add debug echos

* set defaults before

* #2228 wider grep to detect nvms (#2427)

* cln.hsmtool: init backup with the new wallet

* cln.install: fix access to raspiblitz.conf

* cln-plugin.backup: fix path to backup-cli

* cln: hide unhelpful warnings during setup

* remove old jinja template rendering

* fix lnd unlock detection

* cln: look for files in .lightning dir with sudo

* cln: correct lightning name in FInalDialog + typo

* cln: make sure .lightning/bitcoin dir exists

* FinalDialog: make the 24 words fit

* cln.install.sh: create cln config if not present

* Simplify localIP detection and improve compatibility (#2432)

* show tail info on provision

* only show lnd options when activated

* fix syntax

* only show main lightning impl options for RC1

* cln: always start the lightnind.service

* cln: clear before showing summary

* start cln on the end of provisioning

* exit 0 on cln menu

* press key after single actions

* remove key press on cln actions

* change to none

* detect cln running

* fix syntax

* fix lightniing info

* add TODO for CLN

* add clnblockheight

* zty with user bitcoin

* check synced to chain for cln

* fix increment

* try scanprogress

* use cln sync detection and progress

* replace LNTYPE

* next line

* fix spaces

* fix spaces

* Update README.md (#2456)

Fix 404

* Fix FAQ links (#2441)

* Fix invalid URL ( (#2440)

* support channels (#2382)

* use #2370 height optimization

* adjust exit codes in menu scripts

* adjust password menu exit codes

* adapt shutdown for cln

* settings adapt to running lightning impl

* fix syntax

* debug info

* add debug

* better height

* add default values

* add config entry if not there yet

* change default value

* Added exit info for cln

* make sure to load config file if available

* add sparko to menu

* add default for sparko

* replace default sparko entry

* show sparko installed or not

* add more description to sparko option

* RTL for clightnign in service menu

* main menu item rtl

* add RTL description

* debug in RTL install

* install sparko on recovery

* update menu with cln

* rework menu options

Co-authored-by: openoms <oms@tuta.io>
Co-authored-by: openoms <43343391+openoms@users.noreply.github.com>
Co-authored-by: rek79 <rek79@users.noreply.github.com>
Co-authored-by: Bitpaint <67663265+bitpaint@users.noreply.github.com>
Co-authored-by: João Thallis <joaothallis@icloud.com>
Co-authored-by: Peter Flock <78184669+peterflock@users.noreply.github.com>
Co-authored-by: nyxnor <nyxnor@protonmail.com>
2021-08-04 00:18:30 +02:00

678 lines
24 KiB
Python

#!/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 blitzpy import RaspiBlitzConfig, RaspiBlitzInfo
from blitztui.file_logger import setup_logging
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.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
self.check_config()
# 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()
if self.cfg_valid:
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 uxterm frame
self.start_info_lcd()
self.show()
def start_info_lcd(self, pause=12):
# if system has been running for more than 180 seconds then skip pause
if self.uptime > 180:
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('uxterm', ['-fa', 'Terminus', '-fs', '9', '-fn', 'fixed', '-into', str(int(self.ui.widget.winId())),
'+sb', '-hold', '-e', 'bash -c \"/home/admin/00infoLCD.sh --pause {}\"'.format(pause)])
def check_config(self):
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):
log.warning("file does not exist: {}".format(lnd_cfg_abs_path))
if not os.path.exists(rb_cfg_abs_path):
log.warning("file does not exist: {}".format(rb_cfg_abs_path))
if not os.path.exists(rb_info_abs_path):
log.warning("file does not exist: {}".format(rb_info_abs_path))
log.debug("init raspiblitz.conf")
rb_cfg_valid = False
self.rb_cfg = RaspiBlitzConfig(rb_cfg_abs_path)
try:
self.rb_cfg.reload()
rb_cfg_valid = True
except Exception as err:
pass
log.debug("init raspiblitz.info")
rb_info_valid = False
self.rb_info = RaspiBlitzInfo(rb_info_abs_path)
try:
self.rb_info.reload()
rb_info_valid = True
except Exception as err:
pass
self.cfg_valid = rb_cfg_valid and rb_info_valid
log.debug("checked cfg_valid with result: {}".format(self.cfg_valid))
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:
with ReadOnlyStub(network=self.rb_cfg.network.value, chain=self.rb_cfg.chain.value) as stub_readonly:
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
if self.status_lnd_due <= self.uptime:
log.debug("updating status_lnd")
try:
with ReadOnlyStub(network=self.rb_cfg.network.value, chain=self.rb_cfg.chain.value) as stub_readonly:
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
except Exception as err:
log.info("Exception on update_status_lnd")
pass
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")
try:
with ReadOnlyStub(network=self.rb_cfg.network.value, chain=self.rb_cfg.chain.value) as stub_readonly:
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
except Exception as err:
log.info("Exception on update_status_lnd_channels")
pass
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.value,
self.rb_cfg.network.value,
self.rb_cfg.chain.value)))
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.check_config()
self.update_uptime()
if self.cfg_valid:
self.update_status_lnd()
self.update_status_lnd_channels()
def update_watched_attr(self):
log.debug("updating: watched attributes")
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:{}".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.value))
ui.buttonBox.button(QDialogButtonBox.Ok).setText("Donation")
if self.rb_cfg.invoice_allow_donations.value:
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.value)
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.value)
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('uxterm', ['-fa', 'Terminus', '-fs', '9', '-fn', 'fixed', '-into', str(int(self.ui.widget.winId())),
'+sb', '-hold', '-e', 'bash -c \"sudo /home/admin/config.scripts/blitz.shutdown.sh\"'])
def b4_restart(self):
log.info("restart")
if IS_WIN32_ENV:
log.info("skipping on win32")
return
process = QProcess(self)
process.start('uxterm', ['-fa', 'Terminus', '-fs', '9', '-fn', 'fixed', '-into', str(int(self.ui.widget.winId())),
'+sb', '-hold', '-e', 'bash -c \"sudo /home/admin/config.scripts/blitz.shutdown.sh reboot\"'])
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:
with InvoiceStub(network=self.rb_cfg.network.value, chain=self.rb_cfg.chain.value) as stub_invoice:
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"
with ReadOnlyStub(network=self.rb_cfg.network.value, chain=self.rb_cfg.chain.value) as stub_readonly:
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.info("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('-d', '--debug', help="enable debug logging", action="store_true")
# parse args
args = parser.parse_args()
if args.debug:
setup_logging(log_level="DEBUG")
else:
setup_logging()
log.info("Starting BlitzTUI v{}".format(__version__))
# initialize app
app = QApplication(sys.argv)
w = AppWindow()
w.show()
# run app
sys.exit(app.exec_())
if __name__ == "__main__":
main()