7 Commits

5 changed files with 230 additions and 44 deletions

View File

@@ -10,6 +10,10 @@ Maybe it's straightforward or obvious, but just for completeness: the name comes
1. The direct bond to tasmota (written for its "API" if one can call the HTTP endpoints an API)
2. The ability to turn on and off tasmota devices ("on" and "off" pronounced directly one after the other sounds (a bit) like "onov")
## Screenshots
![Tab 1 of the GUI](assets/gui_tab1.png)
![Tab 2 of the GUI](assets/gui_tab1.png)
## CLI Usage

BIN
assets/gui_tab1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

BIN
assets/gui_tab2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

View File

@@ -1,40 +1,134 @@
#!/usr/bin/python3
"""
Tasmotonov GUI - A simple Qt wrapper around the tasmotonov.py script
Copyright (C) 2025 Benjamin Burkhardt
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import sys
import subprocess
import tasmotonov
import configparser
from PySide6.QtUiTools import QUiLoader
from PySide6.QtWidgets import QApplication,QFileDialog
from PySide6.QtCore import QFile, QUrl, QIODevice
# set some standard values
filename = ""
action = "toggle"
def tab1_load_file():
def load_config(file="config.ini"): # load config from configparser.ConfigParser object
to_load = configparser.ConfigParser()
to_load.read(file)
global filename, action
if "DEFAULT" in to_load:
if "file" in to_load['DEFAULT']:
filename = to_load['DEFAULT']['file']
if filename != "":
try:
tab1_load_file()
except FileNotFoundError:
tab1_clear()
if "inline" in to_load['DEFAULT']:
window.tab2_plainTextEdit.setPlainText(to_load['DEFAULT']['inline'])
if "action" in to_load['DEFAULT']:
a_new = to_load['DEFAULT']['action']
if a_new != "":
action = a_new
# else: no config there yet
def save_config(file="config.ini"):
window.tab3_textBrowser.append("Saving configuration!")
global filename, action
to_save = configparser.ConfigParser()
to_save['DEFAULT'] = {}
to_save['DEFAULT']['file'] = str(filename)
to_save['DEFAULT']['inline'] = str(window.tab2_plainTextEdit.toPlainText())
to_save['DEFAULT']['action'] = str(action)
with open(file, 'w') as configfile:
to_save.write(configfile)
# tab1 slots
def tab1_load_file_pressed():
global filename
dialog = QFileDialog()
dialog.setFileMode(QFileDialog.AnyFile)
#fileNames = QStringList()
if dialog.exec():
filename = dialog.selectedFiles()[0]
window.tab1_label.setText(f"File loaded: {filename}")
window.tab1_label.show()
window.tab1_filecontent_textBrowser.setSource(QUrl(filename))
tab1_load_file()
def tab1_load_file():
global filename
window.tab1_label.setText(f"File loaded: {filename}")
window.tab3_textBrowser.append(f"File loaded: {filename}")
window.tab1_label.show()
window.tab1_filecontent_textBrowser.setSource(QUrl(filename))
window.tab1_label_help.show()
# clear listWidget and add the items
window.tab1_listWidget.clear()
window.tab1_listWidget.addItems(tasmotonov.TasmotonovRunner("file", filename, action, False, False).get_addresses())
def tab1_clear():
global filename
filename = ""
window.tab1_label.setText(f"File loaded: ")
window.tab3_textBrowser.append(f"File unloaded")
window.tab1_label.hide()
window.tab1_label_help.hide()
window.tab1_filecontent_textBrowser.clear()
window.tab1_listWidget.clear()
def tab1_action():
if filename != "":
p = subprocess.Popen(["python3", "tasmotonov.py", "file", filename, action], stdout=subprocess.PIPE)
out, err = p.communicate()
window.tab3_textBrowser.append("==== RUNNING ====\n" + str(out) + "\n=================\n")
tasmotonov_runner = tasmotonov.TasmotonovRunner("file", filename, action, False, False)
tasmotonov_runner.run()
window.tab3_textBrowser.append("\n==== RUNNING ====\n\n" + str(tasmotonov_runner.logger.log_string) + "\n=================\n")
tasmotonov_runner.logger.log_string = ""
else:
window.tab3_textBrowser.append("Will not run, no file selected!")
def tab1_single_action():
if filename != "":
tasmotonov_runner = tasmotonov.TasmotonovRunner("file", filename, action, False, False)
tasmotonov_runner.run_single(window.tab1_listWidget.currentRow())
window.tab3_textBrowser.append(str(tasmotonov_runner.logger.log_string))
# tab2 slots
def tab2_plainTextEdit_change():
# clear listWidget
window.tab2_listWidget.clear()
content = window.tab2_plainTextEdit.toPlainText()
if content != "":
tasmotonov_runner = tasmotonov.TasmotonovRunner("inline", content, action, False, False)
window.tab2_listWidget.addItems(tasmotonov_runner.get_addresses())
window.tab3_textBrowser.append(str(tasmotonov_runner.logger.log_string))
def tab2_action():
content = window.tab2_plainTextEdit.toPlainText()
if content != "":
p = subprocess.Popen(["python3", "tasmotonov.py", "inline", content, action], stdout=subprocess.PIPE)
out, err = p.communicate()
window.tab3_textBrowser.append("==== RUNNING ====\n" + str(out) + "\n=================\n")
print(out)
tasmotonov_runner = tasmotonov.TasmotonovRunner("inline", content, action, False, False)
tasmotonov_runner.run()
window.tab3_textBrowser.append("\n==== RUNNING ====\n\n" + str(tasmotonov_runner.logger.log_string) + "\n=================\n")
else:
window.tab3_textBrowser.append("Will not run, no input given!")
def tab2_single_action():
content = window.tab2_plainTextEdit.toPlainText()
tasmotonov_runner = tasmotonov.TasmotonovRunner("inline", content, action, False, False)
if len(tasmotonov_runner.get_addresses()) != 0:
tasmotonov_runner.run_single(window.tab2_listWidget.currentRow())
window.tab3_textBrowser.append(str(tasmotonov_runner.logger.log_string))
# tab3 slots
def tab3_clear():
window.tab3_textBrowser.clear()
# other slots
def select_on():
global action
action = "on"
@@ -51,7 +145,7 @@ def select_toggle():
if __name__ == "__main__":
app = QApplication(sys.argv)
ui_file_name = "tasmotonov.ui"
ui_file_name = "tasmotonov-gui.ui"
ui_file = QFile(ui_file_name)
if not ui_file.open(QIODevice.ReadOnly):
print(f"Cannot open {ui_file_name}: {ui_file.errorString()}")
@@ -64,14 +158,29 @@ if __name__ == "__main__":
sys.exit(-1)
window.tab1_label.hide()
window.tab1_load_file_pushButton.clicked.connect(tab1_load_file)
window.tab1_label_help.hide()
window.tab1_load_file_pushButton.clicked.connect(tab1_load_file_pressed)
window.tab1_clear_pushButton.clicked.connect(tab1_clear)
window.tab1_action_pushButton.clicked.connect(tab1_action)
window.tab1_single_action_pushButton.clicked.connect(tab1_single_action)
window.tab2_plainTextEdit.textChanged.connect(tab2_plainTextEdit_change)
window.tab2_action_pushButton.clicked.connect(tab2_action)
window.tab2_single_action_pushButton.clicked.connect(tab2_single_action)
window.radioButton_on.clicked.connect(select_on)
window.radioButton_off.clicked.connect(select_off)
window.radioButton_toggle.clicked.connect(select_toggle)
window.tab3_clear_button.clicked.connect(tab3_clear)
load_config()
window.show()
sys.exit(app.exec())
# execute app loop and define exit strategy
code = app.exec()
save_config()
sys.exit(code)

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>630</width>
<height>404</height>
<width>1166</width>
<height>546</height>
</rect>
</property>
<property name="windowTitle">
@@ -20,8 +20,8 @@
<enum>QTabWidget::TabShape::Rounded</enum>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="tabPosition">
<enum>QTabWidget::TabPosition::East</enum>
@@ -30,7 +30,7 @@
<enum>QTabWidget::TabShape::Rounded</enum>
</property>
<property name="currentIndex">
<number>1</number>
<number>0</number>
</property>
<property name="usesScrollButtons">
<bool>true</bool>
@@ -43,17 +43,34 @@
<string>From File</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="3" column="0">
<widget class="QTextBrowser" name="tab1_filecontent_textBrowser">
<property name="frameShadow">
<enum>QFrame::Shadow::Sunken</enum>
<item row="8" column="0">
<widget class="QPushButton" name="tab1_action_pushButton">
<property name="minimumSize">
<size>
<width>414</width>
<height>0</height>
</size>
</property>
<property name="openExternalLinks">
<bool>true</bool>
<property name="text">
<string>Run bulk action (specified below)</string>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="8" column="1">
<widget class="QPushButton" name="tab1_single_action_pushButton">
<property name="text">
<string>Run single action (specified below)</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="tab1_label_help">
<property name="text">
<string>Select an item and run an action:</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="tab1_label">
<property name="enabled">
<bool>true</bool>
@@ -69,28 +86,77 @@
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QPushButton" name="tab1_action_pushButton">
<property name="text">
<string>Run!</string>
<item row="6" column="0">
<widget class="QTextBrowser" name="tab1_filecontent_textBrowser">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>414</width>
<height>0</height>
</size>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Sunken</enum>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<item row="6" column="1">
<widget class="QListWidget" name="tab1_listWidget"/>
</item>
<item row="1" column="0" colspan="2">
<widget class="QPushButton" name="tab1_load_file_pushButton">
<property name="text">
<string>Load file</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QPushButton" name="tab1_clear_pushButton">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab2_from_inline">
<attribute name="title">
<string>From Inline</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<widget class="QListWidget" name="tab2_listWidget"/>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="tab2_single_action_pushButton">
<property name="text">
<string>Run single action (specified below)</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="tab2_action_pushButton">
<property name="text">
<string>Run bulk action (specified below)!</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="tab2_label_help_single">
<property name="text">
<string>Single actions: select the address and click the button below</string>
</property>
</widget>
</item>
<item row="0" column="0" rowspan="2">
<widget class="QPlainTextEdit" name="tab2_plainTextEdit">
<property name="lineWrapMode">
<enum>QPlainTextEdit::LineWrapMode::WidgetWidth</enum>
@@ -106,13 +172,6 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="tab2_action_pushButton">
<property name="text">
<string>Run!</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab3_output">
@@ -128,13 +187,27 @@
</widget>
</item>
<item>
<widget class="QTextBrowser" name="tab3_textBrowser"/>
<widget class="QTextBrowser" name="tab3_textBrowser">
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="tab3_clear_button">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="1" column="1">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QRadioButton" name="radioButton_on">