dbus2mqtt 0.4.0__py3-none-any.whl → 0.4.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dbus2mqtt might be problematic. Click here for more details.

@@ -107,8 +107,14 @@ class FlowActionMqttPublishConfig:
107
107
  type: Literal["mqtt_publish"] = "mqtt_publish"
108
108
  payload_type: Literal["json", "yaml", "text", "binary"] = "json"
109
109
 
110
+ @dataclass
111
+ class FlowActionLogConfig:
112
+ msg: str
113
+ type: Literal["log"] = "log"
114
+ level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO"
115
+
110
116
  FlowActionConfig = Annotated[
111
- FlowActionMqttPublishConfig | FlowActionContextSetConfig,
117
+ FlowActionMqttPublishConfig | FlowActionContextSetConfig | FlowActionLogConfig,
112
118
  Field(discriminator="type")
113
119
  ]
114
120
 
@@ -157,6 +163,7 @@ class MqttConfig:
157
163
  username: str
158
164
  password: SecretStr
159
165
  port: int = 1883
166
+ subscription_topics: list[str] = field(default_factory=lambda: ['dbus2mqtt/#'])
160
167
 
161
168
  @dataclass
162
169
  class Config:
@@ -512,7 +512,7 @@ class DbusClient:
512
512
  # clean lingering interface messgage handler from bus
513
513
  self.bus.remove_message_handler(proxy_interface._message_handler)
514
514
 
515
- # For now that InterfacesRemoved signal means the entire object is removed form D-Bus
515
+ # For now that InterfacesRemoved signal means the entire object is removed from D-Bus
516
516
  del self.subscriptions[bus_name].path_objects[path]
517
517
 
518
518
  # cleanup the entire BusNameSubscriptions if no more objects are subscribed
@@ -745,12 +745,13 @@ class DbusClient:
745
745
  path = message.body[0]
746
746
  await self._handle_interfaces_removed(bus_name, path)
747
747
 
748
-
749
748
  async def _on_mqtt_msg(self, msg: MqttMessage):
750
- # self.queue.put({
751
- # "topic": topic,
752
- # "payload": payload
753
- # })
749
+ """Executes dbus method calls or property updates on objects when messages have
750
+ 1. a matching subscription configured
751
+ 2. a matching method
752
+ 3. a matching bus_name (if provided)
753
+ 4. a matching path (if provided)
754
+ """
754
755
 
755
756
  found_matching_topic = False
756
757
  for subscription_configs in self.config.subscriptions:
@@ -766,6 +767,9 @@ class DbusClient:
766
767
  matched_method = False
767
768
  matched_property = False
768
769
 
770
+ payload_bus_name = msg.payload.get("bus_name") or "*"
771
+ payload_path = msg.payload.get("path") or "*"
772
+
769
773
  payload_method = msg.payload.get("method")
770
774
  payload_method_args = msg.payload.get("args") or []
771
775
 
@@ -773,47 +777,44 @@ class DbusClient:
773
777
  payload_value = msg.payload.get("value")
774
778
 
775
779
  if payload_method is None and (payload_property is None or payload_value is None):
776
- logger.info(f"on_mqtt_msg: Unsupported payload, missing 'method' or 'property/value', got method={payload_method}, property={payload_property}, value={payload_value} from {msg.payload}")
780
+ if msg.payload:
781
+ logger.info(f"on_mqtt_msg: Unsupported payload, missing 'method' or 'property/value', got method={payload_method}, property={payload_property}, value={payload_value} from {msg.payload}")
777
782
  return
778
783
 
779
784
  for [bus_name, bus_name_subscription] in self.subscriptions.items():
