Skip to content

Commit

Permalink
Refactor (#278)
Browse files Browse the repository at this point in the history
  • Loading branch information
jziolkowski authored Sep 15, 2024
1 parent a474369 commit b185257
Show file tree
Hide file tree
Showing 27 changed files with 126 additions and 109 deletions.
5 changes: 3 additions & 2 deletions tdmgr/GUI/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
)

from tdmgr.GUI.widgets import GroupBoxV, HLayout, VLayout, console_font
from tdmgr.util import Message, TasmotaDevice
from tdmgr.util.commands import commands
from tdmgr.mqtt import Message
from tdmgr.tasmota.commands import commands
from tdmgr.tasmota.device import TasmotaDevice


class ConsoleWidget(QDockWidget):
Expand Down
4 changes: 3 additions & 1 deletion tdmgr/GUI/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@
base_view,
default_views,
)
from tdmgr.util import TasmotaDevice, initial_commands, resets
from tdmgr.mqtt import initial_commands
from tdmgr.tasmota.commands import resets
from tdmgr.tasmota.device import TasmotaDevice


class DevicesListWidget(QWidget):
Expand Down
4 changes: 2 additions & 2 deletions tdmgr/GUI/dialogs/buttons.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QTabWidget, QWidget

from tdmgr.GUI.widgets import Command, HTMLLabel, VLayout, docs_url
from tdmgr.util.commands import commands
from tdmgr.util.setoptions import setoptions
from tdmgr.tasmota.commands import commands
from tdmgr.tasmota.setoptions import setoptions


class ButtonsDialog(QDialog):
Expand Down
20 changes: 14 additions & 6 deletions tdmgr/GUI/dialogs/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,17 @@
from tdmgr.GUI.telemetry import TelemetryWidget
from tdmgr.GUI.widgets import Toolbar
from tdmgr.models.devices import TasmotaDevicesModel
from tdmgr.util import MQTT_PATH_REGEX, TasmotaDevice, TasmotaEnvironment, initial_commands
from tdmgr.util.discovery import lwt_discovery_stage2
from tdmgr.util.mqtt import DEFAULT_PATTERNS, Message, MqttClient, expand_fulltopic
from tdmgr.mqtt import (
DEFAULT_PATTERNS,
MQTT_PATH_REGEX,
Message,
MqttClient,
expand_fulltopic,
initial_commands,
)
from tdmgr.tasmota.device import TasmotaDevice
from tdmgr.tasmota.discovery import lwt_discovery_stage2
from tdmgr.tasmota.environment import TasmotaEnvironment

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -366,15 +374,15 @@ def mqtt_message(self, msg: Message):
if device := self.env.find_device(msg):
if msg.is_lwt:
log.debug("MQTT: LWT message for %s: %s", device.p["Topic"], msg.payload)
device.update_property("LWT", msg.payload)
device.online = msg.payload

if msg.payload == device.p["Online"]:
if device.online:
# known device came online, query initial state
self.initial_query(device, True)

else:
# forward the message for processing
device.update_property("LWT", device.p["Online"])
device.online = True
device.process_message(msg)

# TODO: ditto
Expand Down
2 changes: 1 addition & 1 deletion tdmgr/GUI/dialogs/patterns.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from PyQt5.QtWidgets import QDialog, QInputDialog, QLabel, QListWidget, QMessageBox, QPushButton

from tdmgr.GUI.widgets import HLayout, VLayout
from tdmgr.util import DEFAULT_PATTERNS
from tdmgr.mqtt import DEFAULT_PATTERNS


class PatternsDialog(QDialog):
Expand Down
4 changes: 2 additions & 2 deletions tdmgr/GUI/dialogs/power.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QTabWidget, QWidget

from tdmgr.GUI.widgets import Command, HTMLLabel, Interlock, PulseTime, VLayout, docs_url
from tdmgr.util.commands import commands
from tdmgr.util.setoptions import setoptions
from tdmgr.tasmota.commands import commands
from tdmgr.tasmota.setoptions import setoptions


class PowerDialog(QDialog):
Expand Down
2 changes: 1 addition & 1 deletion tdmgr/GUI/dialogs/setoptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QLabel

from tdmgr.GUI.widgets import DictComboBox, GroupBoxV, VLayout
from tdmgr.util.setoptions import setoptions
from tdmgr.tasmota.setoptions import setoptions


class SetOptionsDialog(QDialog):
Expand Down
4 changes: 2 additions & 2 deletions tdmgr/GUI/dialogs/switches.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QTabWidget, QWidget

from tdmgr.GUI.widgets import Command, CommandMultiSelect, HTMLLabel, VLayout, docs_url
from tdmgr.util.commands import commands
from tdmgr.util.setoptions import setoptions
from tdmgr.tasmota.commands import commands
from tdmgr.tasmota.setoptions import setoptions


class SwitchesDialog(QDialog):
Expand Down
11 changes: 10 additions & 1 deletion tdmgr/GUI/dialogs/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@
)

from tdmgr.GUI.widgets import DictComboBox, VLayout
from tdmgr.util import template_adc

template_adc = {
"0": "None",
"15": "User",
"1": "Analog",
"2": "Temperature",
"3": "Light",
"4": "Button",
"5": "Buttoni",
}


class TemplateDialog(QDialog):
Expand Down
3 changes: 2 additions & 1 deletion tdmgr/GUI/dialogs/timers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
)

from tdmgr.GUI.widgets import GroupBoxH, GroupBoxV, HLayout, VLayout
from tdmgr.util import Message, TasmotaDevice
from tdmgr.mqtt import Message
from tdmgr.tasmota.device import TasmotaDevice


class TimersDialog(QDialog):
Expand Down
3 changes: 2 additions & 1 deletion tdmgr/GUI/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
)

from tdmgr.GUI.widgets import CheckableAction, GroupBoxH, GroupBoxV, HLayout, Toolbar, VLayout
from tdmgr.util import Message, TasmotaDevice
from tdmgr.mqtt import Message
from tdmgr.tasmota.device import TasmotaDevice

RE_RULE = re.compile(r"^RULE\d", re.IGNORECASE)

Expand Down
2 changes: 1 addition & 1 deletion tdmgr/GUI/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
)

from tdmgr.schemas.result import PulseTimeLegacyResultSchema
from tdmgr.util import TasmotaDevice
from tdmgr.tasmota.device import TasmotaDevice

base_view = ["Device"]
default_views = {
Expand Down
4 changes: 2 additions & 2 deletions tdmgr/models/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from PyQt5.QtCore import QAbstractTableModel, QModelIndex, Qt

from tdmgr.models.roles import DeviceRoles
from tdmgr.util import TasmotaDevice
from tdmgr.tasmota.device import TasmotaDevice


class TasmotaDevicesModel(QAbstractTableModel):
Expand Down Expand Up @@ -128,7 +128,7 @@ def data(self, idx, role=Qt.DisplayRole):
return val

if role == DeviceRoles.LWTRole:
return d.is_online
return d.online

if role == DeviceRoles.RestartReasonRole:
return d.p.get("RestartReason")
Expand Down
23 changes: 23 additions & 0 deletions tdmgr/util/mqtt.py → tdmgr/mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,29 @@
]


def initial_commands():
commands = [
"template",
"modules",
"gpio",
"buttondebounce",
"switchdebounce",
"interlock",
"blinktime",
"blinkcount",
"pulsetime",
]

commands = [(command, "") for command in commands]
commands += [("status", "0"), ("gpios", "255")]

for sht in range(8):
commands.append([f"shutterrelay{sht + 1}", ""])
commands.append([f"shutterposition{sht + 1}", ""])

return commands


def expand_fulltopic(fulltopic):
if fulltopic[-1] != '/':
fulltopic = f"{fulltopic}/"
Expand Down
1 change: 0 additions & 1 deletion tdmgr/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ def configure_logging(args) -> None:
elif args.log_location:
log_path = os.path.join(args.log_location, "tdm.log")


