diff --git a/tdmgr/GUI/console.py b/tdmgr/GUI/console.py index e05f42f..9e0a818 100644 --- a/tdmgr/GUI/console.py +++ b/tdmgr/GUI/console.py @@ -18,7 +18,7 @@ ) from tdmgr.GUI.widgets import GroupBoxV, HLayout, VLayout, console_font -from tdmgr.util import Message +from tdmgr.util import Message, TasmotaDevice from tdmgr.util.commands import commands @@ -29,7 +29,7 @@ def __init__(self, settings, device, *args, **kwargs): super().__init__() self.setAllowedAreas(Qt.BottomDockWidgetArea) self.setWindowTitle(f"Console [{device.name}]") - self.device = device + self.device: TasmotaDevice = device self.settings = settings @@ -113,7 +113,7 @@ def command_changed(self, text): @pyqtSlot(Message) def consoleAppend(self, msg: Message): - if self.device.matches(msg.topic): + if self.device.message_topic_matches_fulltopic(msg): self.console.appendPlainText( f"[{msg.timestamp.strftime('%X')}] {msg.topic} {msg.payload}" ) diff --git a/tdmgr/GUI/dialogs/main.py b/tdmgr/GUI/dialogs/main.py index 312d524..5f56ec8 100644 --- a/tdmgr/GUI/dialogs/main.py +++ b/tdmgr/GUI/dialogs/main.py @@ -363,7 +363,7 @@ def mqtt_message(self, msg: Message): # lwt = self.env.lwts.pop(f"{obj.ft}/LWT", obj.ofln) # device.update_property("LWT", lwt) - if device := self.env.find_device(msg.topic): + 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) @@ -373,9 +373,6 @@ def mqtt_message(self, msg: Message): self.initial_query(device, True) else: - _prefix = re.match(device.fulltopic_regex, msg.topic) - if _prefix: - msg.prefix = _prefix.groupdict()["prefix"] # forward the message for processing device.update_property("LWT", device.p["Online"]) device.process_message(msg) diff --git a/tdmgr/GUI/dialogs/timers.py b/tdmgr/GUI/dialogs/timers.py index 3ddb376..64f1404 100644 --- a/tdmgr/GUI/dialogs/timers.py +++ b/tdmgr/GUI/dialogs/timers.py @@ -14,7 +14,7 @@ ) from tdmgr.GUI.widgets import GroupBoxH, GroupBoxV, HLayout, VLayout -from tdmgr.util import Message +from tdmgr.util import Message, TasmotaDevice class TimersDialog(QDialog): @@ -22,7 +22,7 @@ class TimersDialog(QDialog): def __init__(self, device, *args, **kwargs): super(TimersDialog, self).__init__(*args, **kwargs) - self.device = device + self.device: TasmotaDevice = device self.timers = {} self.armKey = "Arm" self.setWindowTitle(f"Timers [{self.device.name}]") @@ -257,7 +257,7 @@ def parseMessage(self, msg: Message): "Days":"0000000","Repeat":0, "Action":0}, .... """ - if self.device.matches(msg.topic): + if self.device.message_topic_matches_fulltopic(msg): if msg.is_result or msg.endpoint == "TIMERS": payload = msg.dict() all = list(payload) diff --git a/tdmgr/GUI/rules.py b/tdmgr/GUI/rules.py index 5355fa1..a34c8a5 100644 --- a/tdmgr/GUI/rules.py +++ b/tdmgr/GUI/rules.py @@ -270,7 +270,7 @@ def unfold_rule(self, rules: str): @pyqtSlot(Message) def parseMessage(self, msg: Message): - if self.device.matches(msg.topic): + if self.device.message_topic_matches_fulltopic(msg): payload = msg.dict() if payload: if msg.is_result and msg.first_key == "T1" or msg.endpoint == "RULETIMER": diff --git a/tdmgr/util/__init__.py b/tdmgr/util/__init__.py index 5f69f0e..f7612c5 100644 --- a/tdmgr/util/__init__.py +++ b/tdmgr/util/__init__.py @@ -84,13 +84,13 @@ class DiscoveryMode(int, Enum): class TasmotaEnvironment: def __init__(self): - self.devices = [] + self.devices: list[TasmotaDevice] = [] self.lwts = dict() self.retained = set() - def find_device(self, topic) -> 'TasmotaDevice': + def find_device(self, msg: Message) -> 'TasmotaDevice': for d in self.devices: - if d.matches(topic): + if d.message_topic_matches_fulltopic(msg): return d @@ -142,6 +142,14 @@ def __init__(self, topic: str, fulltopic: str = "%prefix%/%topic%/", devicename: r"GPIOs\d?": self._process_gpios, } + @property + def topic(self) -> str: + return self.p["Topic"] + + @property + def fulltopic(self) -> str: + return self.p["FullTopic"] + @classmethod def from_discovery(cls, obj: DiscoverySchema): _ft = obj.ft.replace(obj.t, "%topic%").replace(obj.tp.tele, "%prefix%") @@ -184,6 +192,13 @@ def tele_topic(self, endpoint=""): def is_default(self): return self.p["FullTopic"] in DEFAULT_PATTERNS + def message_topic_matches_fulltopic(self, msg: Message) -> bool: + if _prefix := re.match(self.fulltopic_regex, msg.topic): + if not msg.prefix: + msg.prefix = _prefix.groupdict()["prefix"] + return True + return False + @property def subscribable_topics(self): topics = [] @@ -218,9 +233,6 @@ def fulltopic_regex(self) -> str: ) return f'^{_ft_pattern}' - def matches(self, topic: str) -> bool: - return re.match(self.fulltopic_regex, topic) is not None - def _process_module(self, msg: Message): print(msg.payload) @@ -352,6 +364,13 @@ def color(self): color.update({15: self.setoption(15), 17: self.setoption(17), 68: self.setoption(68)}) return color + @property + def ip_address(self) -> str: + for ip in [self.p.get("IPAddress"), self.p.get("Ethernet", {}).get("IPAddress")]: + if ip != "0.0.0.0": + return ip + return "0.0.0.0" + def setoption(self, o): if 0 <= o < 32: reg = 0 diff --git a/tests/conftest.py b/tests/conftest.py index 9e42777..3ba0720 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,12 +1,21 @@ +import os + import pytest from tdmgr.util import Message, TasmotaDevice +def get_payload(version: str, filename: str) -> bytes: + path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'status_parsing', 'jsonfiles') + fname = os.path.join(path, version, filename) + with open(fname, 'rb') as f: + return f.read() + + @pytest.fixture def device(request): topic = "device" - if request.param: + if hasattr(request, "param"): topic = request.param yield TasmotaDevice(topic) diff --git a/tests/test_device.py b/tests/test_device.py index 18cedce..0f4bd1f 100644 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -1,6 +1,7 @@ import pytest from tdmgr.util import Message, TasmotaDevice +from tests.conftest import get_payload @pytest.mark.parametrize( @@ -14,7 +15,8 @@ ) def test_matches(fulltopic, topic, expected): device = TasmotaDevice("some_topic", fulltopic) - assert device.matches(topic) is expected + msg = Message(topic) + assert device.message_topic_matches_fulltopic(msg) is expected @pytest.mark.parametrize("topic", ["stat/device/RESULT", "stat/device/TEMPLATE"]) @@ -27,3 +29,19 @@ def test_process_template(topic): assert message.first_key == "NAME" assert device.p.get("Template") == "NodeMCU" + + +@pytest.mark.parametrize("version", ("14.2.0.4",)) +@pytest.mark.parametrize( + "filename, expected", + [ + ("STATUS5.json", "192.168.0.1"), + ("STATUS5.1.json", "192.168.7.187"), + ], +) +def test_ip_address(device, version, filename, expected): + payload = get_payload(version, filename) + msg = Message("stat/topic/STATUS5", payload, prefix="stat") + device.process_message(msg) + + assert device.ip_address == expected diff --git a/tests/test_environment.py b/tests/test_environment.py index 30906c8..a50416a 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -1,6 +1,6 @@ import pytest -from tdmgr.util import TasmotaDevice, TasmotaEnvironment +from tdmgr.util import Message, TasmotaDevice, TasmotaEnvironment @pytest.mark.parametrize( @@ -19,6 +19,7 @@ def test_find_device(full_topic, test_topic, expected): dev = TasmotaDevice(topic='device', fulltopic=full_topic) env.devices.append(dev) - device = env.find_device(test_topic) + msg = Message(test_topic) + device = env.find_device(msg) assert (device == dev) is expected