dbus2mqtt 0.1.0__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.

@@ -0,0 +1,129 @@
1
+
2
+ from datetime import datetime
3
+ from typing import Any
4
+
5
+ import yaml
6
+
7
+ from jinja2 import (
8
+ BaseLoader,
9
+ Environment,
10
+ StrictUndefined,
11
+ )
12
+ from yaml import SafeDumper, SafeLoader
13
+
14
+
15
+ def _represent_template_str(dumper: SafeDumper, data: str):
16
+ data = data.replace("{{", "template:{{", 1)
17
+ data = data.replace("}}", "}}:template", 1)
18
+ # return dumper.represent_str(f"template:{data}:template")
19
+ return dumper.represent_str(data)
20
+
21
+ class _CustomSafeLoader(SafeLoader):
22
+ def __init__(self, stream):
23
+ super().__init__(stream)
24
+
25
+ # Disable parsing ISO date strings
26
+ self.add_constructor('tag:yaml.org,2002:timestamp', lambda _l, n: n.value)
27
+
28
+ class _CustomSafeDumper(SafeDumper):
29
+ def __init__(self, stream, **kwargs):
30
+ super().__init__(stream, **kwargs)
31
+ self.add_representer(_TemplatedStr, _represent_template_str)
32
+
33
+ class _TemplatedStr(str):
34
+ """A marker class to force template string formatting in YAML."""
35
+ pass
36
+
37
+ def _mark_templates(obj):
38
+ if isinstance(obj, dict):
39
+ return {k: _mark_templates(v) for k, v in obj.items()}
40
+ elif isinstance(obj, list):
41
+ return [_mark_templates(v) for v in obj]
42
+ elif isinstance(obj, str):
43
+ s = obj.strip()
44
+ if s.startswith("{{") and s.endswith("}}"):
45
+ return _TemplatedStr(obj)
46
+ return obj
47
+
48
+ class TemplateEngine:
49
+ def __init__(self):
50
+
51
+ engine_globals = {}
52
+ engine_globals['now'] = datetime.now
53
+
54
+ self.jinja2_env = Environment(
55
+ loader=BaseLoader(),
56
+ extensions=['jinja2_ansible_filters.AnsibleCoreFiltersExtension'],
57
+ undefined=StrictUndefined,
58
+ keep_trailing_newline=False
59
+ )
60
+
61
+ self.jinja2_async_env = Environment(
62
+ loader=BaseLoader(),
63
+ extensions=['jinja2_ansible_filters.AnsibleCoreFiltersExtension'],
64
+ undefined=StrictUndefined,
65
+ enable_async=True
66
+ )
67
+
68
+ self.app_context: dict[str, Any] = {}
69
+ # self.dbus_context: dict[str, Any] = {}
70
+
71
+ self.jinja2_env.globals.update(engine_globals)
72
+ self.jinja2_async_env.globals.update(engine_globals)
73
+
74
+ def add_functions(self, custom_functions: dict[str, Any]):
75
+ self.jinja2_env.globals.update(custom_functions)
76
+ self.jinja2_async_env.globals.update(custom_functions)
77
+
78
+ def update_app_context(self, context: dict[str, Any]):
79
+ self.app_context.update(context)
80
+
81
+ def _dict_to_templatable_str(self, value: dict[str, Any]) -> str:
82
+ template_str = _mark_templates(value)
83
+ template_str = yaml.dump(template_str, Dumper=_CustomSafeDumper)
84
+ # value= yaml.safe_dump(value, default_style=None)
85
+ # print(f"_dict_to_templatable_str: {value}")
86
+ template_str = template_str.replace("template:{{", "{{").replace("}}:template", "}}")
87
+ # print(value)
88
+ return template_str
89
+
90
+ def _render_result_to_dict(self, value: str) -> dict[str, Any]:
91
+ return yaml.load(value, _CustomSafeLoader)
92
+
93
+ def render_template(self, template: str | dict | None, res_type: type, context: dict[str, Any] = {}) -> Any:
94
+
95
+ if not template:
96
+ return None
97
+
98
+ if res_type not in [dict, str]:
99
+ raise ValueError(f"Unsupported result type: {res_type}")
100
+
101
+ dict_template = isinstance(template, dict)
102
+ if dict_template:
103
+ template = self._dict_to_templatable_str(template)
104
+
105
+ res = self.jinja2_env.from_string(template).render(**context)
106
+
107
+ if res_type is dict:
108
+ res = self._render_result_to_dict(res)
109
+
110
+ return res
111
+
112
+ async def async_render_template(self, template: str | dict | None, res_type: type, context: dict[str, Any] = {}) -> Any:
113
+
114
+ if not template:
115
+ return None
116
+
117
+ if res_type not in [dict, str]:
118
+ raise ValueError(f"Unsupported result type: {res_type}")
119
+
120
+ dict_template = isinstance(template, dict)
121
+ if dict_template:
122
+ template = self._dict_to_templatable_str(template)
123
+
124
+ res = await self.jinja2_async_env.from_string(template).render_async(**context)
125
+
126
+ if res_type is dict:
127
+ res = self._render_result_to_dict(res)
128
+
129
+ return res
@@ -0,0 +1,220 @@
1
+ Metadata-Version: 2.4
2
+ Name: dbus2mqtt
3
+ Version: 0.1.0
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
+ Project-URL: Repository, https://github.com/jwnmulder/dbus2mqtt.git
6
+ Project-URL: Issues, https://github.com/jwnmulder/dbus2mqtt/issues
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: dbus,home-assistant,mpris,mqtt,python
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Python: >=3.12
16
+ Requires-Dist: apscheduler>=3.11.0
17
+ Requires-Dist: colorlog>=6.9.0
18
+ Requires-Dist: dbus-next>=0.2.3
19
+ Requires-Dist: janus>=2.0.0
20
+ Requires-Dist: jinja2-ansible-filters>=1.3.2
21
+ Requires-Dist: jinja2>=3.1.6
22
+ Requires-Dist: jsonargparse>=4.38.0
23
+ Requires-Dist: paho-mqtt>=2.1.0
24
+ Requires-Dist: pydantic>=2.11.3
25
+ Requires-Dist: python-dotenv>=1.1.0
26
+ Requires-Dist: schedule>=1.2.2
27
+ Description-Content-Type: text/markdown
28
+
29
+ # dbus2mqtt
30
+
31
+ > **⚠️ Warning:** This project has no releases yet. Running from source works. Docker images and Python packages are planned but not yet available.
32
+
33
+ **dbus2mqtt** is a Python application that bridges **Linux D-Bus** with **MQTT**.
34
+ 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
+
36
+ This makes it easy to integrate Linux desktop services or system signals into MQTT-based workflows - including **Home Assistant**.
37
+
38
+ ## ✨ Features
39
+
40
+ * 🔗 Forward **D-Bus signals** to MQTT topics.
41
+ * 🧠 Enrich or transform **MQTT payloads** using Jinja2 templates and additional D-Bus calls.
42
+ * ⚡ Trigger message publishing via **signals, timers, property changes, or startup events**.
43
+ * 📡 Expose **D-Bus methods** for remote control via MQTT messages.
44
+ * 🏠 Includes example configurations for **MPRIS** and **Home Assistant Media Player** integration.
45
+
46
+ TODO list
47
+
48
+ * Create a release on PyPI
49
+ * Release a docker image
50
+ * Improve error handling when deleting message with 'retain' set. WARNING:dbus2mqtt.mqtt_client:on_message: Unexpected payload, expecting json, topic=dbus2mqtt/org.mpris.MediaPlayer2/command, payload=, error=Expecting value: line 1 column 1 (char 0)
51
+ * Property set only works the first time, need to restart after which the first set will work again
52
+
53
+ ## Getting started with dbus2mqtt
54
+
55
+ Create a `config.yaml` file with the contents shown below. This configuration will expose all bus properties from the `org.mpris.MediaPlayer2.Player` interface to MQTT on the `dbus2mqtt/org.mpris.MediaPlayer2/state` topic. Have a look at [docs/examples](docs/examples.md) for more examples
56
+
57
+ ```yaml
58
+ dbus:
59
+ subscriptions:
60
+ - bus_name: org.mpris.MediaPlayer2.*
61
+ path: /org/mpris/MediaPlayer2
62
+ interfaces:
63
+ - interface: org.freedesktop.DBus.Properties
64
+ methods:
65
+ - method: GetAll
66
+
67
+ flows:
68
+ - name: "Publish MPRIS state"
69
+ triggers:
70
+ - type: bus_name_added
71
+ - type: schedule
72
+ interval: {seconds: 5}
73
+ actions:
74
+ - type: context_set
75
+ context:
76
+ mpris_bus_name: '{{ dbus_list("org.mpris.MediaPlayer2.*") | first }}'
77
+ path: /org/mpris/MediaPlayer2
78
+ - type: mqtt_publish
79
+ topic: dbus2mqtt/org.mpris.MediaPlayer2/state
80
+ payload_type: json
81
+ payload_template: |
82
+ {{ dbus_call(mpris_bus_name, path, 'org.freedesktop.DBus.Properties', 'GetAll', ['org.mpris.MediaPlayer2.Player']) | to_yaml }}
83
+ ```
84
+
85
+ MQTT connection details can be configured in that same `config.yaml` file or via environment variables. For now create a `.env` file with the following contents.
86
+
87
+ ```bash
88
+ MQTT__HOST=localhost
89
+ MQTT__PORT=1883
90
+ MQTT__USERNAME=
91
+ MQTT__PASSWORD=
92
+ ```
93
+
94
+ ### Running from source
95
+
96
+ To run dbus2mqtt from source (requires uv to be installed)
97
+
98
+ ```bash
99
+ uv run main.py --config config.yaml
100
+ ```
101
+
102
+ ### Run using docker with auto start behavior
103
+
104
+ To build and run dbus2mqtt using Docker with the [home_assistant_media_player.yaml](docs/examples/home_assistant_media_player.yaml) example from this repository
105
+
106
+ ```bash
107
+ # setup configuration
108
+ mkdir -p $HOME/.config/dbus2mqtt
109
+ cp docs/examples/home_assistant_media_player.yaml $HOME/.config/dbus2mqtt/config.yaml
110
+ cp .env.example $HOME/.config/dbus2mqtt/.env
111
+
112
+ # build image
113
+ docker build -t jwnmulder/dbus2mqtt:latest .
114
+
115
+ # run image and automatically start on reboot
116
+ docker run --detach --name dbus2mqtt \
117
+ --volume "$HOME"/.config/dbus2mqtt:"$HOME"/.config/dbus2mqtt \
118
+ --volume /run/user:/run/user \
119
+ --env DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" \
120
+ --env-file "$HOME"/.config/dbus2mqtt/.env \
121
+ --user $(id -u):$(id -g) \
122
+ --privileged \
123
+ --restart unless-stopped \
124
+ jwnmulder/dbus2mqtt \
125
+ --config "$HOME"/.config/dbus2mqtt/config.yaml
126
+
127
+ # view logs
128
+ sudo docker logs dbus2mqtt -f
129
+ ```
130
+
131
+ ## Examples
132
+
133
+ This repository contains some examples under [docs/examples](docs/examples.md). The most complete one being [MPRIS to Home Assistant Media Player integration](docs/examples/home_assistant_media_player.md)
134
+
135
+ ## Configuration reference
136
+
137
+ dbus2mqtt leverages [jsonargparse](https://jsonargparse.readthedocs.io/en/stable/) which allows configuration via either yaml configuration, CLI or environment variables. Until this is fully documented have a look at the examples in this repository.
138
+
139
+ ### MQTT and D-Bus connection details
140
+
141
+ ```bash
142
+ # dbus_next configuration
143
+ export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
144
+
145
+ # dbus2mqtt configuration
146
+ MQTT__HOST=localhost
147
+ MQTT__PORT=1883
148
+ MQTT__USERNAME=
149
+ MQTT__PASSWORD=
150
+ ```
151
+
152
+ or
153
+
154
+ ```yaml
155
+ mqtt:
156
+ host: localhost
157
+ port: 1883
158
+ ```
159
+
160
+ ### Exposing dbus methods
161
+
162
+ ```yaml
163
+ dbus:
164
+ subscriptions:
165
+ - bus_name: org.mpris.MediaPlayer2.*
166
+ path: /org/mpris/MediaPlayer2
167
+ interfaces:
168
+ - interface: org.mpris.MediaPlayer2.Player
169
+ mqtt_call_method_topic: dbus2mqtt/org.mpris.MediaPlayer2/command
170
+ methods:
171
+ - method: Pause
172
+ - method: Play
173
+ ```
174
+
175
+ 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`
176
+
177
+ ```json
178
+ {
179
+ "method" : "Play",
180
+ }
181
+ ```
182
+
183
+ ```json
184
+ {
185
+ "method" : "OpenUri",
186
+ "args": []
187
+ }
188
+ ```
189
+
190
+ ### Exposing dbus signals
191
+
192
+ Publishing signals to MQTT topics works by subscribing to the relevant signal and using flows for publishing
193
+
194
+ ```yaml
195
+ dbus:
196
+ subscriptions:
197
+ - bus_name: org.mpris.MediaPlayer2.*
198
+ path: /org/mpris/MediaPlayer2
199
+ interfaces:
200
+ - interface: org.freedesktop.DBus.Properties
201
+ signals:
202
+ - signal: PropertiesChanged
203
+
204
+ flows:
205
+ - name: "Property Changed flow"
206
+ triggers:
207
+ - type: on_signal
208
+ actions:
209
+ - type: mqtt_publish
210
+ topic: dbus2mqtt/org.mpris.MediaPlayer2/signals/PropertiesChanged
211
+ payload_type: json
212
+ ```
213
+
214
+ ## Flows
215
+
216
+ TODO: Document flows, for now see the [MPRIS to Home Assistant Media Player integration](docs/examples/home_assistant_media_player.md) example
217
+
218
+ ## Jinja templating
219
+
220
+ TODO: Document Jinja templating, for now see the [MPRIS to Home Assistant Media Player integration](docs/examples/home_assistant_media_player.md) example
@@ -0,0 +1,20 @@
1
+ dbus2mqtt/__init__.py,sha256=VunubKEH4_lne9Ttd2YRpyXjZMIAzyD2eiJ2sfvvTFU,362
2
+ dbus2mqtt/__main__.py,sha256=NAoa3nwgBSQI22eWzzRx61SIDThDwXmUofWWZU3_4-Q,71
3
+ dbus2mqtt/config.py,sha256=zcRP0Q_PHz4ypOhSvvxwlJyT6C3BE1vpjcbiUqXcXDw,4198
4
+ dbus2mqtt/event_broker.py,sha256=bFxjrQae87XFWmV6OrYcxpiDPrVQEJv0DJucnI7HZFY,1926
5
+ dbus2mqtt/main.py,sha256=3-2rorUesggZ9Gv3FksqZw_wapfjNRvjibaX51mVadA,4281
6
+ dbus2mqtt/dbus/dbus_client.py,sha256=Hoa7p9zUyFCPKQtl90gCG8eBuQ8annrQoAsvjAekEMI,21882
7
+ dbus2mqtt/dbus/dbus_types.py,sha256=bUik8LWPnLLJhhJExPuvyn1_MmkUjTn4jxXh3EkYgzI,495
8
+ dbus2mqtt/dbus/dbus_util.py,sha256=Lk8w57j8TQxUs8d-DnTcB8wLx-cwkzzHtJY3RbTcXbo,570
9
+ dbus2mqtt/flow/__init__.py,sha256=oatdVeBUjAJkUwM9DZsj67Z2rptOHWUn_QFVJrRu2DA,1063
10
+ dbus2mqtt/flow/flow_processor.py,sha256=v5LvcNe-IpEXkWgBW0mK76khrqb5aFkZF0Nw2IvxIEs,7834
11
+ dbus2mqtt/flow/actions/context_set.py,sha256=_XWJ-xHx6nhRYVrd8pBKE3rePc1KL0VU1W0q1pqYRBE,1085
12
+ dbus2mqtt/flow/actions/mqtt_publish.py,sha256=CmW-CPIYjJ0VldcLjbK6uYDkyE65btQzT1797F2F1D0,1650
13
+ dbus2mqtt/mqtt/mqtt_client.py,sha256=FxP82_P5mIbiFk5LOYBPRZN7zYp8XQY7xed2HCVXQoo,4071
14
+ dbus2mqtt/template/dbus_template_functions.py,sha256=mSZr4s7XzmMCYnJYV1MlBWOBz71MGEBj6mLJzIapNf8,2427
15
+ dbus2mqtt/template/templating.py,sha256=aBDLW6RaqiivvWjzu3jhTDdZvtZCWMtxGxm_VCaZDKs,4202
16
+ dbus2mqtt-0.1.0.dist-info/METADATA,sha256=-qhv2PDi4q8xdDT3Gi26cdkL38HnNi026T9zOFIzOKc,7496
17
+ dbus2mqtt-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
+ dbus2mqtt-0.1.0.dist-info/entry_points.txt,sha256=pmmacoHCsvTTUB5dIPaY4i0H9gX7BQlpd3rBU-Jbv3A,50
19
+ dbus2mqtt-0.1.0.dist-info/licenses/LICENSE,sha256=a4bIEgyA9rrnAfUN90CgbgZ6BQIFHeABkk0JihiBaxM,1074
20
+ dbus2mqtt-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ dbus2mqtt = dbus2mqtt.main:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Jan-Willem Mulder
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.