dbus2mqtt 0.1.1__py3-none-any.whl → 0.2.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.

dbus2mqtt/config.py CHANGED
@@ -15,8 +15,9 @@ class SignalConfig:
15
15
  filter: str | None = None
16
16
 
17
17
  def matches_filter(self, template_engine: TemplateEngine, *args) -> bool:
18
- res = template_engine.render_template(self.filter, str, { "args": args })
19
- return res == "True"
18
+ if self.filter:
19
+ return template_engine.render_template(self.filter, bool, { "args": args })
20
+ return True
20
21
 
21
22
  @dataclass
22
23
  class MethodConfig:
@@ -29,13 +30,15 @@ class PropertyConfig:
29
30
  @dataclass
30
31
  class InterfaceConfig:
31
32
  interface: str
32
- mqtt_call_method_topic: str | None = None
33
+ mqtt_command_topic: str | None = None
33
34
  signals: list[SignalConfig] = field(default_factory=list)
34
35
  methods: list[MethodConfig] = field(default_factory=list)
35
36
  properties: list[PropertyConfig] = field(default_factory=list)
36
37
 
37
- def render_mqtt_call_method_topic(self, template_engine: TemplateEngine, context: dict[str, Any]) -> Any:
38
- return template_engine.render_template(self.mqtt_call_method_topic, str, context)
38
+ def render_mqtt_command_topic(self, template_engine: TemplateEngine, context: dict[str, Any]) -> Any:
39
+ if self.mqtt_command_topic:
40
+ return template_engine.render_template(self.mqtt_command_topic, str, context)
41
+ return None
39
42
 
40
43
  @dataclass
41
44
  class FlowTriggerMqttConfig:
@@ -84,8 +87,6 @@ class FlowActionContextSetConfig:
84
87
  class FlowActionMqttPublishConfig:
85
88
  topic: str
86
89
  payload_template: str | dict[str, Any]
87
- """should be a dict if payload_type is json/yaml
88
- or a string if payload_type is text"""
89
90
  type: Literal["mqtt_publish"] = "mqtt_publish"
90
91
  payload_type: Literal["json", "yaml", "text"] = "json"
91
92
 
@@ -125,7 +126,7 @@ class DbusConfig:
125
126
  def get_subscription_configs(self, bus_name: str, path: str) -> list[SubscriptionConfig]:
126
127
  res: list[SubscriptionConfig] = []
127
128
  for subscription in self.subscriptions:
128
- if fnmatch.fnmatchcase(bus_name, subscription.bus_name) and fnmatch.fnmatchcase(path, subscription.path):
129
+ if fnmatch.fnmatchcase(bus_name, subscription.bus_name) and path == subscription.path:
129
130
  res.append(subscription)
130
131
  return res
131
132
 
@@ -124,7 +124,7 @@ class DbusClient:
124
124
 
125
125
  on_signal_method_name = "on_" + camel_to_snake(signal_config.signal)
