Files
tasmotizer/gui/send_config.py
2020-09-21 21:31:28 +02:00

230 lines
9.3 KiB
Python

import os
import keyring
from PyQt5.QtCore import QSettings, Qt
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QWidget, QFormLayout, QLineEdit, QLabel, QListWidget, \
QStackedWidget, QListWidgetItem, QMessageBox, QCheckBox
from gui.widgets import VLayout, HLayout, SpinBox, Password, Modules, TemplateComboBox
from utils import MissingDetail
class Setting:
def __init__(self, command, description, widget_class, required=False, **kwargs):
self.command = command
self.description = description
self._widget_class = widget_class
self._widget = None
self.required = required
self.default = kwargs.get('default', None)
self.align = kwargs.get('align', None)
self._settings = None
def apply_settings(self, settings):
self._settings = settings
def widget(self):
self._widget = self._widget_class()
try:
if isinstance(self._widget, Password):
value = keyring.get_password('tasmotizer', self.command)
else:
value = self._settings.value(self.command, self.default)
except keyring.errors.KeyringError:
QMessageBox.critical(self, "Error", "Tasmotizer is unable to use your system keyring")
if value:
if isinstance(self._widget, SpinBox):
self._widget.setValue(int(value))
elif isinstance(self._widget, QCheckBox):
self._widget.setChecked(True if value == '1' else False)
elif isinstance(self._widget, TemplateComboBox):
user_templates_file = os.path.sep.join([os.path.dirname(self._settings.fileName()), 'templates.txt'])
if os.path.exists(user_templates_file):
with open(user_templates_file, 'r') as user_tpl:
for entry in user_tpl.readlines():
if len(entry) > 1:
entry = entry.rstrip('\n')
self._widget.addItem(entry)
self._widget.setCurrentText(value)
elif isinstance(self._widget, Modules):
self._widget.setCurrentIndex(int(value))
else:
self._widget.setText(value)
if self.align:
self._widget.setAlignment(self.align)
return self._widget
def serial_command(self):
if isinstance(self._widget, SpinBox):
value = self._widget.value()
elif isinstance(self._widget, Modules):
value = self._widget.currentData()
elif isinstance(self._widget, QCheckBox):
value = 1 if self._widget.isChecked() else 0
elif isinstance(self._widget, TemplateComboBox):
value = self._widget.currentText().rstrip('\n')
else:
value = self._widget.text()
if self.required and not value:
raise MissingDetail(f'{self.section} setting missing', f"{self._settings['desc']} is required.")
if value != self.default or isinstance(self._widget, QLabel):
try:
if isinstance(self._widget, Password):
keyring.set_password('tasmotizer', self.command, value)
else:
self._settings.setValue(self.command, value)
except keyring.errors.KeyringError:
QMessageBox.critical(self, "Error", "Tasmotizer is unable to use your system keyring")
return f'{self.command} {value}'
configs = {
'Hostname':
[
Setting(command='hostname', description='Hostname', widget_class=QLineEdit, required=True),
],
'WiFi':
[
Setting(command='ssid1', description='AP1', widget_class=QLineEdit, required=True),
Setting(command='password1', description='Password1', widget_class=Password, required=True),
],
'Recovery WiFi':
[
Setting(command='ssid2', description='AP2', widget_class=QLabel, default='Tasmota Recovery', align=Qt.AlignVCenter | Qt.AlignRight),
Setting(command='password2', description='Password1', widget_class=QLabel, default='a1b2c3d4', align=Qt.AlignVCenter | Qt.AlignRight),
],
'Static IP':
[
Setting(command='ipaddress1', description='IP', widget_class=QLineEdit, required=True),
Setting(command='ipaddress2', description='Gateway', widget_class=QLineEdit, required=True),
Setting(command='ipaddress3', description='Subnet', widget_class=QLineEdit, required=True, default='255.255.255.0'),
Setting(command='ipaddress4', description='DNS Server', widget_class=QLineEdit),
],
'MQTT':
[
Setting(command='mqtthost', description='Broker', widget_class=QLineEdit, required=True),
Setting(command='mqttport', description='Port', widget_class=SpinBox, default=1883),
Setting(command='topic', description='Topic', widget_class=QLineEdit, required=True, default='tasmota_%06X'),
Setting(command='fulltopic', description='FullTopic', widget_class=QLineEdit, required=True, default='%prefix%/%topic%/'),
],
'MQTT Auth':
[
Setting(command='mqttuser', description='User', widget_class=QLineEdit, required=True),
Setting(command='mqttpassword', description='Password', widget_class=Password),
],
'Module':
[
Setting(command='module', description='Module', widget_class=Modules),
],
'Template':
[
Setting(command='template', description='Template', widget_class=TemplateComboBox, required=True),
],
'CORS':
[
Setting(command='cors', description='CORS Domain', widget_class=QLineEdit, required=True, default='*'),
],
'SetOptions':
[
Setting(command='setoption19', description='Enable HomeAssistant auto-discovery (SetOption19)', widget_class=QCheckBox),
Setting(command='setoption52', description='Display optional time offset from UTC in JSON payloads (SetOption52)', widget_class=QCheckBox),
Setting(command='setoption65', description='Tasmota won\'t erase the settings after 4 quick power cycles (SetOption65)', widget_class=QCheckBox),
]
}
class ConfigWidget(QWidget):
def __init__(self, section, content):
super(ConfigWidget, self).__init__()
self.setLayout(QFormLayout())
self.section = section
self.content = content
self.settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'tasmota', 'tasmotizer')
for setting in self.content:
setting.apply_settings(self.settings)
self.layout().addRow(setting.description, setting.widget())
def collect_and_save(self):
return [setting.serial_command() for setting in self.content]
class SendConfigDialog(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle('Send configuration to device')
self.settings = QSettings(QSettings.IniFormat, QSettings.UserScope, 'tasmota', 'tasmotizer')
self.commands = []
self.module_mode = 0
vl = VLayout()
self.setLayout(vl)
hl = HLayout(0)
self.config_list = QListWidget()
self.config_list.setMinimumWidth(150)
self.config_list.setAlternatingRowColors(True)
self.config_stack = QStackedWidget()
self.config_stack.setMaximumWidth(500)
for section, content in configs.items():
widget = ConfigWidget(section, content)
self.config_stack.addWidget(widget)
config_list_item = QListWidgetItem(section)
config_list_item.setFlags(config_list_item.flags() | Qt.ItemIsUserCheckable)
config_list_item.setCheckState(self.settings.value(section, Qt.Unchecked, int))
self.config_list.addItem(config_list_item)
hl.addWidgets([self.config_list, self.config_stack])
hl.setStretch(0, 2)
hl.setStretch(1, 1)
vl.addLayout(hl)
btns = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close)
btns.accepted.connect(self.accept)
btns.rejected.connect(self.reject)
vl.addWidget(btns)
self.config_list.currentRowChanged.connect(self.config_stack.setCurrentIndex)
self.config_list.doubleClicked.connect(self.toggle_item_check)
def toggle_item_check(self, idx):
item = self.config_list.item(idx.row())
if item.checkState() == Qt.Unchecked:
item.setCheckState(Qt.Checked)
else:
item.setCheckState(Qt.Unchecked)
def accept(self):
try:
for row in range(self.config_list.count()):
item = self.config_list.item(row)
widget = self.config_stack.widget(row)
if item.checkState() == Qt.Checked:
self.commands.extend(widget.collect_and_save())
self.settings.setValue(widget.section, Qt.Checked)
else:
self.settings.remove(widget.section)
if self.commands:
self.commands.append("restart 1")
self.done(QDialog.Accepted)
else:
QMessageBox.warning(self, "Warning", "Nothing to send.\nTick one of the checkboxes on the list.")
except MissingDetail as e:
QMessageBox.critical(self, e.args[0], e.args[1])