780
- for [path, proxy_object] in bus_name_subscription.path_objects.items():
781
- for subscription_configs in self.config.get_subscription_configs(bus_name=bus_name, path=path):
782
- for interface_config in subscription_configs.interfaces:
783
-
784
- for method in interface_config.methods:
785
-
786
- # filter configured method, configured topic, ...
787
- if method.method == payload_method:
788
- interface = proxy_object.get_interface(name=interface_config.interface)
789
- matched_method = True
790
-
791
- try:
792
- logger.info(f"on_mqtt_msg: method={method.method}, args={payload_method_args}, bus_name={bus_name}, path={path}, interface={interface_config.interface}")
793
- await self.call_dbus_interface_method(interface, method.method, payload_method_args)
794
- except Exception as e:
795
- logger.warning(f"on_mqtt_msg: method={method.method}, args={payload_method_args}, bus_name={bus_name} failed, exception={e}")
796
-
797
- for property in interface_config.properties:
798
- # filter configured property, configured topic, ...
799
- if property.property == payload_property:
800
- interface = proxy_object.get_interface(name=interface_config.interface)
801
- matched_property = True
802
-
803
- try:
804
- logger.info(f"on_mqtt_msg: property={property.property}, value={payload_value}, bus_name={bus_name}, path={path}, interface={interface_config.interface}")
805
- await self.set_dbus_interface_property(interface, property.property, payload_value)
806
- except Exception as e:
807
- logger.warning(f"on_mqtt_msg: property={property.property}, value={payload_value}, bus_name={bus_name} failed, exception={e}")
785
+ if fnmatch.fnmatchcase(bus_name, payload_bus_name):
786
+ for [path, proxy_object] in bus_name_subscription.path_objects.items():
787
+ if fnmatch.fnmatchcase(path, payload_path):
788
+ for subscription_configs in self.config.get_subscription_configs(bus_name=bus_name, path=path):
789
+ for interface_config in subscription_configs.interfaces:
790
+
791
+ for method in interface_config.methods:
792
+
793
+ # filter configured method, configured topic, ...
794
+ if method.method == payload_method:
795
+ interface = proxy_object.get_interface(name=interface_config.interface)
796
+ matched_method = True
797
+
798
+ try:
799
+ logger.info(f"on_mqtt_msg: method={method.method}, args={payload_method_args}, bus_name={bus_name}, path={path}, interface={interface_config.interface}")
800
+ await self.call_dbus_interface_method(interface, method.method, payload_method_args)
801
+ except Exception as e:
802
+ logger.warning(f"on_mqtt_msg: method={method.method}, args={payload_method_args}, bus_name={bus_name} failed, exception={e}")
803
+
804
+ for property in interface_config.properties:
805
+ # filter configured property, configured topic, ...
806
+ if property.property == payload_property:
807
+ interface = proxy_object.get_interface(name=interface_config.interface)
808
+ matched_property = True
809
+
810
+ try:
811
+ logger.info(f"on_mqtt_msg: property={property.property}, value={payload_value}, bus_name={bus_name}, path={path}, interface={interface_config.interface}")
812
+ await self.set_dbus_interface_property(interface, property.property, payload_value)
813
+ except Exception as e:
814
+ logger.warning(f"on_mqtt_msg: property={property.property}, value={payload_value}, bus_name={bus_name} failed, exception={e}")
808
815
 
809
816
  if not matched_method and not matched_property:
810
817
  if payload_method:
811
- logger.info(f"No configured or active dbus subscriptions for topic={msg.topic}, method={payload_method}, active bus_names={list(self.subscriptions.keys())}")
818
+ logger.info(f"No configured or active dbus subscriptions for topic={msg.topic}, method={payload_method}, bus_name={payload_bus_name}, path={payload_path or '*'}, active bus_names={list(self.subscriptions.keys())}")
812
819
  if payload_property:
