Compare commits
9 Commits
v0.1.0
...
f5f132914b
Author | SHA1 | Date | |
---|---|---|---|
f5f132914b | |||
393b5e34b8 | |||
b088ad2cdb | |||
a408bb05a4 | |||
5af51ab79f | |||
c68e1e23fb | |||
231d95a0f7 | |||
b9eaf0928a | |||
9ed7c53b33 |
58
README.md
58
README.md
@@ -11,13 +11,69 @@ Maybe it's straightforward or obvious, but just for completeness: the name comes
|
||||
2. The ability to turn on and off tasmota devices ("on" and "off" pronounced directly one after the other sounds (a bit) like "onov")
|
||||
|
||||
|
||||
## Dependencies
|
||||
## CLI Usage
|
||||
|
||||
```
|
||||
usage: Tasmotonov - simply toggle multiple tasmota lights [-h] [-v] [--version] {file,inline} data {on,off,toggle}
|
||||
|
||||
A very simple script which allows you to turn on/off multiple tasmota devices specified.
|
||||
|
||||
positional arguments:
|
||||
{file,inline} Select either to read the adresses (of the devices) from a "file" or from "inline"
|
||||
data Either the path to the file, or a comma- or semicolon-separated list of tasmota adresses.
|
||||
{on,off,toggle} Select to turn all tasmota devices "on" or "off" or "toggle" (case insensitive)
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
-v, --verbose Turn on verbose file output
|
||||
--version show program's version number and exit
|
||||
|
||||
Info: if you choose a file as source, this files needs to contain the addresses of the tasmota devices either comma-separated, semicolon-separated, or newline-separated!
|
||||
|
||||
© Benjamin Burkhardt, 2025
|
||||
```
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
The CLI script ([tasmotonov.py](tasmotonov.py)) relies on two libaries apart from python3's standard libraries:
|
||||
|
||||
- `fqdn`: for validating the FQDN
|
||||
- `requests`: for making the HTTP requests
|
||||
|
||||
To install it, just execute the following command:
|
||||
|
||||
```bash
|
||||
pip install fqdn requests
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
The GUI application is based on Qt with it's python3 bindings PyQt6:
|
||||
|
||||
- `PySide6`: for running all the GUI stuff
|
||||
|
||||
To install it, just execute the following command:
|
||||
|
||||
```bash
|
||||
pip install PySide6
|
||||
```
|
||||
|
||||
|
||||
## Running
|
||||
|
||||
CLI: Use `./tasmotonov.py`
|
||||
|
||||
GUI: Use `./tasmotonov-gui.py`
|
||||
|
||||
|
||||
## Plans
|
||||
|
||||
I plan to add
|
||||
|
||||
- a darkmode maybe (but of course the interface doesn't look good either lol ;)
|
||||
- and bundle everything together to get a desktop application (e.g. runnable under windows without development tools)
|
||||
|
||||
|
||||
## License
|
||||
|
||||
|
77
tasmotonov-gui.py
Executable file
77
tasmotonov-gui.py
Executable file
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
from PySide6.QtUiTools import QUiLoader
|
||||
from PySide6.QtWidgets import QApplication,QFileDialog
|
||||
from PySide6.QtCore import QFile, QUrl, QIODevice
|
||||
|
||||
filename = ""
|
||||
action = "toggle"
|
||||
|
||||
def tab1_load_file():
|
||||
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))
|
||||
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")
|
||||
else:
|
||||
window.tab3_textBrowser.append("Will not run, no file selected!")
|
||||
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)
|
||||
else:
|
||||
window.tab3_textBrowser.append("Will not run, no input given!")
|
||||
def select_on():
|
||||
global action
|
||||
action = "on"
|
||||
window.tab3_textBrowser.append("Now turning everything on when running.")
|
||||
def select_off():
|
||||
global action
|
||||
action = "off"
|
||||
window.tab3_textBrowser.append("Now turning everything off when running.")
|
||||
def select_toggle():
|
||||
global action
|
||||
action = "toggle"
|
||||
window.tab3_textBrowser.append("Now toggling when running.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
ui_file_name = "tasmotonov.ui"
|
||||
ui_file = QFile(ui_file_name)
|
||||
if not ui_file.open(QIODevice.ReadOnly):
|
||||
print(f"Cannot open {ui_file_name}: {ui_file.errorString()}")
|
||||
sys.exit(-1)
|
||||
loader = QUiLoader()
|
||||
window = loader.load(ui_file)
|
||||
ui_file.close()
|
||||
if not window:
|
||||
print(loader.errorString())
|
||||
sys.exit(-1)
|
||||
|
||||
window.tab1_label.hide()
|
||||
window.tab1_load_file_pushButton.clicked.connect(tab1_load_file)
|
||||
window.tab1_action_pushButton.clicked.connect(tab1_action)
|
||||
window.tab2_action_pushButton.clicked.connect(tab2_action)
|
||||
window.radioButton_on.clicked.connect(select_on)
|
||||
window.radioButton_off.clicked.connect(select_off)
|
||||
window.radioButton_toggle.clicked.connect(select_toggle)
|
||||
|
||||
window.show()
|
||||
|
||||
sys.exit(app.exec())
|
||||
|
@@ -1,13 +1,34 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
"""
|
||||
Tasmotonov - A very simple script which allows you to turn on/off (or toggle) multiple tasmota devices specified.
|
||||
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 # for exiting with the correct exit code
|
||||
import argparse # to parse the cli args!
|
||||
import requests # needed to access the http endpoints
|
||||
import ipaddress # to validate IP addresses
|
||||
from fqdn import FQDN # validate FQDNs
|
||||
|
||||
version = "v1.0.0"
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog='Tasmotonov - simply toggle multiple tasmota lights',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
description='A very simple script which allows you to turn on/off multiple tasmota devices specified.',
|
||||
epilog='Info: if you choose a file as source, this files needs to contain the addresses of the tasmota devices either comma-separated, semicolon-separated, or newline-separated!\n\n© Benjamin Burkhardt, 2025')
|
||||
|
||||
@@ -15,6 +36,7 @@ parser.add_argument('source', help='Select either to read the adresses (of the d
|
||||
parser.add_argument('data', help='Either the path to the file, or a comma- or semicolon-separated list of tasmota adresses.')
|
||||
parser.add_argument('action', help='Select to turn all tasmota devices "on" or "off" or "toggle" (case insensitive)', choices=['on', 'off', 'toggle'])
|
||||
parser.add_argument('-v', '--verbose', help='Turn on verbose file output', action='store_true')
|
||||
parser.add_argument('--version', action='version', version=f'Tasmotonov.py {version}')
|
||||
|
||||
|
||||
# some helpers / utils
|
||||
@@ -59,6 +81,7 @@ def log_success(to_log, end='\n'):
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = parser.parse_args() # parse all given arguments
|
||||
log(f'Parsed args: {args}')
|
||||
|
||||
# convert the given adresses to a list
|
||||
tasmota_addresses = []
|
||||
@@ -103,7 +126,7 @@ if __name__ == '__main__':
|
||||
tasmota_addresses_cleaned = []
|
||||
for address in tasmota_addresses:
|
||||
if address != '':
|
||||
tasmota_addresses_cleaned.append(address.replace('\n', '').replace(';', '').replace(',', ''))
|
||||
tasmota_addresses_cleaned.append(address.replace('\n', '').replace(';', '').replace(',', '').replace(' ', ''))
|
||||
|
||||
# now check data for consistency and integrity (is it really an ip address or a domain name?)
|
||||
tasmota_addresses_validated = []
|
||||
|
170
tasmotonov.ui
Normal file
170
tasmotonov.ui
Normal file
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>mainWindow</class>
|
||||
<widget class="QMainWindow" name="mainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>630</width>
|
||||
<height>404</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Tasmotonov</string>
|
||||
</property>
|
||||
<property name="documentMode">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="tabShape">
|
||||
<enum>QTabWidget::TabShape::Rounded</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="1">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="tabPosition">
|
||||
<enum>QTabWidget::TabPosition::East</enum>
|
||||
</property>
|
||||
<property name="tabShape">
|
||||
<enum>QTabWidget::TabShape::Rounded</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="usesScrollButtons">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="documentMode">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab1_from_file">
|
||||
<attribute name="title">
|
||||
<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>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="tab1_label">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>File loaded: </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QPushButton" name="tab1_action_pushButton">
|
||||
<property name="text">
|
||||
<string>Run!</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="tab1_load_file_pushButton">
|
||||
<property name="text">
|
||||
<string>Load file</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>
|
||||
<widget class="QPlainTextEdit" name="tab2_plainTextEdit">
|
||||
<property name="lineWrapMode">
|
||||
<enum>QPlainTextEdit::LineWrapMode::WidgetWidth</enum>
|
||||
</property>
|
||||
<property name="plainText">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::TextInteractionFlag::LinksAccessibleByMouse|Qt::TextInteractionFlag::TextEditable|Qt::TextInteractionFlag::TextEditorInteraction|Qt::TextInteractionFlag::TextSelectableByKeyboard|Qt::TextInteractionFlag::TextSelectableByMouse</set>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Enter your addresses here (IP-Adresses, or Domain names / FQDNs). The list can be either comma-, semicolon-, or whitespace-separated, but should not be mixed!</string>
|
||||
</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">
|
||||
<attribute name="title">
|
||||
<string>Console</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="tab3_label_output">
|
||||
<property name="text">
|
||||
<string>Tasmotonov.py output</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTextBrowser" name="tab3_textBrowser"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButton_on">
|
||||
<property name="text">
|
||||
<string>All ON!</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButton_off">
|
||||
<property name="text">
|
||||
<string>All OFF!</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radioButton_toggle">
|
||||
<property name="text">
|
||||
<string>TOGGLE all!</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Reference in New Issue
Block a user