Browse Source

refactor: added power setting to mqtt callbacks' userdata

Fabian Peter Hammerle 3 năm trước cách đây
mục cha
commit
0579c33564
3 tập tin đã thay đổi với 97 bổ sung30 xóa
  1. 19 9
      intertechno_cc1101_mqtt/__init__.py
  2. 12 5
      tests/test_mqtt.py
  3. 66 16
      tests/test_mqtt_message.py

+ 19 - 9
intertechno_cc1101_mqtt/__init__.py

@@ -14,6 +14,13 @@ _LOGGER = logging.getLogger(__name__)
 Aliases = typing.Dict[str, typing.Dict[str, int]]
 
 
+class _MQTTEventUserData:
+    # pylint: disable=too-few-public-methods; @dataclass eventually
+    def __init__(self, aliases: Aliases, power_setting: int):
+        self.aliases = aliases
+        self.power_setting = power_setting
+
+
 def _parse_topic(
     topic: str, aliases: Aliases
 ) -> typing.Tuple[typing.Optional[int], typing.Optional[int]]:
@@ -53,13 +60,13 @@ def _parse_topic(
 
 def _mqtt_on_message(
     mqtt_client: paho.mqtt.client.Client,
-    aliases: Aliases,
+    userdata: _MQTTEventUserData,
     message: paho.mqtt.client.MQTTMessage,
 ):
     # pylint: disable=unused-argument; callback
     # https://github.com/eclipse/paho.mqtt.python/blob/v1.5.0/src/paho/mqtt/client.py#L469
     _LOGGER.debug("received topic=%s payload=%r", message.topic, message.payload)
-    address, button_index = _parse_topic(topic=message.topic, aliases=aliases)
+    address, button_index = _parse_topic(topic=message.topic, aliases=userdata.aliases)
     if not address:
         return
     try:
@@ -70,16 +77,15 @@ def _mqtt_on_message(
             exc_info=True,
         )
         return
-    power_setting = intertechno_cc1101.DEFAULT_POWER_SETTING
     # https://www.home-assistant.io/integrations/switch.mqtt/#payload_on
     try:
         if message.payload.upper() == b"ON":
             remote_control.turn_on(
-                button_index=button_index, power_setting=power_setting
+                button_index=button_index, power_setting=userdata.power_setting
             )
         elif message.payload.upper() == b"OFF":
             remote_control.turn_off(
-                button_index=button_index, power_setting=power_setting
+                button_index=button_index, power_setting=userdata.power_setting
             )
         else:
             _LOGGER.warning(
@@ -135,7 +141,7 @@ def _publish_homeassistant_discovery_configs(
 
 def _mqtt_on_connect(
     mqtt_client: paho.mqtt.client.Client,
-    aliases: Aliases,
+    userdata: _MQTTEventUserData,
     flags: typing.Dict,
     return_code: int,
 ) -> None:
@@ -149,12 +155,12 @@ def _mqtt_on_connect(
     set_topic = "intertechno-cc1101/+/+/set"
     _LOGGER.info("subscribing to MQTT topic %r (address & button index)", set_topic)
     mqtt_client.subscribe(set_topic)
-    if aliases:
+    if userdata.aliases:
         set_alias_topic = "intertechno-cc1101/+/set"
         _LOGGER.info("subscribing to MQTT topic %r (alias)", set_alias_topic)
         mqtt_client.subscribe(set_alias_topic)
         _publish_homeassistant_discovery_configs(
-            mqtt_client=mqtt_client, aliases=aliases
+            mqtt_client=mqtt_client, aliases=userdata.aliases
         )
 
 
@@ -171,7 +177,11 @@ def _run(
     else:
         aliases = {}
     # https://pypi.org/project/paho-mqtt/
-    mqtt_client = paho.mqtt.client.Client(userdata=aliases)
+    mqtt_client = paho.mqtt.client.Client(
+        userdata=_MQTTEventUserData(
+            aliases=aliases, power_setting=intertechno_cc1101.DEFAULT_POWER_SETTING
+        )
+    )
     mqtt_client.on_connect = _mqtt_on_connect
     _LOGGER.info("connecting to MQTT broker %s:%d", mqtt_host, mqtt_port)
     if mqtt_username:

+ 12 - 5
tests/test_mqtt.py

@@ -23,12 +23,17 @@ def test__run(caplog, mqtt_host, mqtt_port):
             mqtt_password=None,
             alias_file_path=None,
         )
-    mqtt_client_mock.assert_called_once_with(userdata={})
+    assert mqtt_client_mock.call_count == 1
+    assert not mqtt_client_mock.call_args[0]
+    assert set(mqtt_client_mock.call_args[1]) == {"userdata"}
+    event_userdata = mqtt_client_mock.call_args[1]["userdata"]
+    assert event_userdata.aliases == {}
+    assert event_userdata.power_setting == 0xC6
     assert not mqtt_client_mock().username_pw_set.called
     mqtt_client_mock().connect.assert_called_once_with(host=mqtt_host, port=mqtt_port)
     mqtt_client_mock().socket().getpeername.return_value = (mqtt_host, mqtt_port)
     with caplog.at_level(logging.DEBUG):
-        mqtt_client_mock().on_connect(mqtt_client_mock(), {}, {}, 0)
+        mqtt_client_mock().on_connect(mqtt_client_mock(), event_userdata, {}, 0)
     # pylint: disable=comparison-with-callable
     assert mqtt_client_mock().on_message == intertechno_cc1101_mqtt._mqtt_on_message
     mqtt_client_mock().subscribe.assert_called_once_with("intertechno-cc1101/+/+/set")
@@ -65,7 +70,7 @@ def test__run_authentication(mqtt_host, mqtt_port, mqtt_username, mqtt_password)
             mqtt_password=mqtt_password,
             alias_file_path=None,
         )
-    mqtt_client_mock.assert_called_once_with(userdata={})
+    assert mqtt_client_mock.call_count == 1
     mqtt_client_mock().username_pw_set.assert_called_once_with(
         username=mqtt_username, password=mqtt_password
     )
@@ -103,12 +108,14 @@ def test__run_alias_file_path(caplog, tmp_path, mqtt_host, mqtt_port, aliases):
             mqtt_password=None,
             alias_file_path=alias_file_path,
         )
-    mqtt_client_mock.assert_called_once_with(userdata=aliases)
+    assert mqtt_client_mock.call_count == 1
+    event_userdata = mqtt_client_mock.call_args[1]["userdata"]
+    assert event_userdata.aliases == aliases
     mqtt_client_mock().socket().getpeername.return_value = (mqtt_host, mqtt_port)
     with unittest.mock.patch(
         "intertechno_cc1101_mqtt._publish_homeassistant_discovery_configs"
     ) as publish_discovery_config_mock, caplog.at_level(logging.INFO):
-        mqtt_client_mock().on_connect(mqtt_client_mock(), aliases, {}, 0)
+        mqtt_client_mock().on_connect(mqtt_client_mock(), event_userdata, {}, 0)
     assert mqtt_client_mock().subscribe.call_args_list[0] == unittest.mock.call(
         "intertechno-cc1101/+/+/set"
     )

+ 66 - 16
tests/test_mqtt_message.py

@@ -20,7 +20,11 @@ def test__mqtt_on_message_address(topic, address):
     message = MQTTMessage(topic=topic)
     message.payload = b"ON"
     with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
-        intertechno_cc1101_mqtt._mqtt_on_message("dummy", {}, message)
+        intertechno_cc1101_mqtt._mqtt_on_message(
+            "dummy",
+            intertechno_cc1101_mqtt._MQTTEventUserData(aliases={}, power_setting=0xC6),
+            message,
+        )
     remote_control_mock.assert_called_once_with(address=address)
 
 
@@ -36,7 +40,13 @@ def test__mqtt_on_message_invalid_address(caplog, topic, address_str):
     message.payload = b"ON"
     with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
         with caplog.at_level(logging.WARNING):
-            intertechno_cc1101_mqtt._mqtt_on_message("dummy", {}, message)
+            intertechno_cc1101_mqtt._mqtt_on_message(
+                "dummy",
+                intertechno_cc1101_mqtt._MQTTEventUserData(
+                    aliases={}, power_setting=0xC6
+                ),
+                message,
+            )
     remote_control_mock.assert_not_called()
     assert caplog.record_tuples == [
         (
@@ -58,22 +68,30 @@ def test__mqtt_on_message_invalid_address(caplog, topic, address_str):
     ((b"ON", True), (b"On", True), (b"on", True), (b"OFF", False), (b"off", False)),
 )
 @pytest.mark.parametrize("retain", [True, False])
+@pytest.mark.parametrize("power_setting", [0xC6, 0xC0])
 def test__mqtt_on_message_button_index_action(
-    topic, button_index, payload, turn_on, retain
+    topic, button_index, payload, turn_on, retain, power_setting
 ):
+    # pylint: disable=too-many-arguments
     message = MQTTMessage(topic=topic)
     message.payload = payload
     message.retain = retain
     with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
-        intertechno_cc1101_mqtt._mqtt_on_message("dummy", {}, message)
+        intertechno_cc1101_mqtt._mqtt_on_message(
+            "dummy",
+            intertechno_cc1101_mqtt._MQTTEventUserData(
+                aliases={}, power_setting=power_setting
+            ),
+            message,
+        )
     if turn_on:
         remote_control_mock().turn_on.assert_called_once_with(
-            button_index=button_index, power_setting=0xC6
+            button_index=button_index, power_setting=power_setting
         )
         remote_control_mock().turn_off.assert_not_called()
     else:
         remote_control_mock().turn_off.assert_called_once_with(
-            button_index=button_index, power_setting=0xC6
+            button_index=button_index, power_setting=power_setting
         )
         remote_control_mock().turn_on.assert_not_called()
 
@@ -90,7 +108,13 @@ def test__mqtt_on_message_invalid_button_index(caplog, topic, button_index_str):
     message.payload = b"ON"
     with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
         with caplog.at_level(logging.WARNING):
-            intertechno_cc1101_mqtt._mqtt_on_message("dummy", {}, message)
+            intertechno_cc1101_mqtt._mqtt_on_message(
+                "dummy",
+                intertechno_cc1101_mqtt._MQTTEventUserData(
+                    aliases={}, power_setting=0xC6
+                ),
+                message,
+            )
     remote_control_mock().turn_on.assert_not_called()
     remote_control_mock().turn_off.assert_not_called()
     assert caplog.record_tuples == [
@@ -117,10 +141,13 @@ def test__mqtt_on_message_alias(topic, address, button_index):
     with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
         intertechno_cc1101_mqtt._mqtt_on_message(
             "dummy",
-            {
-                "some-name": {"address": 21, "button-index": 0},
-                "another-name": {"address": "42", "button-index": "7"},  # :O
-            },
+            intertechno_cc1101_mqtt._MQTTEventUserData(
+                aliases={
+                    "some-name": {"address": 21, "button-index": 0},
+                    "another-name": {"address": "42", "button-index": "7"},  # :O
+                },
+                power_setting=0xC6,
+            ),
             message,
         )
     remote_control_mock.assert_called_once_with(address=address)
@@ -142,7 +169,12 @@ def test__mqtt_on_message_undefined_alias(caplog, topic, alias):
     with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
         with caplog.at_level(logging.WARNING):
             intertechno_cc1101_mqtt._mqtt_on_message(
-                "dummy", {"some-name": {"address": 21, "button-index": 0}}, message
+                "dummy",
+                intertechno_cc1101_mqtt._MQTTEventUserData(
+                    aliases={"some-name": {"address": 21, "button-index": 0}},
+                    power_setting=0xC6,
+                ),
+                message,
             )
     remote_control_mock.assert_not_called()
     assert caplog.record_tuples == [
@@ -167,7 +199,13 @@ def test__mqtt_on_message_alias_missing_attrs(caplog, aliases):
     message.payload = b"ON"
     with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
         with caplog.at_level(logging.WARNING):
-            intertechno_cc1101_mqtt._mqtt_on_message("dummy", aliases, message)
+            intertechno_cc1101_mqtt._mqtt_on_message(
+                "dummy",
+                intertechno_cc1101_mqtt._MQTTEventUserData(
+                    aliases=aliases, power_setting=0xC6
+                ),
+                message,
+            )
     remote_control_mock.assert_not_called()
     assert caplog.record_tuples == [
         (
@@ -185,7 +223,11 @@ def test__mqtt_on_message_remote_init_failed(caplog, topic):
     message = MQTTMessage(topic=topic)
     message.payload = b"ON"
     with caplog.at_level(logging.WARNING):
-        intertechno_cc1101_mqtt._mqtt_on_message("dummy", {}, message)
+        intertechno_cc1101_mqtt._mqtt_on_message(
+            "dummy",
+            intertechno_cc1101_mqtt._MQTTEventUserData(aliases={}, power_setting=0xC6),
+            message,
+        )
     assert len(caplog.records) == 1
     assert caplog.records[0].levelno == logging.WARNING
     assert (
@@ -202,7 +244,11 @@ def test__mqtt_on_message_transmission_failed(caplog):
         "cc1101.CC1101.__enter__",
         side_effect=FileNotFoundError("[Errno 2] No such file or directory"),
     ), caplog.at_level(logging.ERROR):
-        intertechno_cc1101_mqtt._mqtt_on_message("dummy", {}, message)
+        intertechno_cc1101_mqtt._mqtt_on_message(
+            "dummy",
+            intertechno_cc1101_mqtt._MQTTEventUserData(aliases={}, power_setting=0xC6),
+            message,
+        )
     assert len(caplog.records) == 1
     assert caplog.records[0].levelno == logging.ERROR
     assert caplog.records[0].message == "failed to send signal"
@@ -214,7 +260,11 @@ def test__mqtt_on_message_invalid_payload(caplog, payload):
     message = MQTTMessage(topic=b"intertechno-cc1101/1234/7/set")
     message.payload = payload
     with unittest.mock.patch("intertechno_cc1101.RemoteControl") as remote_control_mock:
-        intertechno_cc1101_mqtt._mqtt_on_message("dummy", {}, message)
+        intertechno_cc1101_mqtt._mqtt_on_message(
+            "dummy",
+            intertechno_cc1101_mqtt._MQTTEventUserData(aliases={}, power_setting=0xC6),
+            message,
+        )
     remote_control_mock().turn_off.assert_not_called()
     remote_control_mock().turn_on.assert_not_called()
     assert caplog.record_tuples == [