813
- logger.info(f"No configured or active dbus subscriptions for topic={msg.topic}, property={payload_property}, active bus_names={list(self.subscriptions.keys())}")
814
-
815
- # raw mode, payload contains: bus_name (specific or wildcard), path, interface_name
816
- # topic: dbus2mqtt/raw (with allowlist check)
817
-
818
- # predefined mode with topic matching from configuration
819
- # topic: dbus2mqtt/MediaPlayer/command
820
+ logger.info(f"No configured or active dbus subscriptions for topic={msg.topic}, property={payload_property}, bus_name={payload_bus_name}, path={payload_path or '*'}, active bus_names={list(self.subscriptions.keys())}")
@@ -0,0 +1,36 @@
1
+
2
+ import logging
3
+
4
+ from jinja2.exceptions import TemplateError
5
+
6
+ from dbus2mqtt import AppContext
7
+ from dbus2mqtt.config import FlowActionLogConfig
8
+ from dbus2mqtt.flow import FlowAction, FlowExecutionContext
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class LogAction(FlowAction):
13
+
14
+ def __init__(self, config: FlowActionLogConfig, app_context: AppContext):
15
+ self.config = config
16
+ self.templating = app_context.templating
17
+
18
+ async def execute(self, context: FlowExecutionContext):
19
+
20
+ render_context = context.get_aggregated_context()
21
+
22
+ log_msg = self.config.msg
23
+ log_level = logging._nameToLevel.get(self.config.level.upper(), logging.INFO)
24
+
25
+ try:
26
+ log_msg = await self.templating.async_render_template(
27
+ templatable=self.config.msg,
28
+ context=render_context,
29
+ res_type=str
30
+ )
31
+
32
+ except TemplateError as e:
33
+ logger.warning(f"Error rendering jinja template, flow: '{context.name or ''}', msg={e}, msg={self.config.msg}, render_context={render_context}", exc_info=True)
34
+ return
35
+
36
+ logger.log(level=log_level, msg=log_msg)
@@ -17,6 +17,7 @@ from dbus2mqtt.config import (
17
17
  from dbus2mqtt.event_broker import FlowTriggerMessage
18
18
  from dbus2mqtt.flow import FlowAction, FlowExecutionContext
19
19
  from dbus2mqtt.flow.actions.context_set import ContextSetAction
20
+ from dbus2mqtt.flow.actions.log_action import LogAction
20
21
  from dbus2mqtt.flow.actions.mqtt_publish import MqttPublishAction
21
22
 
22
23
  logger = logging.getLogger(__name__)
@@ -102,8 +103,11 @@ class FlowActionContext:
102
103
  action = None
103
104
  if action_config.type == "context_set":
104
105
  action = ContextSetAction(action_config, self.app_context)
105
- if action_config.type == "mqtt_publish":
106
+ elif action_config.type == "mqtt_publish":
106
107
  action = MqttPublishAction(action_config, self.app_context)
108
+ elif action_config.type == "log":
109
+ action = LogAction(action_config, self.app_context)
110
+
107
111
  if action:
108
112
  res.append(action)
109
113
 
@@ -13,6 +13,8 @@ import paho.mqtt.client as mqtt
13
13
  import yaml
14
14
 
15
15
  from paho.mqtt.enums import CallbackAPIVersion
16
+ from paho.mqtt.packettypes import PacketTypes
17
+ from paho.mqtt.properties import Properties
16
18
  from paho.mqtt.subscribeoptions import SubscribeOptions
17
19
 
18
20
  from dbus2mqtt import AppContext
@@ -27,8 +29,11 @@ class MqttClient:
27
29
  self.event_broker = app_context.event_broker
28
30
 
29
31
  unique_client_id_postfix = ''.join(random.choices(string.ascii_lowercase + string.digits, k=6))
32
+ self.client_id_prefix = "dbus2mqtt-"
33
+ self.client_id = f"{self.client_id_prefix}{unique_client_id_postfix}"
34
+
30
35
  self.client = mqtt.Client(
31
- client_id=f"dbus2mqtt-client-{unique_client_id_postfix}",
36
+ client_id=self.client_id,
32
37
  protocol=mqtt.MQTTv5,
33
38
  callback_api_version=CallbackAPIVersion.VERSION2
34
39
  )
@@ -84,7 +89,16 @@ class MqttClient:
84
89
  if first_message:
85
90
  await asyncio.wait_for(self.connected_event.wait(), timeout=5)
86
91
 
87
- self.client.publish(topic=msg.topic, payload=payload or "").wait_for_publish(timeout=1000)
92
+ publish_properties = Properties(PacketTypes.PUBLISH)
93
+ publish_properties.UserProperty = ("client_id", self.client_id)
94
+
95
+ publish_info = self.client.publish(
96
+ topic=msg.topic,
97
+ payload=payload or "",
98
+ properties=publish_properties
99
+ )
100
+ publish_info.wait_for_publish(timeout=1000)
101
+
88
102
  if first_message:
89
103
  logger.info(f"First message published: topic={msg.topic}, payload={payload_log_msg}")
90
104
  first_message = False
@@ -100,24 +114,31 @@ class MqttClient:
100
114
  logger.warning(f"on_connect: Failed to connect: {reason_code}. Will retry connection")
101
115
  else:
102
116
  logger.info(f"on_connect: Connected to {self.config.host}:{self.config.port}")
103
- # Subscribing in on_connect() means that if we lose the connection and
104
- # reconnect then subscriptions will be renewed.
105
- # TODO: Determine topics based on config
106
- client.subscribe("dbus2mqtt/#", options=SubscribeOptions(noLocal=True))
117
+
118
+ subscriptions = [(t, SubscribeOptions(noLocal=True)) for t in self.config.subscription_topics]
119
+ client.subscribe(subscriptions)
107
120
 
108
121
  self.loop.call_soon_threadsafe(self.connected_event.set)
109
122
 
110
123
  def on_message(self, client: mqtt.Client, userdata: Any, msg: mqtt.MQTTMessage):
111
124
 
112
- # TODO: Skip messages being sent by other dbus2mqtt clients
113
-
125
+ # Skip retained messages
114
126
  payload = msg.payload.decode()
115
127
  if msg.retain:
116
128
  logger.info(f"on_message: skipping msg with retain=True, topic={msg.topic}, payload={payload}")
117
129
  return
118
130
 
131
+ # Skip messages being sent by other dbus2mqtt clients
132
+ if msg.properties:
133
+ user_properties: list[tuple[str, object]] = getattr(msg.properties, "UserProperty", [])
134
+ client_id = next((str(v) for k, v in user_properties if k == "client_id"), None)
135
+ if client_id and client_id != self.client_id:
136
+ logger.info(f"on_message: skipping msg from another dbus2mqtt client, topic={msg.topic}, client_id={client_id}")
137
+ if client_id and client_id.startswith(self.client_id_prefix):
138
+ return
139
+
119
140
  try:
120
- json_payload = json.loads(payload)
141
+ json_payload = json.loads(payload) if payload else {}
121
142
  logger.debug(f"on_message: msg.topic={msg.topic}, msg.payload={json.dumps(json_payload)}")
122
143
  self.event_broker.on_mqtt_receive(MqttMessage(msg.topic, json_payload))
123
144
  except json.JSONDecodeError as e:
@@ -1,3 +1,4 @@
1
+ import urllib.parse
1
2
 
2
3
  from datetime import datetime
3
4
  from typing import Any, TypeVar
@@ -7,11 +8,18 @@ from jinja2.nativetypes import NativeEnvironment
7
8
 
8
9
  TemplateResultType = TypeVar('TemplateResultType')
9
10
 
11
+ def urldecode(string):
12
+ return urllib.parse.unquote(string)
13
+
10
14
  class TemplateEngine:
11
15
  def __init__(self):
12
16
 
13
17
  engine_globals = {}
14
18
  engine_globals['now'] = datetime.now
19
+ engine_globals['urldecode'] = urldecode
20
+
21
+ engine_filters = {}
22
+ engine_filters['urldecode'] = urldecode
15
23
 
16
24
  self.jinja2_env = NativeEnvironment(
17
25
  loader=BaseLoader(),
@@ -32,6 +40,9 @@ class TemplateEngine:
32
40
  self.jinja2_env.globals.update(engine_globals)
33
41
  self.jinja2_async_env.globals.update(engine_globals)
34
42
 
43
+ self.jinja2_env.filters.update(engine_filters)
44
+ self.jinja2_async_env.filters.update(engine_filters)
45
+
35
46
  def add_functions(self, custom_functions: dict[str, Any]):
36
47
  self.jinja2_env.globals.update(custom_functions)
37
48
  self.jinja2_async_env.globals.update(custom_functions)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbus2mqtt
3
- Version: 0.4.0
3
+ Version: 0.4.2
4
4
  Summary: A Python tool to expose Linux D-Bus signals, methods and properties over MQTT - featuring templating, payload enrichment and Home Assistant-ready examples
5
5
  Project-URL: Repository, https://github.com/jwnmulder/dbus2mqtt.git
6
6
  Project-URL: Issues, https://github.com/jwnmulder/dbus2mqtt/issues
@@ -32,8 +32,8 @@ Description-Content-Type: text/markdown
32
32
 
33
33
  # dbus2mqtt
34
34
 
35
- **dbus2mqtt** is a Python application that bridges **Linux D-Bus** with **MQTT**.
36
- It lets you forward D-Bus signals and properties to MQTT topics, call D-Bus methods via MQTT messages, and shape payloads using flexible **Jinja2 templating**.
35
+ **dbus2mqtt** is a Python application that bridges **DBus** with **MQTT**.
36
+ It lets you forward Linux D-Bus signals and properties to MQTT topics, call D-Bus methods via MQTT messages, and shape payloads using flexible **Jinja2 templating**.
37
37
 
38
38
  This makes it easy to integrate Linux desktop services or system signals into MQTT-based workflows - including **Home Assistant**.
39
39
 
@@ -154,6 +154,8 @@ or
154
154
  mqtt:
155
155
  host: localhost
156
156
  port: 1883
157
+ subscription_topics:
158
+ - dbus2mqtt/#
157
159
  ```
158
160
 
159
161
  ### Exposing dbus methods
@@ -173,21 +175,31 @@ dbus:
173
175
 
174
176
  This configuration will expose 2 methods. Triggering methods can be done by publishing json messages to the `dbus2mqtt/org.mpris.MediaPlayer2/command` MQTT topic. Arguments can be passed along in `args`.
175
177
 
176
- Note that methods are called on **all** bus_names matching the configured pattern
178
+ Some examples that call methods on **all** bus_names matching the configured pattern
177
179
 
178
180
  ```json
179
181
  {
180
- "method" : "Play",
182
+ "method": "Play",
181
183
  }
182
184
  ```
183
185
 
184
186
  ```json
185
187
  {
186
- "method" : "OpenUri",
188
+ "method": "OpenUri",
187
189
  "args": []
188
190
  }
189
191
  ```
190
192
 
193
+ To specifically target objects the properties `bus_name` and/or `path` can be used. Both properties support wildcards
194
+
195
+ ```json
196
+ {
197
+ "method": "Play",
198
+ "bus_name": "*.firefox",
199
+ "path": "/org/mpris/MediaPlayer2"
200
+ }
201
+ ```
202
+
191
203
  ### Exposing dbus signals
192
204
 
193
205
  Publishing signals to MQTT topics works by subscribing to the relevant signal and using flows for publishing
@@ -214,7 +226,7 @@ dbus:
214
226
 
215
227
  ## Flows
216
228
 
217
- TODO: Document flows, for now see the [MPRIS to Home Assistant Media Player integration](https://github.com/jwnmulder/dbus2mqtt/blob/main/docs/examples/home_assistant_media_player.md) example
229
+ A reference of all supported flow triggers and actions can be found on [Flows](https://github.com/jwnmulder/dbus2mqtt/blob/main/docs/flows.md)
218
230
 
219
231
  ## Jinja templating
220
232
 
@@ -2,22 +2,23 @@ dbus2mqtt/__init__.py,sha256=VunubKEH4_lne9Ttd2YRpyXjZMIAzyD2eiJ2sfvvTFU,362
2
2
  dbus2mqtt/__main__.py,sha256=NAoa3nwgBSQI22eWzzRx61SIDThDwXmUofWWZU3_4-Q,71
3
3
  dbus2mqtt/event_broker.py,sha256=Hp5yurhP8FkAO-y0l2grygHxR2e37ZQ7QScjcQDA2UU,1334
4
4
  dbus2mqtt/main.py,sha256=Kr2LRVxWcPDtNwCj8Eqz9TxtGLAVrV4q0nizKh1pLXc,4539
5
- dbus2mqtt/config/__init__.py,sha256=LXQg2cZ_vMvhAl7_cC6G3gxCaLCq9uaTfZqw7OEXkPQ,5104
5
+ dbus2mqtt/config/__init__.py,sha256=947_dLVt8qq7P3p15jGIy5Vf1jUUMGrh1xr_DVhZclQ,5372
6
6
  dbus2mqtt/config/jsonarparse.py,sha256=VJGFeyQJcOE6bRXlSRr3FClvN_HnmiIlEGmOgNM0oCc,1214
7
- dbus2mqtt/dbus/dbus_client.py,sha256=0XneV3wulj6d_l4Jq9KIwROC19DLF853u6weCdyxpwA,39222
7
+ dbus2mqtt/dbus/dbus_client.py,sha256=WHsmOiaJ5SY6zk-C99m83MEpycnUyGpsYmGopJJbPE8,39690
8
8
  dbus2mqtt/dbus/dbus_types.py,sha256=NmPD9um499e49Pk8DWH4IrIPQh1BinHYQgoXllCNiDw,777
9
9
  dbus2mqtt/dbus/dbus_util.py,sha256=h-1Y8Mvz9bj9X7mPZ8LghkvXDrujdJHK0__AOW373hE,697
10
10
  dbus2mqtt/dbus/introspection_patches/mpris_playerctl.py,sha256=q93d_Yp93u3Y-9q0dRdKW5hrij9GK3CFqKhUWVE8uw4,5883
11
11
  dbus2mqtt/dbus/introspection_patches/mpris_vlc.py,sha256=Cf-o-05W6gUoKpcYR7n0dRi-CrbeASPTwkyEzZGnU3Y,4241
12
12
  dbus2mqtt/flow/__init__.py,sha256=tAL-CjXQHq_tGTKctIdOZ5teVKBtcJs6Astq_RdruV8,1540
13
- dbus2mqtt/flow/flow_processor.py,sha256=-fJ5JPFcFqz1RokKaqjdHoU-EPg5McAERKYFwIkMzgU,8749
13
+ dbus2mqtt/flow/flow_processor.py,sha256=N-btGap1wqnM4zKyulH_5KQhGi0IRlsK2cHrrnomMQQ,8922
14
14
  dbus2mqtt/flow/actions/context_set.py,sha256=dIT39MJJVb0wuRI_ZM3ssnXYfa-iyB4o_UZD-1BZL2g,1087
15
+ dbus2mqtt/flow/actions/log_action.py,sha256=2_-YEKkX5kvFzK6x4v-Hx3u2PEM8fip_4buMg_ij-oI,1156
15
16
  dbus2mqtt/flow/actions/mqtt_publish.py,sha256=psNkTvaR3JZwAwpM4AqiZTDnA5UQX9r4CUZ1LA7iRW4,2366
16
- dbus2mqtt/mqtt/mqtt_client.py,sha256=0d8XSe4-WxrUBxcUprukwxgzIhnCn1G0fCPnLqPp9ko,5148
17
+ dbus2mqtt/mqtt/mqtt_client.py,sha256=P6rxVNA2M_wxhI3RcHpvayEEmcUQGGDG6hkLmjiqY1w,6056
17
18
  dbus2mqtt/template/dbus_template_functions.py,sha256=UEoXK2PqDKF6jR4vTFHQwq58f5APnOJr7B1_I1zW8yM,2449
18
- dbus2mqtt/template/templating.py,sha256=ZLp1A8PFAs_-Bndx4yGqyppaDfh8marHlK7P3bFInu4,4144
19
- dbus2mqtt-0.4.0.dist-info/METADATA,sha256=enb7xSlQVzVTVxCLshavr5HoV8tNamfd2AOJzgel_Sg,7887
20
- dbus2mqtt-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
- dbus2mqtt-0.4.0.dist-info/entry_points.txt,sha256=pmmacoHCsvTTUB5dIPaY4i0H9gX7BQlpd3rBU-Jbv3A,50
22
- dbus2mqtt-0.4.0.dist-info/licenses/LICENSE,sha256=a4bIEgyA9rrnAfUN90CgbgZ6BQIFHeABkk0JihiBaxM,1074
23
- dbus2mqtt-0.4.0.dist-info/RECORD,,
19
+ dbus2mqtt/template/templating.py,sha256=YzE6-pBoATrV7nXkgxSiLlbxgbC65-2wGiFPHgIDd9g,4470
20
+ dbus2mqtt-0.4.2.dist-info/METADATA,sha256=K8XT049Ryv76ssq7GeUYSRjeZbsWLYSvVoOHrA1bfeY,8105
21
+ dbus2mqtt-0.4.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
22
+ dbus2mqtt-0.4.2.dist-info/entry_points.txt,sha256=pmmacoHCsvTTUB5dIPaY4i0H9gX7BQlpd3rBU-Jbv3A,50
23
+ dbus2mqtt-0.4.2.dist-info/licenses/LICENSE,sha256=a4bIEgyA9rrnAfUN90CgbgZ6BQIFHeABkk0JihiBaxM,1074
24
+ dbus2mqtt-0.4.2.dist-info/RECORD,,