126
126
  dbus_signal_state = {
127
- "bus_name_subscriptions": bus_name_subscriptions,
127
+ "bus_name": bus_name,
128
128
  "path": path,
129
129
  "interface_name": interface.name,
130
130
  "subscription_config": subscription_config,
@@ -354,6 +354,8 @@ class DbusClient:
354
354
 
355
355
  async def _handle_on_dbus_signal(self, signal: DbusSignalWithState):
356
356
 
357
+ logger.debug(f"dbus_signal: signal={signal.signal_config.signal}, args={signal.args}, bus_name={signal.bus_name}, path={signal.path}, interface={signal.interface_name}")
358
+
357
359
  for flow in signal.subscription_config.flows:
358
360
  for trigger in flow.triggers:
359
361
  if trigger.type == "dbus_signal" and signal.signal_config.signal == trigger.signal:
@@ -366,7 +368,7 @@ class DbusClient:
366
368
 
367
369
  if matches_filter:
368
370
  context = {
369
- "bus_name": signal.bus_name_subscriptions.bus_name,
371
+ "bus_name": signal.bus_name,
370
372
  "path": signal.path,
371
373
  "interface": signal.interface_name,
372
374
  "args": signal.args
@@ -387,15 +389,15 @@ class DbusClient:
387
389
  for subscription_configs in self.config.subscriptions:
388
390
  for interface_config in subscription_configs.interfaces:
389
391
  # TODO, performance improvement
390
- mqtt_topic = interface_config.render_mqtt_call_method_topic(self.templating, {})
392
+ mqtt_topic = interface_config.render_mqtt_command_topic(self.templating, {})
391
393
  found_matching_topic |= mqtt_topic == msg.topic
392
394
 
393
395
  if not found_matching_topic:
394
396
  return
395
397
 
396
398
  logger.debug(f"on_mqtt_msg: topic={msg.topic}, payload={json.dumps(msg.payload)}")
397
- calls_done: list[str] = []
398
- properties_set: list[str] = []
399
+ matched_method = False
400
+ matched_property = False
399
401
 
400
402
  payload_method = msg.payload.get("method")
401
403
  payload_method_args = msg.payload.get("args") or []
@@ -417,27 +419,27 @@ class DbusClient:
417
419
  # filter configured method, configured topic, ...
418
420
  if method.method == payload_method:
419
421
  interface = proxy_object.get_interface(name=interface_config.interface)
422
+ matched_method = True
420
423
 
421
424
  try:
422
425
  logger.info(f"on_mqtt_msg: method={method.method}, args={payload_method_args}, bus_name={bus_name}, path={path}, interface={interface_config.interface}")
423
426
  await self.call_dbus_interface_method(interface, method.method, payload_method_args)
424
- calls_done.append(method.method)
425
427
  except Exception as e:
426
- logger.warning(f"on_mqtt_msg: method={method.method}, bus_name={bus_name} failed, exception={e}")
428
+ logger.warning(f"on_mqtt_msg: method={method.method}, args={payload_method_args}, bus_name={bus_name} failed, exception={e}")
427
429
 
428
430
  for property in interface_config.properties:
429
431
  # filter configured property, configured topic, ...
430
432
  if property.property == payload_property:
431
433
  interface = proxy_object.get_interface(name=interface_config.interface)
434
+ matched_property = True
432
435
 
433
436
  try:
434
437
  logger.info(f"on_mqtt_msg: property={property.property}, value={payload_value}, bus_name={bus_name}, path={path}, interface={interface_config.interface}")
435
438
  await self.set_dbus_interface_property(interface, property.property, payload_value)
436
- properties_set.append(property.property)
437
439
  except Exception as e:
438
- logger.warning(f"on_mqtt_msg: property={property.property}, bus_name={bus_name} failed, exception={e}")
440
+ logger.warning(f"on_mqtt_msg: property={property.property}, value={payload_value}, bus_name={bus_name} failed, exception={e}")
439
441
 
440
- if len(calls_done) == 0 and len(properties_set) == 0:
442
+ if not matched_method and not matched_property:
441
443
  if payload_method:
442
444
  logger.info(f"No configured or active dbus subscriptions for topic={msg.topic}, method={payload_method}, active bus_names={list(self.subscriptions.keys())}")
443
445
  if payload_property:
@@ -15,7 +15,7 @@ def unwrap_dbus_object(o):
15
15
  json_obj = json.loads(res)
16
16
  return json_obj
17
17
 
18
- def unwrap_dbus_objects(*args):
18
+ def unwrap_dbus_objects(args):
19
19
  res = [unwrap_dbus_object(o) for o in args]
20
20
  return res
21
21
 
dbus2mqtt/event_broker.py CHANGED
@@ -13,7 +13,6 @@ from dbus2mqtt.config import (
13
13
  SignalConfig,
14
14
  SubscriptionConfig,
15
15
  )
16
- from dbus2mqtt.dbus.dbus_types import BusNameSubscriptions
17
16
 
18
17
  logger = logging.getLogger(__name__)
19
18
 
@@ -26,7 +25,7 @@ class MqttMessage:
26
25
 
27
26
  @dataclass
28
27
  class DbusSignalWithState:
29
- bus_name_subscriptions: BusNameSubscriptions
28
+ bus_name: str
30
29
  path: str
31
30
  interface_name: str
32
31
  subscription_config: SubscriptionConfig
@@ -6,15 +6,34 @@ class FlowExecutionContext:
6
6
 
7
7
  def __init__(self, name: str | None, global_flows_context: dict[str, Any], flow_context: dict[str, Any]):
8
8
  self.name = name
9
+
9
10
  self.global_flows_context = global_flows_context
11
+ """
12
+ Global flows context which is shared across all flows.
13
+ Modifiable by user.
14
+ **Not** cleaned up after flow execution.
15
+ """
16
+
10
17
  self.flow_context = flow_context
18
+ """
19
+ Flow context which contains flow specific context like 'subscription_bus_name'.
20
+ **Not** modifiable by user.
21
+ **Not** cleaned up after flow execution.
22
+ """
11
23
 
12
- # per flow execution context
13
24
  self.context: dict[str, Any] = {}
25
+ """
26
+ Per flow execution context.
27
+ Modifiable by user.
28
+ Cleaned up after each flow execution
29
+ """
14
30
 
15
31
  def get_aggregated_context(self) -> dict[str, Any]:
16
- """Get the aggregated context for the flow execution."""
17
- # Merge global flows context, flow context, and local context
32
+ """
33
+ Get the aggregated context for the flow execution.
34
+ Merges global flows context, flow context, and local context
35
+ """
36
+
18
37
  context = {}
19
38
  if self.global_flows_context:
20
39
  context.update(self.global_flows_context)
@@ -15,12 +15,14 @@ class ContextSetAction(FlowAction):
15
15
  async def execute(self, context: FlowExecutionContext):
16
16
 
17
17
  aggregated_context = context.get_aggregated_context()
18
+
18
19
  if self.config.global_context:
19
20
  context_new = await self.templating.async_render_template(self.config.global_context, dict, aggregated_context)
20
21
  logger.debug(f"Update global_context with: {context_new}")
21
22
  context.global_flows_context.update(context_new)
22
23
 
23
24
  if self.config.context:
25
+
24
26
  context_new = await self.templating.async_render_template(self.config.context, dict, aggregated_context)
25
27
  logger.debug(f"Update context with: {context_new}")
26
28
  context.context.update(context_new)
@@ -1,7 +1,7 @@
1
1
 
2
2
  import logging
3
3
 
4
- from jinja2.exceptions import TemplateRuntimeError
4
+ from jinja2.exceptions import TemplateError
5
5
 
6
6
  from dbus2mqtt import AppContext
7
7
  from dbus2mqtt.config import FlowActionMqttPublishConfig
@@ -24,14 +24,20 @@ class MqttPublishAction(FlowAction):
24
24
  try:
25
25
  mqtt_topic = await self.templating.async_render_template(self.config.topic, str, render_context)
26
26
 
27
- payload_res_type = str if self.config.payload_type == "text" else dict
28
- payload = await self.templating.async_render_template(self.config.payload_template, payload_res_type, render_context)
27
+ if self.config.payload_type == "text":
28
+ res_type = str
29
+ else:
30
+ res_type = dict
29
31
 
30
- except TemplateRuntimeError as e:
31
- logger.warning(f"Error rendering jinja template, flow: '{context.name}', error: {str(e)}. render_context={render_context}", exc_info=True)
32
+ payload = await self.templating.async_render_template(self.config.payload_template, res_type, render_context)
33
+
34
+ except TemplateError as e:
35
+ logger.warning(f"Error rendering jinja template, flow: '{context.name or ''}', msg={e}, payload_template={self.config.payload_template}, render_context={render_context}", exc_info=True)
32
36
  return
33
37
  except Exception as e:
34
- logger.warning(f"Error rendering jinja template, flow: '{context.name}', error: {str(e)}. render_context={render_context}", exc_info=False)
38
+ # Dont log full exception info to avoid log spamming on dbus errors
39
+ # due to clients disconnecting
40
+ logger.warning(f"Error rendering jinja template, flow: '{context.name or ''}', msg={e} payload_template={self.config.payload_template}, render_context={render_context}")
35
41
  return
36
42
 
37
43
  logger.debug(f"public_mqtt: flow={context.name}, payload={payload}")
dbus2mqtt/main.py CHANGED
@@ -121,15 +121,6 @@ def main():
121
121
  apscheduler_logger = logging.getLogger("apscheduler")
122
122
  apscheduler_logger.setLevel(logging.WARNING)
123
123
 
124
-
125
- # handler.setFormatter(colorlog.ColoredFormatter('%(log_color)s%(levelname)s:%(name)s:%(message)s'))
126
-
127
- # logger = colorlog.getLogger('')
128
- # for handler in logger.handlers:
129
- # print(handler.st)
130
- # if isinstance(handler, colorlog.StreamHandler):
131
- # handler.setFormatter(colorlog.ColoredFormatter('%(log_color)s%(levelname)s:%(name)s:%(message)s'))
132
-
133
124
  logger.debug(f"config: {config}")
134
125
 
135
126
  asyncio.run(run(config))
@@ -43,15 +43,14 @@ class MqttClient:
43
43
  port=self.config.port
44
44
  )
45
45
 
46
- # def on_dbus_signal(self, bus_name: str, path: str, interface: str, signal: str, topic, msg: dict[str, Any]):
47
- # payload = json.dumps(msg)
48
- # logger.debug(f"on_dbus_signal: payload={payload}")
49
- # self.client.publish(topic=topic, payload=payload)
50
-
51
46
  async def mqtt_publish_queue_processor_task(self):
47
+
48
+ first_message = True
49
+
52
50
  """Continuously processes messages from the async queue."""
53
51
  while True:
54
52
  msg = await self.event_broker.mqtt_publish_queue.async_q.get() # Wait for a message
53
+
55
54
  try:
56
55
  payload = msg.payload
57
56
  type = msg.payload_serialization_type
@@ -65,6 +64,11 @@ class MqttClient:
65
64
 
66
65
  logger.debug(f"mqtt_publish_queue_processor_task: payload={payload}")
67
66
  self.client.publish(topic=msg.topic, payload=payload)
67
+
68
+ if first_message:
69
+ logger.info(f"First message published: topic={msg.topic}, payload={payload}")
70
+ first_message = False
71
+
68
72
  except Exception as e:
69
73
  logger.warning(f"mqtt_publish_queue_processor_task: Exception {e}", exc_info=True)
70
74
  finally:
@@ -1,49 +1,11 @@
1
1
 
2
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
3
+ from typing import Any, TypeVar
4
+
5
+ from jinja2 import BaseLoader, StrictUndefined, TemplateError
6
+ from jinja2.nativetypes import NativeEnvironment
7
+
8
+ TemplateResultType = TypeVar('TemplateResultType')
47
9
 
48
10
  class TemplateEngine:
49
11
  def __init__(self):
@@ -51,14 +13,14 @@ class TemplateEngine:
51
13
  engine_globals = {}
52
14
  engine_globals['now'] = datetime.now
53
15
 
54
- self.jinja2_env = Environment(
16
+ self.jinja2_env = NativeEnvironment(
55
17
  loader=BaseLoader(),
56
18
  extensions=['jinja2_ansible_filters.AnsibleCoreFiltersExtension'],
57
19
  undefined=StrictUndefined,
58
20
  keep_trailing_newline=False
59
21
  )
60
22
 
61
- self.jinja2_async_env = Environment(
23
+ self.jinja2_async_env = NativeEnvironment(
62
24
  loader=BaseLoader(),
63
25
  extensions=['jinja2_ansible_filters.AnsibleCoreFiltersExtension'],
64
26
  undefined=StrictUndefined,
@@ -66,7 +28,6 @@ class TemplateEngine:
66
28
  )
67
29
 
68
30
  self.app_context: dict[str, Any] = {}
69
- # self.dbus_context: dict[str, Any] = {}
70
31
 
71
32
  self.jinja2_env.globals.update(engine_globals)
72
33
  self.jinja2_async_env.globals.update(engine_globals)
@@ -78,52 +39,65 @@ class TemplateEngine:
78
39
  def update_app_context(self, context: dict[str, Any]):
79
40
  self.app_context.update(context)
80
41
 
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
42
+ def _convert_value(self, res: Any, res_type: type[TemplateResultType]) -> TemplateResultType:
89
43
 
90
- def _render_result_to_dict(self, value: str) -> dict[str, Any]:
91
- return yaml.load(value, _CustomSafeLoader)
44
+ if isinstance(res, res_type):
45
+ return res
92
46
 
93
- def render_template(self, template: str | dict | None, res_type: type, context: dict[str, Any] = {}) -> Any:
47
+ try:
48
+ return res_type(res) # type: ignore
94
49
 
95
- if not template:
96
- return None
50
+ except Exception as e:
51
+ raise ValueError(f"Error converting rendered template result from '{type(res).__name__}' to '{res_type.__name__}'") from e
97
52
 
98
- if res_type not in [dict, str]:
99
- raise ValueError(f"Unsupported result type: {res_type}")
53
+ def _render_template_nested(self, templatable: str | dict[str, Any], context: dict[str, Any] = {}) -> Any:
100
54
 
101
- dict_template = isinstance(template, dict)
102
- if dict_template:
103
- template = self._dict_to_templatable_str(template)
55
+ if isinstance(templatable, str):
56
+ try:
57
+ return self.jinja2_env.from_string(templatable).render(**context)
58
+ except TemplateError as e:
59
+ raise TemplateError(f"Error compiling template, template={templatable}: {e}") from e
104
60
 
105
- res = self.jinja2_env.from_string(template).render(**context)
61
+ elif isinstance(templatable, dict):
62
+ res = {}
63
+ for k, v in templatable.items():
64
+ if isinstance(v, dict) or isinstance(v, str):
65
+ res[k] = self._render_template_nested(v, context)
66
+ else:
67
+ res[k] = v
68
+ return res
106
69
 
107
- if res_type is dict:
108
- res = self._render_result_to_dict(res)
70
+ def render_template(self, templatable: str | dict[str, Any], res_type: type[TemplateResultType], context: dict[str, Any] = {}) -> TemplateResultType:
109
71
 
110
- return res
72
+ if isinstance(templatable, dict) and res_type is not dict:
73
+ raise ValueError(f"res_type should dict for dictionary templates, templatable={templatable}")
111
74
 
112
- async def async_render_template(self, template: str | dict | None, res_type: type, context: dict[str, Any] = {}) -> Any:
75
+ res = self._render_template_nested(templatable, context)
76
+ res = self._convert_value(res, res_type)
77
+ return res
113
78
 
114
- if not template:
115
- return None
79
+ async def _async_render_template_nested(self, templatable: str | dict[str, Any], context: dict[str, Any] = {}) -> Any:
116
80
 
117
- if res_type not in [dict, str]:
118
- raise ValueError(f"Unsupported result type: {res_type}")
81
+ if isinstance(templatable, str):
82
+ try:
83
+ return await self.jinja2_async_env.from_string(templatable).render_async(**context)
84
+ except TemplateError as e:
85
+ raise TemplateError(f"Error compiling template, template={templatable}: {e}") from e
119
86
 
120
- dict_template = isinstance(template, dict)
121
- if dict_template:
122
- template = self._dict_to_templatable_str(template)
87
+ elif isinstance(templatable, dict):
88
+ res = {}
89
+ for k, v in templatable.items():
90
+ if isinstance(v, dict) or isinstance(v, str):
91
+ res[k] = await self._async_render_template_nested(v, context)
92
+ else:
93
+ res[k] = v
94
+ return res
123
95
 
124
- res = await self.jinja2_async_env.from_string(template).render_async(**context)
96
+ async def async_render_template(self, templatable: str | dict[str, Any], res_type: type[TemplateResultType], context: dict[str, Any] = {}) -> TemplateResultType:
125
97
 
126
- if res_type is dict:
127
- res = self._render_result_to_dict(res)
98
+ if isinstance(templatable, dict) and res_type is not dict:
99
+ raise ValueError(f"res_type should be dict for dictionary templates, templatable={templatable}")
128
100
 
101
+ res = await self._async_render_template_nested(templatable, context)
102
+ res = self._convert_value(res, res_type)
129
103
  return res
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dbus2mqtt
3
- Version: 0.1.1
3
+ Version: 0.2.0
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
@@ -9,6 +9,7 @@ License-File: LICENSE
9
9
  Keywords: dbus,home-assistant,mpris,mqtt,python
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
12
13
  Classifier: Operating System :: POSIX :: Linux
13
14
  Classifier: Programming Language :: Python :: 3
14
15
  Classifier: Programming Language :: Python :: 3.10
@@ -31,8 +32,6 @@ Description-Content-Type: text/markdown
31
32
 
32
33
  # dbus2mqtt
33
34
 
34
- > **⚠️ Warning:** This project has no releases yet. Running from source works. Docker images and Python packages are planned but not yet available.
35
-
36
35
  **dbus2mqtt** is a Python application that bridges **Linux D-Bus** with **MQTT**.
37
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**.
38
37
 
@@ -46,12 +45,11 @@ This makes it easy to integrate Linux desktop services or system signals into MQ
46
45
  * 📡 Expose **D-Bus methods** for remote control via MQTT messages.
47
46
  * 🏠 Includes example configurations for **MPRIS** and **Home Assistant Media Player** integration.
48
47
 
49
- TODO list
48
+ ## Project status
49
+
50
+ **dbus2mqtt** is considered stable for the use-cases it has been tested against, and is actively being developed. Documentation is continuously being improved.
50
51
 
51
- * Create a release on PyPI
52
- * Release a docker image
53
- * 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)
54
- * Property set only works the first time, need to restart after which the first set will work again
52
+ Initial testing has focused on MPRIS integration. A table of tested MPRIS players and their supported methods can be found here: [home_assistant_media_player.md](https://github.com/jwnmulder/dbus2mqtt/blob/main/docs/examples/home_assistant_media_player.md)
55
53
 
56
54
  ## Getting started with dbus2mqtt
57
55
 
@@ -82,7 +80,7 @@ dbus:
82
80
  topic: dbus2mqtt/org.mpris.MediaPlayer2/state
83
81
  payload_type: json
84
82
  payload_template: |
85
- {{ dbus_call(mpris_bus_name, path, 'org.freedesktop.DBus.Properties', 'GetAll', ['org.mpris.MediaPlayer2.Player']) | to_yaml }}
83
+ {{ dbus_call(mpris_bus_name, path, 'org.freedesktop.DBus.Properties', 'GetAll', ['org.mpris.MediaPlayer2.Player']) }}
86
84
  ```
87
85
 
88
86
  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.
@@ -94,17 +92,17 @@ MQTT__USERNAME=
94
92
  MQTT__PASSWORD=
95
93
  ```
96
94
 
97
- ### Running from source
98
-
99
- To run dbus2mqtt from source (requires uv to be installed)
95
+ ### Install and run dbus2mqtt
100
96
 
101
97
  ```bash
102
- uv run main.py --config config.yaml
98
+ python -m pip install dbus2mqtt
99
+ dbus2mqtt --config config.yaml
103
100
  ```
104
101
 
102
+
105
103
  ### Run using docker with auto start behavior
106
104
 
107
- 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
+ To build and run dbus2mqtt using Docker with the [home_assistant_media_player.yaml](https://github.com/jwnmulder/dbus2mqtt/blob/main/docs/examples/home_assistant_media_player.yaml) example from this repository.
108
106
 
109
107
  ```bash
110
108
  # setup configuration
@@ -112,10 +110,8 @@ mkdir -p $HOME/.config/dbus2mqtt
112
110
  cp docs/examples/home_assistant_media_player.yaml $HOME/.config/dbus2mqtt/config.yaml
113
111
  cp .env.example $HOME/.config/dbus2mqtt/.env
114
112
 
115
- # build image
116
- docker build -t jwnmulder/dbus2mqtt:latest .
117
-
118
113
  # run image and automatically start on reboot
114
+ sudo docker pull jwnmulder/dbus2mqtt
119
115
  docker run --detach --name dbus2mqtt \
120
116
  --volume "$HOME"/.config/dbus2mqtt:"$HOME"/.config/dbus2mqtt \
121
117
  --volume /run/user:/run/user \
@@ -133,7 +129,7 @@ sudo docker logs dbus2mqtt -f
133
129
 
134
130
  ## Examples
135
131
 
136
- 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)
132
+ This repository contains examples under [docs/examples](https://github.com/jwnmulder/dbus2mqtt/blob/main//docs/examples.md). The most complete one being [MPRIS to Home Assistant Media Player integration](https://github.com/jwnmulder/dbus2mqtt/blob/main/docs/examples/home_assistant_media_player.md)
137
133
 
138
134
  ## Configuration reference
139
135
 
@@ -169,7 +165,7 @@ dbus:
169
165
  path: /org/mpris/MediaPlayer2
170
166
  interfaces:
171
167
  - interface: org.mpris.MediaPlayer2.Player
172
- mqtt_call_method_topic: dbus2mqtt/org.mpris.MediaPlayer2/command
168
+ mqtt_command_topic: dbus2mqtt/org.mpris.MediaPlayer2/command
173
169
  methods:
174
170
  - method: Pause
175
171
  - method: Play
@@ -216,8 +212,8 @@ dbus:
216
212
 
217
213
  ## Flows
218
214
 
219
- TODO: Document flows, for now see the [MPRIS to Home Assistant Media Player integration](docs/examples/home_assistant_media_player.md) example
215
+ 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
220
216
 
221
217
  ## Jinja templating
222
218
 
223
- TODO: Document Jinja templating, for now see the [MPRIS to Home Assistant Media Player integration](docs/examples/home_assistant_media_player.md) example
219
+ TODO: Document Jinja templating, 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
@@ -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=aTAWR4G1y12-96w1db2rjdCafEy6gF9gAiNFcW3Sz4M,4152
4
+ dbus2mqtt/event_broker.py,sha256=GG7vZZHu08vJgCH5cA-yw3yWU3I2wWHhiEFNMHA4oJk,1836
5
+ dbus2mqtt/main.py,sha256=1zodEH7s-u75eY4fYLSpV-mtcCV_VL38Do2VXLinrh4,3898
6
+ dbus2mqtt/dbus/dbus_client.py,sha256=Y5upYGX84TCZRzJb7cQhxv9irXBfxo6bFmUil-jdlo4,22005
7
+ dbus2mqtt/dbus/dbus_types.py,sha256=bUik8LWPnLLJhhJExPuvyn1_MmkUjTn4jxXh3EkYgzI,495
8
+ dbus2mqtt/dbus/dbus_util.py,sha256=kAqA9SPR1N45fzXeXmBXlhulFEIFKrOIvr_LeygO928,569
9
+ dbus2mqtt/flow/__init__.py,sha256=tAL-CjXQHq_tGTKctIdOZ5teVKBtcJs6Astq_RdruV8,1540
10
+ dbus2mqtt/flow/flow_processor.py,sha256=v5LvcNe-IpEXkWgBW0mK76khrqb5aFkZF0Nw2IvxIEs,7834
11
+ dbus2mqtt/flow/actions/context_set.py,sha256=dIT39MJJVb0wuRI_ZM3ssnXYfa-iyB4o_UZD-1BZL2g,1087
12
+ dbus2mqtt/flow/actions/mqtt_publish.py,sha256=-qQtyc1KspPzAus3AK0iO629RP3RIAMwUecMqqBIrRY,1878
13
+ dbus2mqtt/mqtt/mqtt_client.py,sha256=xUJzkkv2uE3xWy0-SINeRjwfMNnDNLytdxEe3ahIIvk,4006
14
+ dbus2mqtt/template/dbus_template_functions.py,sha256=mSZr4s7XzmMCYnJYV1MlBWOBz71MGEBj6mLJzIapNf8,2427
15
+ dbus2mqtt/template/templating.py,sha256=phmh18uslexw1CmjYHotMHc4zfzIEUflwLnrnBGfAVs,4096
16
+ dbus2mqtt-0.2.0.dist-info/METADATA,sha256=qwjAjBYoDxEsbFkwZXnhEW7hzwXICEd4qFj8sdo8xTw,7750
17
+ dbus2mqtt-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
+ dbus2mqtt-0.2.0.dist-info/entry_points.txt,sha256=pmmacoHCsvTTUB5dIPaY4i0H9gX7BQlpd3rBU-Jbv3A,50
19
+ dbus2mqtt-0.2.0.dist-info/licenses/LICENSE,sha256=a4bIEgyA9rrnAfUN90CgbgZ6BQIFHeABkk0JihiBaxM,1074
20
+ dbus2mqtt-0.2.0.dist-info/RECORD,,
@@ -1,20 +0,0 @@
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.1.dist-info/METADATA,sha256=7dImzxIO65xyS3sJ6qyku2LjbkYNUVHgxNc33MjTwJE,7649
17
- dbus2mqtt-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
- dbus2mqtt-0.1.1.dist-info/entry_points.txt,sha256=pmmacoHCsvTTUB5dIPaY4i0H9gX7BQlpd3rBU-Jbv3A,50
19
- dbus2mqtt-0.1.1.dist-info/licenses/LICENSE,sha256=a4bIEgyA9rrnAfUN90CgbgZ6BQIFHeABkk0JihiBaxM,1074
20
- dbus2mqtt-0.1.1.dist-info/RECORD,,