dbus2mqtt 0.4.4__py3-none-any.whl → 0.5.1__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/__init__.py +32 -15
- dbus2mqtt/config/jsonarparse.py +23 -17
- dbus2mqtt/dbus/dbus_client.py +126 -16
- dbus2mqtt/dbus/dbus_util.py +158 -0
- dbus2mqtt/event_broker.py +7 -3
- dbus2mqtt/flow/flow_processor.py +6 -3
- dbus2mqtt/mqtt/mqtt_client.py +50 -2
- dbus2mqtt/template/dbus_template_functions.py +1 -1
- {dbus2mqtt-0.4.4.dist-info → dbus2mqtt-0.5.1.dist-info}/METADATA +12 -124
- {dbus2mqtt-0.4.4.dist-info → dbus2mqtt-0.5.1.dist-info}/RECORD +13 -13
- {dbus2mqtt-0.4.4.dist-info → dbus2mqtt-0.5.1.dist-info}/WHEEL +0 -0
- {dbus2mqtt-0.4.4.dist-info → dbus2mqtt-0.5.1.dist-info}/entry_points.txt +0 -0
- {dbus2mqtt-0.4.4.dist-info → dbus2mqtt-0.5.1.dist-info}/licenses/LICENSE +0 -0
dbus2mqtt/config/__init__.py
CHANGED
|
@@ -3,9 +3,9 @@ import uuid
|
|
|
3
3
|
import warnings
|
|
4
4
|
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import Any, Literal
|
|
7
7
|
|
|
8
|
-
from
|
|
8
|
+
from jsonargparse.typing import SecretStr
|
|
9
9
|
|
|
10
10
|
from dbus2mqtt.template.templating import TemplateEngine
|
|
11
11
|
|
|
@@ -32,6 +32,7 @@ class PropertyConfig:
|
|
|
32
32
|
class InterfaceConfig:
|
|
33
33
|
interface: str
|
|
34
34
|
mqtt_command_topic: str | None = None
|
|
35
|
+
mqtt_response_topic: str | None = None
|
|
35
36
|
signals: list[SignalConfig] = field(default_factory=list)
|
|
36
37
|
methods: list[MethodConfig] = field(default_factory=list)
|
|
37
38
|
properties: list[PropertyConfig] = field(default_factory=list)
|
|
@@ -41,11 +42,10 @@ class InterfaceConfig:
|
|
|
41
42
|
return template_engine.render_template(self.mqtt_command_topic, str, context)
|
|
42
43
|
return None
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
filter: str | None = None
|
|
45
|
+
def render_mqtt_response_topic(self, template_engine: TemplateEngine, context: dict[str, Any]) -> str | None:
|
|
46
|
+
if self.mqtt_response_topic:
|
|
47
|
+
return template_engine.render_template(self.mqtt_response_topic, str, context)
|
|
48
|
+
return None
|
|
49
49
|
|
|
50
50
|
@dataclass
|
|
51
51
|
class FlowTriggerScheduleConfig:
|
|
@@ -87,10 +87,26 @@ class FlowTriggerObjectRemovedConfig:
|
|
|
87
87
|
type: Literal["object_removed"] = "object_removed"
|
|
88
88
|
# filter: str | None = None
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
]
|
|
90
|
+
@dataclass
|
|
91
|
+
class FlowTriggerMqttMessageConfig:
|
|
92
|
+
topic: str
|
|
93
|
+
type: Literal["mqtt_message"] = "mqtt_message"
|
|
94
|
+
filter: str | None = None
|
|
95
|
+
|
|
96
|
+
def matches_filter(self, template_engine: TemplateEngine, trigger_context: dict[str, Any]) -> bool:
|
|
97
|
+
if self.filter:
|
|
98
|
+
return template_engine.render_template(self.filter, bool, trigger_context)
|
|
99
|
+
return True
|
|
100
|
+
|
|
101
|
+
FlowTriggerConfig = (
|
|
102
|
+
FlowTriggerScheduleConfig
|
|
103
|
+
| FlowTriggerDbusSignalConfig
|
|
104
|
+
| FlowTriggerBusNameAddedConfig
|
|
105
|
+
| FlowTriggerBusNameRemovedConfig
|
|
106
|
+
| FlowTriggerObjectAddedConfig
|
|
107
|
+
| FlowTriggerObjectRemovedConfig
|
|
108
|
+
| FlowTriggerMqttMessageConfig
|
|
109
|
+
)
|
|
94
110
|
|
|
95
111
|
@dataclass
|
|
96
112
|
class FlowActionContextSetConfig:
|
|
@@ -113,10 +129,11 @@ class FlowActionLogConfig:
|
|
|
113
129
|
type: Literal["log"] = "log"
|
|
114
130
|
level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO"
|
|
115
131
|
|
|
116
|
-
FlowActionConfig =
|
|
117
|
-
FlowActionMqttPublishConfig
|
|
118
|
-
|
|
119
|
-
|
|
132
|
+
FlowActionConfig = (
|
|
133
|
+
FlowActionMqttPublishConfig
|
|
134
|
+
| FlowActionContextSetConfig
|
|
135
|
+
| FlowActionLogConfig
|
|
136
|
+
)
|
|
120
137
|
|
|
121
138
|
@dataclass
|
|
122
139
|
class FlowConfig:
|
dbus2mqtt/config/jsonarparse.py
CHANGED
|
@@ -1,32 +1,38 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
1
3
|
import jsonargparse
|
|
2
4
|
|
|
5
|
+
from yaml import YAMLError
|
|
3
6
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
v = stream.strip()
|
|
7
|
+
default_yaml_loader = jsonargparse.get_loader("yaml")
|
|
8
|
+
def _custom_yaml_load(stream: str) -> Any:
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
# Without this, str:"{'PlaybackStatus': 'Off'}" would become dict:{'PlaybackStatus': False}
|
|
10
|
-
if v in ['on', 'On', 'off', 'Off', 'TRUE', 'FALSE', 'True', 'False']:
|
|
11
|
-
return stream
|
|
10
|
+
v = stream.strip()
|
|
12
11
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
if "#" not in first_line and ("{{" in first_line or first_line.startswith("{%")):
|
|
18
|
-
return stream
|
|
12
|
+
# jsonargparse tries to parse yaml 1.1 boolean like values
|
|
13
|
+
# Without this, str:"{'PlaybackStatus': 'Off'}" would become dict:{'PlaybackStatus': False}
|
|
14
|
+
if v in ['on', 'On', 'off', 'Off', 'TRUE', 'FALSE', 'True', 'False']:
|
|
15
|
+
return stream
|
|
19
16
|
|
|
20
17
|
# Delegate to default yaml loader from jsonargparse
|
|
21
|
-
|
|
22
|
-
return yaml_loader(stream)
|
|
18
|
+
return default_yaml_loader(stream)
|
|
23
19
|
|
|
24
20
|
def new_argument_parser() -> jsonargparse.ArgumentParser:
|
|
25
21
|
|
|
26
22
|
# register out custom yaml loader for jsonargparse
|
|
27
|
-
jsonargparse.set_loader(
|
|
23
|
+
jsonargparse.set_loader(
|
|
24
|
+
mode="yaml_custom",
|
|
25
|
+
loader_fn=_custom_yaml_load,
|
|
26
|
+
exceptions=(YAMLError,),
|
|
27
|
+
json_superset=True
|
|
28
|
+
)
|
|
28
29
|
|
|
29
30
|
# unless specified otherwise, load config from config.yaml
|
|
30
|
-
parser = jsonargparse.ArgumentParser(
|
|
31
|
+
parser = jsonargparse.ArgumentParser(
|
|
32
|
+
default_config_files=["config.yaml"],
|
|
33
|
+
default_env=True,
|
|
34
|
+
env_prefix=False,
|
|
35
|
+
parser_mode="yaml_custom"
|
|
36
|
+
)
|
|
31
37
|
|
|
32
38
|
return parser
|
dbus2mqtt/dbus/dbus_client.py
CHANGED
|
@@ -7,11 +7,12 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
import dbus_fast.aio as dbus_aio
|
|
9
9
|
import dbus_fast.constants as dbus_constants
|
|
10
|
-
import dbus_fast.errors as dbus_errors
|
|
11
10
|
import dbus_fast.introspection as dbus_introspection
|
|
12
11
|
import dbus_fast.message as dbus_message
|
|
13
12
|
import janus
|
|
14
13
|
|
|
14
|
+
from dbus_fast import SignatureTree
|
|
15
|
+
|
|
15
16
|
from dbus2mqtt import AppContext
|
|
16
17
|
from dbus2mqtt.config import SubscriptionConfig
|
|
17
18
|
from dbus2mqtt.dbus.dbus_types import (
|
|
@@ -21,6 +22,7 @@ from dbus2mqtt.dbus.dbus_types import (
|
|
|
21
22
|
)
|
|
22
23
|
from dbus2mqtt.dbus.dbus_util import (
|
|
23
24
|
camel_to_snake,
|
|
25
|
+
convert_mqtt_args_to_dbus,
|
|
24
26
|
unwrap_dbus_object,
|
|
25
27
|
unwrap_dbus_objects,
|
|
26
28
|
)
|
|
@@ -28,7 +30,7 @@ from dbus2mqtt.dbus.introspection_patches.mpris_playerctl import (
|
|
|
28
30
|
mpris_introspection_playerctl,
|
|
29
31
|
)
|
|
30
32
|
from dbus2mqtt.dbus.introspection_patches.mpris_vlc import mpris_introspection_vlc
|
|
31
|
-
from dbus2mqtt.event_broker import MqttMessage
|
|
33
|
+
from dbus2mqtt.event_broker import MqttMessage, MqttReceiveHints
|
|
32
34
|
from dbus2mqtt.flow.flow_processor import FlowScheduler, FlowTriggerMessage
|
|
33
35
|
|
|
34
36
|
logger = logging.getLogger(__name__)
|
|
@@ -636,11 +638,19 @@ class DbusClient:
|
|
|
636
638
|
|
|
637
639
|
async def call_dbus_interface_method(self, interface: dbus_aio.proxy_object.ProxyInterface, method: str, method_args: list[Any]):
|
|
638
640
|
|
|
641
|
+
converted_args = convert_mqtt_args_to_dbus(method_args)
|
|
639
642
|
call_method_name = "call_" + camel_to_snake(method)
|
|
643
|
+
|
|
644
|
+
# In case of a payload that doesn't match the dbus signature type, this prints a better error message
|
|
645
|
+
interface_method = next((m for m in interface.introspection.methods if m.name == method), None)
|
|
646
|
+
if interface_method:
|
|
647
|
+
in_signature_tree = SignatureTree(interface_method.in_signature)
|
|
648
|
+
in_signature_tree.verify(converted_args)
|
|
649
|
+
|
|
640
650
|
try:
|
|
641
|
-
res = await interface.__getattribute__(call_method_name)(*
|
|
642
|
-
except
|
|
643
|
-
logger.
|
|
651
|
+
res = await interface.__getattribute__(call_method_name)(*converted_args)
|
|
652
|
+
except Exception as e:
|
|
653
|
+
logger.debug(f"Error while calling dbus object, bus_name={interface.bus_name}, interface={interface.introspection.name}, method={method}, converted_args={converted_args}", exc_info=True)
|
|
644
654
|
raise e
|
|
645
655
|
|
|
646
656
|
if res:
|
|
@@ -672,9 +682,9 @@ class DbusClient:
|
|
|
672
682
|
async def mqtt_receive_queue_processor_task(self):
|
|
673
683
|
"""Continuously processes messages from the async queue."""
|
|
674
684
|
while True:
|
|
675
|
-
msg = await self.event_broker.mqtt_receive_queue.async_q.get() # Wait for a message
|
|
685
|
+
msg, hints = await self.event_broker.mqtt_receive_queue.async_q.get() # Wait for a message
|
|
676
686
|
try:
|
|
677
|
-
await self._on_mqtt_msg(msg)
|
|
687
|
+
await self._on_mqtt_msg(msg, hints)
|
|
678
688
|
except Exception as e:
|
|
679
689
|
logger.warning(f"mqtt_receive_queue_processor_task: Exception {e}", exc_info=True)
|
|
680
690
|
finally:
|
|
@@ -745,7 +755,7 @@ class DbusClient:
|
|
|
745
755
|
path = message.body[0]
|
|
746
756
|
await self._handle_interfaces_removed(bus_name, path)
|
|
747
757
|
|
|
748
|
-
async def _on_mqtt_msg(self, msg: MqttMessage):
|
|
758
|
+
async def _on_mqtt_msg(self, msg: MqttMessage, hints: MqttReceiveHints):
|
|
749
759
|
"""Executes dbus method calls or property updates on objects when messages have
|
|
750
760
|
1. a matching subscription configured
|
|
751
761
|
2. a matching method
|
|
@@ -756,7 +766,6 @@ class DbusClient:
|
|
|
756
766
|
found_matching_topic = False
|
|
757
767
|
for subscription_configs in self.config.subscriptions:
|
|
758
768
|
for interface_config in subscription_configs.interfaces:
|
|
759
|
-
# TODO, performance improvement
|
|
760
769
|
mqtt_topic = interface_config.render_mqtt_command_topic(self.templating, {})
|
|
761
770
|
found_matching_topic |= mqtt_topic == msg.topic
|
|
762
771
|
|
|
@@ -777,7 +786,7 @@ class DbusClient:
|
|
|
777
786
|
payload_value = msg.payload.get("value")
|
|
778
787
|
|
|
779
788
|
if payload_method is None and (payload_property is None or payload_value is None):
|
|
780
|
-
if msg.payload:
|
|
789
|
+
if msg.payload and hints.log_unmatched_message:
|
|
781
790
|
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}")
|
|
782
791
|
return
|
|
783
792
|
|
|
@@ -789,17 +798,32 @@ class DbusClient:
|
|
|
789
798
|
for interface_config in subscription_configs.interfaces:
|
|
790
799
|
|
|
791
800
|
for method in interface_config.methods:
|
|
792
|
-
|
|
793
801
|
# filter configured method, configured topic, ...
|
|
794
802
|
if method.method == payload_method:
|
|
795
803
|
interface = proxy_object.get_interface(name=interface_config.interface)
|
|
796
804
|
matched_method = True
|
|
797
805
|
|
|
806
|
+
result = None
|
|
807
|
+
error = None
|
|
798
808
|
try:
|
|
799
809
|
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)
|
|
810
|
+
result = await self.call_dbus_interface_method(interface, method.method, payload_method_args)
|
|
811
|
+
|
|
812
|
+
# Send response if configured
|
|
813
|
+
await self._send_mqtt_response(
|
|
814
|
+
interface_config, result, None, bus_name, path,
|
|
815
|
+
method=method.method, args=payload_method_args
|
|
816
|
+
)
|
|
817
|
+
|
|
801
818
|
except Exception as e:
|
|
802
|
-
|
|
819
|
+
error = e
|
|
820
|
+
logger.warning(f"on_mqtt_msg: Failed calling method={method.method}, args={payload_method_args}, bus_name={bus_name}, exception={e}")
|
|
821
|
+
|
|
822
|
+
# Send error response if configured
|
|
823
|
+
await self._send_mqtt_response(
|
|
824
|
+
interface_config, None, error, bus_name, path,
|
|
825
|
+
method=method.method, args=payload_method_args
|
|
826
|
+
)
|
|
803
827
|
|
|
804
828
|
for property in interface_config.properties:
|
|
805
829
|
# filter configured property, configured topic, ...
|
|
@@ -810,11 +834,97 @@ class DbusClient:
|
|
|
810
834
|
try:
|
|
811
835
|
logger.info(f"on_mqtt_msg: property={property.property}, value={payload_value}, bus_name={bus_name}, path={path}, interface={interface_config.interface}")
|
|
812
836
|
await self.set_dbus_interface_property(interface, property.property, payload_value)
|
|
837
|
+
|
|
838
|
+
# Send property set response if configured
|
|
839
|
+
await self._send_mqtt_response(
|
|
840
|
+
interface_config, payload_value, None, bus_name, path,
|
|
841
|
+
property=property.property, value=[payload_value]
|
|
842
|
+
)
|
|
843
|
+
|
|
813
844
|
except Exception as e:
|
|
814
845
|
logger.warning(f"on_mqtt_msg: property={property.property}, value={payload_value}, bus_name={bus_name} failed, exception={e}")
|
|
815
846
|
|
|
816
|
-
|
|
847
|
+
# Send property set error response if configured
|
|
848
|
+
await self._send_mqtt_response(
|
|
849
|
+
interface_config, None, e, bus_name, path,
|
|
850
|
+
property=property.property, value=[payload_value],
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
if not matched_method and not matched_property and hints.log_unmatched_message:
|
|
817
854
|
if payload_method:
|
|
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
|
|
855
|
+
logger.info(f"No configured or active dbus subscriptions for topic={msg.topic}, method={payload_method}, bus_name={payload_bus_name}, path={payload_path}, active bus_names={list(self.subscriptions.keys())}")
|
|
819
856
|
if payload_property:
|
|
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
|
|
857
|
+
logger.info(f"No configured or active dbus subscriptions for topic={msg.topic}, property={payload_property}, bus_name={payload_bus_name}, path={payload_path}, active bus_names={list(self.subscriptions.keys())}")
|
|
858
|
+
|
|
859
|
+
async def _send_mqtt_response(self, interface_config, result: Any, error: Exception | None, bus_name: str, path: str, *args, **kwargs):
|
|
860
|
+
"""Send MQTT response for a method call if response topic is configured
|
|
861
|
+
|
|
862
|
+
Args:
|
|
863
|
+
method (str, optional): The method to execute
|
|
864
|
+
args (list, optional): Arguments for the method
|
|
865
|
+
property (str, optional): The property to set
|
|
866
|
+
value (any, optional): The value to set for the property
|
|
867
|
+
"""
|
|
868
|
+
|
|
869
|
+
if not interface_config.mqtt_response_topic:
|
|
870
|
+
return
|
|
871
|
+
|
|
872
|
+
try:
|
|
873
|
+
# Build response context
|
|
874
|
+
response_context = {
|
|
875
|
+
"bus_name": bus_name,
|
|
876
|
+
"path": path,
|
|
877
|
+
"interface": interface_config.interface,
|
|
878
|
+
"timestamp": datetime.now().isoformat()
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
# Check if 'method' and 'args' are provided
|
|
882
|
+
if 'method' in kwargs and 'args' in kwargs:
|
|
883
|
+
method = kwargs['method']
|
|
884
|
+
args = kwargs['args']
|
|
885
|
+
response_context.update({
|
|
886
|
+
"method": method,
|
|
887
|
+
"args": args,
|
|
888
|
+
})
|
|
889
|
+
# Check if 'property' and 'value' are provided
|
|
890
|
+
elif 'property' in kwargs and 'value' in kwargs:
|
|
891
|
+
property = kwargs['property']
|
|
892
|
+
value = kwargs['value']
|
|
893
|
+
response_context.update({
|
|
894
|
+
"property": property,
|
|
895
|
+
"value": value,
|
|
896
|
+
})
|
|
897
|
+
else:
|
|
898
|
+
return "Invalid arguments: Please provide either 'method' and 'args' or 'property' and 'value'"
|
|
899
|
+
|
|
900
|
+
# Add result or error to context
|
|
901
|
+
if error:
|
|
902
|
+
response_context.update({
|
|
903
|
+
"success": False,
|
|
904
|
+
"error": str(error),
|
|
905
|
+
"error_type": error.__class__.__name__
|
|
906
|
+
})
|
|
907
|
+
else:
|
|
908
|
+
response_context.update({
|
|
909
|
+
"success": True,
|
|
910
|
+
"result": result
|
|
911
|
+
})
|
|
912
|
+
|
|
913
|
+
# Render response topic
|
|
914
|
+
response_topic = interface_config.render_mqtt_response_topic(
|
|
915
|
+
self.templating, response_context
|
|
916
|
+
)
|
|
917
|
+
|
|
918
|
+
if response_topic:
|
|
919
|
+
# Send response via MQTT
|
|
920
|
+
response_msg = MqttMessage(
|
|
921
|
+
topic=response_topic,
|
|
922
|
+
payload=response_context,
|
|
923
|
+
payload_serialization_type="json"
|
|
924
|
+
)
|
|
925
|
+
await self.event_broker.publish_to_mqtt(response_msg)
|
|
926
|
+
|
|
927
|
+
logger.debug(f"Sent MQTT response: topic={response_topic}, success={response_context['success']}")
|
|
928
|
+
|
|
929
|
+
except Exception as e:
|
|
930
|
+
logger.warning(f"Failed to send MQTT response: {e}")
|
dbus2mqtt/dbus/dbus_util.py
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import base64
|
|
2
|
+
import logging
|
|
2
3
|
import re
|
|
3
4
|
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
4
7
|
import dbus_fast.signature as dbus_signature
|
|
5
8
|
|
|
9
|
+
from dbus_fast import Variant
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
6
12
|
|
|
7
13
|
def unwrap_dbus_object(obj):
|
|
8
14
|
if isinstance(obj, dict):
|
|
@@ -22,3 +28,155 @@ def unwrap_dbus_objects(args):
|
|
|
22
28
|
|
|
23
29
|
def camel_to_snake(name):
|
|
24
30
|
return re.sub(r'([a-z])([A-Z])', r'\1_\2', name).lower()
|
|
31
|
+
|
|
32
|
+
def _convert_value_to_dbus(value: Any) -> Any:
|
|
33
|
+
"""
|
|
34
|
+
Recursively convert a single value to D-Bus compatible type.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
value: The value to convert (can be dict, list, primitive, etc.)
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
D-Bus compatible value
|
|
41
|
+
"""
|
|
42
|
+
if value is None:
|
|
43
|
+
return value
|
|
44
|
+
|
|
45
|
+
elif isinstance(value, dict):
|
|
46
|
+
# Convert dict to D-Bus dictionary (a{sv} - dictionary of string to variant)
|
|
47
|
+
dbus_dict = {}
|
|
48
|
+
for k, v in value.items():
|
|
49
|
+
# Keys are typically strings in D-Bus dictionaries
|
|
50
|
+
key = str(k)
|
|
51
|
+
# Recursively convert the value first, then wrap in Variant
|
|
52
|
+
converted_value = _convert_value_to_dbus(v)
|
|
53
|
+
# Determine the appropriate D-Bus signature for the converted value
|
|
54
|
+
signature = _get_dbus_signature(converted_value)
|
|
55
|
+
dbus_dict[key] = Variant(signature, converted_value)
|
|
56
|
+
return dbus_dict
|
|
57
|
+
|
|
58
|
+
elif isinstance(value, list):
|
|
59
|
+
# Convert list to D-Bus array
|
|
60
|
+
converted_list = []
|
|
61
|
+
for item in value:
|
|
62
|
+
converted_list.append(_convert_value_to_dbus(item))
|
|
63
|
+
return converted_list
|
|
64
|
+
|
|
65
|
+
elif isinstance(value, bool):
|
|
66
|
+
# Boolean values are fine as-is for D-Bus
|
|
67
|
+
return value
|
|
68
|
+
|
|
69
|
+
elif isinstance(value, int):
|
|
70
|
+
# Integer values are fine as-is for D-Bus
|
|
71
|
+
return value
|
|
72
|
+
|
|
73
|
+
elif isinstance(value, float):
|
|
74
|
+
# Float values are fine as-is for D-Bus
|
|
75
|
+
return value
|
|
76
|
+
|
|
77
|
+
elif isinstance(value, str):
|
|
78
|
+
# String values are fine as-is for D-Bus
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
else:
|
|
82
|
+
# For any other type, try to convert to string as fallback
|
|
83
|
+
logger.warning(f"Unknown type {type(value)} for D-Bus conversion, converting to string: {value}")
|
|
84
|
+
return str(value)
|
|
85
|
+
|
|
86
|
+
def _get_dbus_signature(value: Any) -> str:
|
|
87
|
+
"""
|
|
88
|
+
Get the appropriate D-Bus signature for a value.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
value: The value to get signature for
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
D-Bus type signature string
|
|
95
|
+
"""
|
|
96
|
+
if isinstance(value, bool):
|
|
97
|
+
return 'b' # boolean
|
|
98
|
+
elif isinstance(value, int):
|
|
99
|
+
uint16_min = 0
|
|
100
|
+
uint16_max = 0xFFFF
|
|
101
|
+
int16_min = -0x7FFF - 1
|
|
102
|
+
int16_max = 0x7FFF
|
|
103
|
+
uint32_min = 0
|
|
104
|
+
uint32_max = 0xFFFFFFFF
|
|
105
|
+
int32_min = -0x7FFFFFFF - 1
|
|
106
|
+
int32_max = 0x7FFFFFFF
|
|
107
|
+
uint64_min = 0
|
|
108
|
+
uint64_max = 18446744073709551615
|
|
109
|
+
|
|
110
|
+
if uint16_min <= value <= uint16_max:
|
|
111
|
+
return 'q' # 16-bit unsigned int
|
|
112
|
+
elif int16_min <= value <= int16_max:
|
|
113
|
+
return 'n' # 16-bit signed int
|
|
114
|
+
elif uint32_min <= value <= uint32_max:
|
|
115
|
+
return 'u' # 32-bit unsigned int
|
|
116
|
+
elif int32_min <= value <= int32_max:
|
|
117
|
+
return 'i' # 32-bit signed integer
|
|
118
|
+
elif uint64_min <= value <= uint64_max:
|
|
119
|
+
return 't' # 64-bit unsigned integer
|
|
120
|
+
else:
|
|
121
|
+
return 'x' # 64-bit signed integer
|
|
122
|
+
elif isinstance(value, float):
|
|
123
|
+
return 'd' # double
|
|
124
|
+
elif isinstance(value, str):
|
|
125
|
+
return 's' # string
|
|
126
|
+
elif isinstance(value, list):
|
|
127
|
+
if not value:
|
|
128
|
+
return 'as' # assume array of strings for empty arrays
|
|
129
|
+
# Get signature of first element and assume homogeneous array
|
|
130
|
+
element_sig = _get_dbus_signature(value[0])
|
|
131
|
+
return f'a{element_sig}' # array of elements
|
|
132
|
+
elif isinstance(value, dict):
|
|
133
|
+
return 'a{sv}' # dictionary of string to variant
|
|
134
|
+
else:
|
|
135
|
+
return 's' # fallback to string
|
|
136
|
+
|
|
137
|
+
# Wraps all complex types in VariantList
|
|
138
|
+
def convert_mqtt_args_to_dbus(args: list[Any]) -> list[Any]:
|
|
139
|
+
"""
|
|
140
|
+
Convert MQTT/JSON arguments to D-Bus with explicit Variant wrapping for all complex types.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
args: List of arguments from MQTT
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
List of D-Bus compatible arguments with explicit Variants
|
|
147
|
+
"""
|
|
148
|
+
converted_args = []
|
|
149
|
+
|
|
150
|
+
for arg in args:
|
|
151
|
+
converted_arg = _convert_and_wrap_in_variant(arg)
|
|
152
|
+
converted_args.append(converted_arg)
|
|
153
|
+
|
|
154
|
+
return converted_args
|
|
155
|
+
|
|
156
|
+
def _convert_and_wrap_in_variant(value: Any) -> Any:
|
|
157
|
+
"""
|
|
158
|
+
Convert a value and wrap complex types in Variants.
|
|
159
|
+
"""
|
|
160
|
+
if value is None:
|
|
161
|
+
return value
|
|
162
|
+
elif isinstance(value, bool | int | float | str):
|
|
163
|
+
# Primitive types can be used as-is or wrapped in Variant if needed
|
|
164
|
+
return value
|
|
165
|
+
elif isinstance(value, dict):
|
|
166
|
+
# Convert dict and wrap in Variant
|
|
167
|
+
converted_dict = {}
|
|
168
|
+
for k, v in value.items():
|
|
169
|
+
key = str(k)
|
|
170
|
+
converted_value = _convert_value_to_dbus(v)
|
|
171
|
+
signature = _get_dbus_signature(converted_value)
|
|
172
|
+
converted_dict[key] = Variant(signature, converted_value)
|
|
173
|
+
return converted_dict
|
|
174
|
+
elif isinstance(value, list):
|
|
175
|
+
# Convert list and potentially wrap in Variant
|
|
176
|
+
converted_list = []
|
|
177
|
+
for item in value:
|
|
178
|
+
converted_list.append(_convert_and_wrap_in_variant(item))
|
|
179
|
+
return converted_list
|
|
180
|
+
else:
|
|
181
|
+
# Fallback
|
|
182
|
+
return value
|
dbus2mqtt/event_broker.py
CHANGED
|
@@ -18,6 +18,10 @@ class MqttMessage:
|
|
|
18
18
|
payload: Any
|
|
19
19
|
payload_serialization_type: str = "json"
|
|
20
20
|
|
|
21
|
+
@dataclass
|
|
22
|
+
class MqttReceiveHints:
|
|
23
|
+
log_unmatched_message: bool = True
|
|
24
|
+
|
|
21
25
|
@dataclass
|
|
22
26
|
class FlowTriggerMessage:
|
|
23
27
|
flow_config: FlowConfig
|
|
@@ -27,7 +31,7 @@ class FlowTriggerMessage:
|
|
|
27
31
|
|
|
28
32
|
class EventBroker:
|
|
29
33
|
def __init__(self):
|
|
30
|
-
self.mqtt_receive_queue = janus.Queue[MqttMessage]()
|
|
34
|
+
self.mqtt_receive_queue = janus.Queue[tuple[MqttMessage, MqttReceiveHints]]()
|
|
31
35
|
self.mqtt_publish_queue = janus.Queue[MqttMessage]()
|
|
32
36
|
self.flow_trigger_queue = janus.Queue[FlowTriggerMessage]()
|
|
33
37
|
# self.dbus_send_queue: janus.Queue
|
|
@@ -40,9 +44,9 @@ class EventBroker:
|
|
|
40
44
|
return_exceptions=True
|
|
41
45
|
)
|
|
42
46
|
|
|
43
|
-
def on_mqtt_receive(self, msg: MqttMessage):
|
|
47
|
+
def on_mqtt_receive(self, msg: MqttMessage, hints: MqttReceiveHints):
|
|
44
48
|
# logger.debug("on_mqtt_receive")
|
|
45
|
-
self.mqtt_receive_queue.sync_q.put(msg)
|
|
49
|
+
self.mqtt_receive_queue.sync_q.put((msg, hints))
|
|
46
50
|
|
|
47
51
|
async def publish_to_mqtt(self, msg: MqttMessage):
|
|
48
52
|
# logger.debug("publish_to_mqtt")
|
dbus2mqtt/flow/flow_processor.py
CHANGED
|
@@ -8,6 +8,9 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
|
|
8
8
|
|
|
9
9
|
from dbus2mqtt import AppContext
|
|
10
10
|
from dbus2mqtt.config import (
|
|
11
|
+
FlowActionContextSetConfig,
|
|
12
|
+
FlowActionLogConfig,
|
|
13
|
+
FlowActionMqttPublishConfig,
|
|
11
14
|
FlowConfig,
|
|
12
15
|
FlowTriggerConfig,
|
|
13
16
|
FlowTriggerDbusSignalConfig,
|
|
@@ -101,11 +104,11 @@ class FlowActionContext:
|
|
|
101
104
|
res = []
|
|
102
105
|
for action_config in self.flow_config.actions:
|
|
103
106
|
action = None
|
|
104
|
-
if action_config.type ==
|
|
107
|
+
if action_config.type == FlowActionContextSetConfig.type:
|
|
105
108
|
action = ContextSetAction(action_config, self.app_context)
|
|
106
|
-
elif action_config.type ==
|
|
109
|
+
elif action_config.type == FlowActionMqttPublishConfig.type:
|
|
107
110
|
action = MqttPublishAction(action_config, self.app_context)
|
|
108
|
-
elif action_config.type ==
|
|
111
|
+
elif action_config.type == FlowActionLogConfig.type:
|
|
109
112
|
action = LogAction(action_config, self.app_context)
|
|
110
113
|
|
|
111
114
|
if action:
|
dbus2mqtt/mqtt/mqtt_client.py
CHANGED
|
@@ -5,6 +5,7 @@ import logging
|
|
|
5
5
|
import random
|
|
6
6
|
import string
|
|
7
7
|
|
|
8
|
+
from datetime import datetime
|
|
8
9
|
from typing import Any
|
|
9
10
|
from urllib.parse import ParseResult
|
|
10
11
|
from urllib.request import urlopen
|
|
@@ -18,13 +19,15 @@ from paho.mqtt.properties import Properties
|
|
|
18
19
|
from paho.mqtt.subscribeoptions import SubscribeOptions
|
|
19
20
|
|
|
20
21
|
from dbus2mqtt import AppContext
|
|
21
|
-
from dbus2mqtt.
|
|
22
|
+
from dbus2mqtt.config import FlowConfig, FlowTriggerMqttMessageConfig
|
|
23
|
+
from dbus2mqtt.event_broker import FlowTriggerMessage, MqttMessage, MqttReceiveHints
|
|
22
24
|
|
|
23
25
|
logger = logging.getLogger(__name__)
|
|
24
26
|
|
|
25
27
|
class MqttClient:
|
|
26
28
|
|
|
27
29
|
def __init__(self, app_context: AppContext, loop):
|
|
30
|
+
self.app_context = app_context
|
|
28
31
|
self.config = app_context.config.mqtt
|
|
29
32
|
self.event_broker = app_context.event_broker
|
|
30
33
|
|
|
@@ -140,6 +143,51 @@ class MqttClient:
|
|
|
140
143
|
try:
|
|
141
144
|
json_payload = json.loads(payload) if payload else {}
|
|
142
145
|
logger.debug(f"on_message: msg.topic={msg.topic}, msg.payload={json.dumps(json_payload)}")
|
|
143
|
-
|
|
146
|
+
|
|
147
|
+
# publish to flow trigger queue for any configured mqtt_message triggers
|
|
148
|
+
flow_trigger_messages = self._trigger_flows(msg.topic, {
|
|
149
|
+
"topic": msg.topic,
|
|
150
|
+
"payload": json_payload
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
# publish on a queue that is being processed by dbus_client
|
|
154
|
+
self.event_broker.on_mqtt_receive(
|
|
155
|
+
MqttMessage(msg.topic, json_payload),
|
|
156
|
+
MqttReceiveHints(
|
|
157
|
+
log_unmatched_message=len(flow_trigger_messages) == 0
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
|
|
144
161
|
except json.JSONDecodeError as e:
|
|
145
162
|
logger.warning(f"on_message: Unexpected payload, expecting json, topic={msg.topic}, payload={payload}, error={e}")
|
|
163
|
+
|
|
164
|
+
def _trigger_flows(self, topic: str, trigger_context: dict) -> list[FlowTriggerMessage]:
|
|
165
|
+
"""Triggers all flows that have a mqtt_trigger defined that matches the given topic
|
|
166
|
+
and configured filters."""
|
|
167
|
+
|
|
168
|
+
flow_trigger_messages = []
|
|
169
|
+
|
|
170
|
+
all_flows: list[FlowConfig] = []
|
|
171
|
+
all_flows.extend(self.app_context.config.flows)
|
|
172
|
+
for subscription in self.app_context.config.dbus.subscriptions:
|
|
173
|
+
all_flows.extend(subscription.flows)
|
|
174
|
+
|
|
175
|
+
for flow in all_flows:
|
|
176
|
+
for trigger in flow.triggers:
|
|
177
|
+
if trigger.type == FlowTriggerMqttMessageConfig.type:
|
|
178
|
+
matches_filter = trigger.topic == topic
|
|
179
|
+
if matches_filter and trigger.filter is not None:
|
|
180
|
+
matches_filter = trigger.matches_filter(self.app_context.templating, trigger_context)
|
|
181
|
+
|
|
182
|
+
if matches_filter:
|
|
183
|
+
trigger_message = FlowTriggerMessage(
|
|
184
|
+
flow,
|
|
185
|
+
trigger,
|
|
186
|
+
datetime.now(),
|
|
187
|
+
trigger_context=trigger_context,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
flow_trigger_messages.append(trigger_message)
|
|
191
|
+
self.event_broker.flow_trigger_queue.sync_q.put(trigger_message)
|
|
192
|
+
|
|
193
|
+
return flow_trigger_messages
|
|
@@ -29,7 +29,7 @@ class DbusContext:
|
|
|
29
29
|
async def async_dbus_call_fn(self, bus_name: str, path: str, interface: str, method:str, method_args: list[Any] = []):
|
|
30
30
|
|
|
31
31
|
if not isinstance(method_args, list):
|
|
32
|
-
# Pylance will
|
|
32
|
+
# Pylance will mention this line is unreachable. It is not, jinja2 can pass in any type
|
|
33
33
|
raise ValueError("method_args must be a list")
|
|
34
34
|
|
|
35
35
|
proxy_object = self.dbus_client.get_subscribed_proxy_object(bus_name, path)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dbus2mqtt
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: General purpose DBus to MQTT bridge - expose signals, methods and properties over MQTT - featuring jinja based templating, payload enrichment and MPRIS / BlueZ / Home Assistant ready examples
|
|
5
5
|
Project-URL: Documentation, https://jwnmulder.github.io/dbus2mqtt
|
|
6
6
|
Project-URL: Source, https://github.com/jwnmulder/dbus2mqtt
|
|
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
21
|
Classifier: Topic :: Home Automation
|
|
21
22
|
Requires-Python: >=3.10
|
|
22
23
|
Requires-Dist: apscheduler>=3.11.0
|
|
@@ -27,7 +28,6 @@ Requires-Dist: jinja2-ansible-filters>=1.3.2
|
|
|
27
28
|
Requires-Dist: jinja2>=3.1.6
|
|
28
29
|
Requires-Dist: jsonargparse>=4.38.0
|
|
29
30
|
Requires-Dist: paho-mqtt>=2.1.0
|
|
30
|
-
Requires-Dist: pydantic>=2.11.3
|
|
31
31
|
Requires-Dist: python-dotenv>=1.1.0
|
|
32
32
|
Requires-Dist: pyyaml>=6.0.2
|
|
33
33
|
Description-Content-Type: text/markdown
|
|
@@ -51,7 +51,7 @@ This makes it easy to integrate Linux desktop services or system signals into MQ
|
|
|
51
51
|
|
|
52
52
|
**dbus2mqtt** is considered stable for the use-cases it has been tested against, and is actively being developed. Documentation is continuously being improved.
|
|
53
53
|
|
|
54
|
-
Initial testing has focused on MPRIS integration. A table of tested MPRIS players and their supported methods can be found
|
|
54
|
+
Initial testing has focused on MPRIS integration. A table of tested MPRIS players and their supported methods can be found on [Mediaplayer integration with Home Assistant](https://jwnmulder.github.io/dbus2mqtt/examples/home_assistant_media_player.html)
|
|
55
55
|
|
|
56
56
|
## Getting started with dbus2mqtt
|
|
57
57
|
|
|
@@ -94,7 +94,6 @@ MQTT__USERNAME=
|
|
|
94
94
|
MQTT__PASSWORD=
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
|
|
98
97
|
### Install and run dbus2mqtt
|
|
99
98
|
|
|
100
99
|
```bash
|
|
@@ -102,136 +101,25 @@ python -m pip install dbus2mqtt
|
|
|
102
101
|
dbus2mqtt --config config.yaml
|
|
103
102
|
```
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
### Run using docker with auto start behavior
|
|
107
|
-
|
|
108
|
-
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.
|
|
109
|
-
|
|
110
|
-
```bash
|
|
111
|
-
# setup configuration
|
|
112
|
-
mkdir -p $HOME/.config/dbus2mqtt
|
|
113
|
-
cp docs/examples/home_assistant_media_player.yaml $HOME/.config/dbus2mqtt/config.yaml
|
|
114
|
-
cp .env.example $HOME/.config/dbus2mqtt/.env
|
|
115
|
-
|
|
116
|
-
# run image and automatically start on reboot
|
|
117
|
-
sudo docker pull jwnmulder/dbus2mqtt
|
|
118
|
-
sudo docker run --detach --name dbus2mqtt \
|
|
119
|
-
--volume "$HOME"/.config/dbus2mqtt:"$HOME"/.config/dbus2mqtt \
|
|
120
|
-
--volume /run/user:/run/user \
|
|
121
|
-
--env DBUS_SESSION_BUS_ADDRESS="$DBUS_SESSION_BUS_ADDRESS" \
|
|
122
|
-
--env-file "$HOME"/.config/dbus2mqtt/.env \
|
|
123
|
-
--user $(id -u):$(id -g) \
|
|
124
|
-
--privileged \
|
|
125
|
-
--restart unless-stopped \
|
|
126
|
-
jwnmulder/dbus2mqtt \
|
|
127
|
-
--config "$HOME"/.config/dbus2mqtt/config.yaml
|
|
128
|
-
|
|
129
|
-
# view logs
|
|
130
|
-
sudo docker logs dbus2mqtt -f
|
|
131
|
-
```
|
|
104
|
+
See [setup](https://jwnmulder.github.io/dbus2mqtt/setup.html) for more installation options and configuration details.
|
|
132
105
|
|
|
133
106
|
## Examples
|
|
134
107
|
|
|
135
|
-
More dbus2mqtt examples can be found
|
|
136
|
-
The most complete one being [
|
|
137
|
-
|
|
138
|
-
## Configuration reference
|
|
139
|
-
|
|
140
|
-
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.
|
|
141
|
-
|
|
142
|
-
### MQTT and D-Bus connection details
|
|
143
|
-
|
|
144
|
-
```bash
|
|
145
|
-
# dbus_fast configuration
|
|
146
|
-
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
|
|
147
|
-
|
|
148
|
-
# dbus2mqtt configuration
|
|
149
|
-
MQTT__HOST=localhost
|
|
150
|
-
MQTT__PORT=1883
|
|
151
|
-
MQTT__USERNAME=
|
|
152
|
-
MQTT__PASSWORD=
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
or
|
|
156
|
-
|
|
157
|
-
```yaml
|
|
158
|
-
mqtt:
|
|
159
|
-
host: localhost
|
|
160
|
-
port: 1883
|
|
161
|
-
subscription_topics:
|
|
162
|
-
- dbus2mqtt/#
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### Exposing dbus methods
|
|
166
|
-
|
|
167
|
-
```yaml
|
|
168
|
-
dbus:
|
|
169
|
-
subscriptions:
|
|
170
|
-
- bus_name: org.mpris.MediaPlayer2.*
|
|
171
|
-
path: /org/mpris/MediaPlayer2
|
|
172
|
-
interfaces:
|
|
173
|
-
- interface: org.mpris.MediaPlayer2.Player
|
|
174
|
-
mqtt_command_topic: dbus2mqtt/org.mpris.MediaPlayer2/command
|
|
175
|
-
methods:
|
|
176
|
-
- method: Pause
|
|
177
|
-
- method: Play
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
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`.
|
|
108
|
+
More dbus2mqtt examples can be found in the [examples](https://jwnmulder.github.io/dbus2mqtt/examples/index.html) section.
|
|
109
|
+
The most complete one being [Mediaplayer integration with Home Assistant](https://jwnmulder.github.io/dbus2mqtt/examples/home_assistant_media_player.html)
|
|
181
110
|
|
|
182
|
-
|
|
111
|
+
## Exposing dbus methods, properties and signals
|
|
183
112
|
|
|
184
|
-
|
|
185
|
-
{
|
|
186
|
-
"method": "Play",
|
|
187
|
-
}
|
|
188
|
-
```
|
|
113
|
+
See [subscriptions](https://jwnmulder.github.io/dbus2mqtt/subscriptions.html) for documentation on calling methods, setting properties and exposing D-Bus signals to MQTT. When configured, D-Bus methods can be invoked by publishing a message like
|
|
189
114
|
|
|
190
115
|
```json
|
|
191
116
|
{
|
|
192
|
-
"method": "
|
|
193
|
-
"args": []
|
|
117
|
+
"method": "Play"
|
|
194
118
|
}
|
|
195
119
|
```
|
|
196
120
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
```json
|
|
200
|
-
{
|
|
201
|
-
"method": "Play",
|
|
202
|
-
"bus_name": "*.firefox",
|
|
203
|
-
"path": "/org/mpris/MediaPlayer2"
|
|
204
|
-
}
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### Exposing dbus signals
|
|
208
|
-
|
|
209
|
-
Publishing signals to MQTT topics works by subscribing to the relevant signal and using flows for publishing
|
|
210
|
-
|
|
211
|
-
```yaml
|
|
212
|
-
dbus:
|
|
213
|
-
subscriptions:
|
|
214
|
-
- bus_name: org.mpris.MediaPlayer2.*
|
|
215
|
-
path: /org/mpris/MediaPlayer2
|
|
216
|
-
interfaces:
|
|
217
|
-
- interface: org.freedesktop.DBus.Properties
|
|
218
|
-
signals:
|
|
219
|
-
- signal: PropertiesChanged
|
|
220
|
-
|
|
221
|
-
flows:
|
|
222
|
-
- name: "Property Changed flow"
|
|
223
|
-
triggers:
|
|
224
|
-
- type: on_signal
|
|
225
|
-
actions:
|
|
226
|
-
- type: mqtt_publish
|
|
227
|
-
topic: dbus2mqtt/org.mpris.MediaPlayer2/signals/PropertiesChanged
|
|
228
|
-
payload_type: json
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
## Flows
|
|
232
|
-
|
|
233
|
-
A reference of all supported flow triggers and actions can be found on [Flows](https://jwnmulder.github.io/dbus2mqtt/flows/)
|
|
121
|
+
## Flows and Jinja based templating
|
|
234
122
|
|
|
235
|
-
|
|
123
|
+
For more advanced use-cases, dbus2mqtt has support for flows and Jinja2 based templates. A reference of all supported flow triggers and actions can be found on [flows](https://jwnmulder.github.io/dbus2mqtt/flows/index.html)
|
|
236
124
|
|
|
237
|
-
|
|
125
|
+
Jinja templating documentation can be found here: [templating](https://jwnmulder.github.io/dbus2mqtt/templating/index.html)
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
dbus2mqtt/__init__.py,sha256=VunubKEH4_lne9Ttd2YRpyXjZMIAzyD2eiJ2sfvvTFU,362
|
|
2
2
|
dbus2mqtt/__main__.py,sha256=NAoa3nwgBSQI22eWzzRx61SIDThDwXmUofWWZU3_4-Q,71
|
|
3
|
-
dbus2mqtt/event_broker.py,sha256=
|
|
3
|
+
dbus2mqtt/event_broker.py,sha256=8Iw1PNpH4IxQbpcFtNvPDc8_M8kuGarN6Kvz_a2aFfc,1468
|
|
4
4
|
dbus2mqtt/main.py,sha256=Kr2LRVxWcPDtNwCj8Eqz9TxtGLAVrV4q0nizKh1pLXc,4539
|
|
5
|
-
dbus2mqtt/config/__init__.py,sha256=
|
|
6
|
-
dbus2mqtt/config/jsonarparse.py,sha256
|
|
7
|
-
dbus2mqtt/dbus/dbus_client.py,sha256=
|
|
5
|
+
dbus2mqtt/config/__init__.py,sha256=daqVzuSxNnUNKtMoMYs73AdlkfS6goXUsxF1wnKi7y0,5894
|
|
6
|
+
dbus2mqtt/config/jsonarparse.py,sha256=-wcJW-O-Coqs0uqr5VVvk9mj6DWEm45NylSLkOhCECs,1084
|
|
7
|
+
dbus2mqtt/dbus/dbus_client.py,sha256=PDZiscCF4imn7cxo5bDLmhQ8g9ZL-6RbGu5had1xiso,44832
|
|
8
8
|
dbus2mqtt/dbus/dbus_types.py,sha256=NmPD9um499e49Pk8DWH4IrIPQh1BinHYQgoXllCNiDw,777
|
|
9
|
-
dbus2mqtt/dbus/dbus_util.py,sha256=
|
|
9
|
+
dbus2mqtt/dbus/dbus_util.py,sha256=NUe_9Aohcib_bU8RUa19UPFteDZ0_WsmgCbbnmTUvcY,5814
|
|
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=
|
|
13
|
+
dbus2mqtt/flow/flow_processor.py,sha256=UUdnIeVKXFRTJDBHLein6riHAh7UOKPdT4mTsnAmVV8,9067
|
|
14
14
|
dbus2mqtt/flow/actions/context_set.py,sha256=dIT39MJJVb0wuRI_ZM3ssnXYfa-iyB4o_UZD-1BZL2g,1087
|
|
15
15
|
dbus2mqtt/flow/actions/log_action.py,sha256=2_-YEKkX5kvFzK6x4v-Hx3u2PEM8fip_4buMg_ij-oI,1156
|
|
16
16
|
dbus2mqtt/flow/actions/mqtt_publish.py,sha256=psNkTvaR3JZwAwpM4AqiZTDnA5UQX9r4CUZ1LA7iRW4,2366
|
|
17
|
-
dbus2mqtt/mqtt/mqtt_client.py,sha256=
|
|
18
|
-
dbus2mqtt/template/dbus_template_functions.py,sha256=
|
|
17
|
+
dbus2mqtt/mqtt/mqtt_client.py,sha256=qvhaxtftdE7uS5Vhw-Dvhaicro4pWGtGlk254m2nEtI,8076
|
|
18
|
+
dbus2mqtt/template/dbus_template_functions.py,sha256=oYXJ4HC1XCFGarf_tRzNGhvx2ECXDoT9J4Mz-cxqoJg,2447
|
|
19
19
|
dbus2mqtt/template/templating.py,sha256=QLar09NinZO8rYGbVs9EThX-SOyTeBVCOppueU7VYdo,4483
|
|
20
|
-
dbus2mqtt-0.
|
|
21
|
-
dbus2mqtt-0.
|
|
22
|
-
dbus2mqtt-0.
|
|
23
|
-
dbus2mqtt-0.
|
|
24
|
-
dbus2mqtt-0.
|
|
20
|
+
dbus2mqtt-0.5.1.dist-info/METADATA,sha256=bvfnVoBAYjKCvTYwqyBpVlX98ObW9gjg1bWhYtfUBWg,5393
|
|
21
|
+
dbus2mqtt-0.5.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
22
|
+
dbus2mqtt-0.5.1.dist-info/entry_points.txt,sha256=pmmacoHCsvTTUB5dIPaY4i0H9gX7BQlpd3rBU-Jbv3A,50
|
|
23
|
+
dbus2mqtt-0.5.1.dist-info/licenses/LICENSE,sha256=a4bIEgyA9rrnAfUN90CgbgZ6BQIFHeABkk0JihiBaxM,1074
|
|
24
|
+
dbus2mqtt-0.5.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|