mirror of
https://github.com/tasmota/tasmotizer.git
synced 2026-03-03 07:04:12 +01:00
230 lines
9.3 KiB
Python
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])
|