logging.basicConfig(
level="DEBUG" if args.debug else "INFO",
datefmt="%Y-%m-%d %H:%M:%S",
Expand Down
Empty file added tdmgr/tasmota/__init__.py
Empty file.
10 changes: 10 additions & 0 deletions tdmgr/util/commands.py → tdmgr/tasmota/commands.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
resets = [
"1: reset device settings to firmware defaults",
"2: erase flash, reset device settings to firmware defaults",
"3: erase flash SDK parameters",
"4: reset device settings to firmware defaults, keep Wi-Fi credentials",
"5: erase flash, reset parameters to firmware defaults, keep Wi-Fi settings",
"6: erase flash, reset parameters to firmware defaults, keep Wi-Fi and MQTT settings",
"99: reset device bootcount to zero",
]

commands = {
"BlinkCount": {
"description": "Number of relay toggles (blinks)",
Expand Down
1 change: 1 addition & 0 deletions tdmgr/tasmota/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
COMMAND_UNKNOWN = {"Command": "Unknown"}
92 changes: 13 additions & 79 deletions tdmgr/util/__init__.py → tdmgr/tasmota/device.py
Original file line number Diff line number Diff line change
@@ -1,97 +1,24 @@
import json
import logging
import re
from enum import Enum
from functools import lru_cache
from typing import Callable, Optional, Union

from pkg_resources import parse_version
from pydantic import BaseModel, ValidationError
from PyQt5.QtCore import QObject, pyqtSignal

from tdmgr.mqtt import DEFAULT_PATTERNS, MQTT_PATH_REGEX, Message
from tdmgr.schemas.discovery import DiscoverySchema, TopicPrefixes
from tdmgr.schemas.result import (
PulseTimeLegacyResultSchema,
PulseTimeResultSchema,
TemplateResultSchema,
)
from tdmgr.schemas.status import STATUS_SCHEMA_MAP
from tdmgr.util.mqtt import DEFAULT_PATTERNS, MQTT_PATH_REGEX, Message
from tdmgr.tasmota.common import COMMAND_UNKNOWN

# TODO: extract from __init__

# TODO: extract to common
resets = [
"1: reset device settings to firmware defaults",
"2: erase flash, reset device settings to firmware defaults",
"3: erase flash SDK parameters",
"4: reset device settings to firmware defaults, keep Wi-Fi credentials",
"5: erase flash, reset parameters to firmware defaults, keep Wi-Fi settings",
"6: erase flash, reset parameters to firmware defaults, keep Wi-Fi and MQTT settings",
"99: reset device bootcount to zero",
]

template_adc = {
"0": "None",
"15": "User",
"1": "Analog",
"2": "Temperature",
"3": "Light",
"4": "Button",
"5": "Buttoni",
}


COMMAND_UNKNOWN = {"Command": "Unknown"}


# TODO: extract to mqtt
def initial_commands():
commands = [
"template",
"modules",
"gpio",
"buttondebounce",
"switchdebounce",
"interlock",
"blinktime",
"blinkcount",
"pulsetime",
]

commands = [(command, "") for command in commands]
commands += [("status", "0"), ("gpios", "255")]

for sht in range(8):
commands.append([f"shutterrelay{sht + 1}", ""])
commands.append([f"shutterposition{sht + 1}", ""])

return commands


def parse_payload(payload):
match = re.match(r"(\d+) \((.*)\)", payload)
if match:
return dict([match.groups()])
return {}


class DiscoveryMode(int, Enum):
BOTH = 0
NATIVE = 1
LEGACY = 2


class TasmotaEnvironment:
def __init__(self):
self.devices: list[TasmotaDevice] = []
self.lwts = dict()
self.retained = set()

def find_device(self, msg: Message) -> 'TasmotaDevice':
for d in self.devices:
if d.message_topic_matches_fulltopic(msg):
return d
log = logging.getLogger(__name__)


class TasmotaDevice(QObject):
Expand Down Expand Up @@ -294,7 +221,7 @@ def process_status(self, schema: BaseModel, payload: dict):
self.update_property(k, v)

except ValidationError as e:
logging.critical("MQTT: Cannot parse %s", e)
log.critical("MQTT: Cannot parse %s", e)

def process_sensor(self, payload: str):
sensor_data = json.loads(payload)
Expand All @@ -305,7 +232,7 @@ def process_sensor(self, payload: str):

def process_message(self, msg: Message):
if self.debug:
logging.debug("MQTT: %s %s", msg.topic, msg.payload)
log.debug("MQTT: %s %s", msg.topic, msg.payload)

if msg.prefix in (self.topic_prefixes.stat, self.topic_prefixes.tele):
# /STATE or /STATUS<x> response
Expand Down Expand Up @@ -408,9 +335,16 @@ def name(self):
return self.p["Topic"]

@property
def is_online(self):
def online(self):
return self.p.get("LWT", self.p["Offline"]) == self.p["Online"]

@online.setter
def online(self, val: Union[bool, dict]):
if isinstance(val, bool):
self.update_property("LWT", self.p["Online"])
else:
self.update_property("LWT", val)

@property
def url(self) -> Optional[str]:
if self.ip_address != "0.0.0.0":
Expand Down
4 changes: 3 additions & 1 deletion tdmgr/util/discovery.py → tdmgr/tasmota/discovery.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import logging

from tdmgr.util import Message, TasmotaDevice, TasmotaEnvironment
from tdmgr.mqtt import Message
from tdmgr.tasmota.device import TasmotaDevice
from tdmgr.tasmota.environment import TasmotaEnvironment


def native_discovery():
Expand Down
Loading

0 comments on commit b185257

Please sign in to comment.