dbus2mqtt 0.4.1__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
@@ -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,22 +114,29 @@ 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
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)}")
@@ -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.1
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
@@ -224,7 +226,7 @@ dbus:
224
226
 
225
227
  ## Flows
226
228
 
227
- 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)
228
230
 
229
231
  ## Jinja templating
230
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=zWz9V0lbGzbb3AifqWrwkOaC9OdZtQbfHhkSSJfXMi8,39690
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=R0ZNUmOr8Lg8s2TuUcjH3BvEPPlNK731tzO0tNVvqqs,5167
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.1.dist-info/METADATA,sha256=LR9PxxTxJ6TcxHpa8YKg9E6uWRDWYRBbpQ5-o5QGTxQ,8114
20
- dbus2mqtt-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
- dbus2mqtt-0.4.1.dist-info/entry_points.txt,sha256=pmmacoHCsvTTUB5dIPaY4i0H9gX7BQlpd3rBU-Jbv3A,50
22
- dbus2mqtt-0.4.1.dist-info/licenses/LICENSE,sha256=a4bIEgyA9rrnAfUN90CgbgZ6BQIFHeABkk0JihiBaxM,1074
23
- dbus2mqtt-0.4.1.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,,