Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor #278

Merged
merged 2 commits into from
Sep 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading