Skip to content

Commit

Permalink
mqtt check for duplicate topics
Browse files Browse the repository at this point in the history
  • Loading branch information
spacemanspiff2007 committed Apr 12, 2024
1 parent e985209 commit 3e8884b
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 4 deletions.
20 changes: 20 additions & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,26 @@ These input settings can be used to poll data from a Tibber bridge:
password: "printed on bridge socket"
Example mqtt config
======================================

MQTT topics can be configured either by providing a full topic or a topic fragment.
With a topic fragment the resulting topic is build with the parent topic.
The structure is ``topic prefix`` / ``device`` / ``value``.
Providing a full topic will ignore the fragments.
The entries for qos and retain are optional.

..
YamlModel: OptionalMqttPublishConfig

.. code-block:: yaml
full topic: my/full/topic
qos: 1
Configuration Reference
======================================
All possible configuration options are described here. Not all entries are created by default in the config file
Expand Down
2 changes: 1 addition & 1 deletion src/sml2mqtt/mqtt/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

# isort: split

from .mqtt_obj import BASE_TOPIC, MqttObj, patch_analyze, setup_base_topic
from .mqtt_obj import BASE_TOPIC, MqttObj, check_for_duplicate_topics, patch_analyze, setup_base_topic
17 changes: 16 additions & 1 deletion src/sml2mqtt/mqtt/mqtt_obj.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import dataclasses
from collections.abc import Callable
from collections.abc import Callable, Generator
from typing import Any, Final

from sml2mqtt.__log__ import get_logger
Expand Down Expand Up @@ -121,6 +121,11 @@ def create_child(self, topic_fragment: str | None = None, qos: int | None = None
child.update()
return child

def iter_objs(self) -> Generator['MqttObj', Any, None]:
yield self
for child in self.children:
yield from child.iter_objs()


BASE_TOPIC: Final = MqttObj()

Expand All @@ -130,3 +135,13 @@ def setup_base_topic(topic: str, qos: int, retain: bool):
BASE_TOPIC.cfg.qos = qos
BASE_TOPIC.cfg.retain = retain
BASE_TOPIC.update()


def check_for_duplicate_topics(obj: MqttObj):
log = get_logger('mqtt')

topics: set[str] = set()
for o in obj.iter_objs():
if (topic := o.topic) in topics:
log.warning(f'Topic "{topic:s}" is already configured!')
topics.add(topic)
5 changes: 4 additions & 1 deletion src/sml2mqtt/sml_device/setup_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from typing import TYPE_CHECKING

from sml2mqtt.mqtt import BASE_TOPIC, check_for_duplicate_topics
from sml2mqtt.sml_value import SmlValue
from sml2mqtt.sml_value.operations import (
FactorOperation,
Expand All @@ -13,7 +14,6 @@
RoundOperation,
SequenceOperation,
SkipZeroMeterOperation,
VirtualMeterOperation,
)
from sml2mqtt.sml_value.setup_operations import setup_operations

Expand Down Expand Up @@ -118,3 +118,6 @@ def setup_device(device: SmlDevice, frame: SmlFrameValues, cfg: SmlDeviceConfig

_create_default_transformations(device.log, sml_value, frame, general_cfg)
_create_default_filters(device.log, sml_value, general_cfg)

# Check for duplicate MQTT topics
check_for_duplicate_topics(BASE_TOPIC)
5 changes: 5 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ def stream_reader(monkeypatch):
return r


@pytest.fixture(autouse=True)
def _clear_mqtt(monkeypatch):
monkeypatch.setattr(sml2mqtt.mqtt.BASE_TOPIC, 'children', [])


@pytest.fixture(autouse=True)
def check_no_logged_error(caplog, request):
caplog.set_level(logging.DEBUG)
Expand Down
17 changes: 16 additions & 1 deletion tests/test_mqtt_obj.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from sml2mqtt.mqtt import MqttObj
import pytest

from sml2mqtt.mqtt import MqttObj, check_for_duplicate_topics


def test_topmost(monkeypatch):
Expand Down Expand Up @@ -48,3 +50,16 @@ def test_child_change(monkeypatch):
parent.cfg.retain = False
parent.update()
assert (child.topic, child.qos, child.retain) == ('base/child', 0, False)


@pytest.mark.ignore_log_warnings()
def test_check_for_duplicate_messages(caplog):
parent = MqttObj('base', 2, True).update()
parent.create_child('child')
parent.create_child('child')

check_for_duplicate_topics(parent)

msg = "\n".join(x.msg for x in caplog.records)

assert msg == 'Topic "base/child" is already configured!'

0 comments on commit 3e8884b

Please sign in to comment.