dbus2mqtt 0.3.1__py3-none-any.whl → 0.4.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 +24 -5
- dbus2mqtt/dbus/dbus_client.py +502 -158
- dbus2mqtt/dbus/dbus_types.py +17 -3
- dbus2mqtt/dbus/dbus_util.py +12 -11
- dbus2mqtt/event_broker.py +1 -21
- dbus2mqtt/flow/flow_processor.py +19 -5
- dbus2mqtt/main.py +17 -3
- dbus2mqtt/mqtt/mqtt_client.py +10 -3
- dbus2mqtt/template/dbus_template_functions.py +2 -2
- {dbus2mqtt-0.3.1.dist-info → dbus2mqtt-0.4.1.dist-info}/METADATA +18 -8
- dbus2mqtt-0.4.1.dist-info/RECORD +23 -0
- dbus2mqtt-0.3.1.dist-info/RECORD +0 -23
- {dbus2mqtt-0.3.1.dist-info → dbus2mqtt-0.4.1.dist-info}/WHEEL +0 -0
- {dbus2mqtt-0.3.1.dist-info → dbus2mqtt-0.4.1.dist-info}/entry_points.txt +0 -0
- {dbus2mqtt-0.3.1.dist-info → dbus2mqtt-0.4.1.dist-info}/licenses/LICENSE +0 -0
dbus2mqtt/dbus/dbus_client.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fnmatch
|
|
1
2
|
import json
|
|
2
3
|
import logging
|
|
3
4
|
|
|
@@ -5,11 +6,19 @@ from datetime import datetime
|
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
7
8
|
import dbus_fast.aio as dbus_aio
|
|
9
|
+
import dbus_fast.constants as dbus_constants
|
|
10
|
+
import dbus_fast.errors as dbus_errors
|
|
8
11
|
import dbus_fast.introspection as dbus_introspection
|
|
12
|
+
import dbus_fast.message as dbus_message
|
|
13
|
+
import janus
|
|
9
14
|
|
|
10
15
|
from dbus2mqtt import AppContext
|
|
11
|
-
from dbus2mqtt.config import
|
|
12
|
-
from dbus2mqtt.dbus.dbus_types import
|
|
16
|
+
from dbus2mqtt.config import SubscriptionConfig
|
|
17
|
+
from dbus2mqtt.dbus.dbus_types import (
|
|
18
|
+
BusNameSubscriptions,
|
|
19
|
+
DbusSignalWithState,
|
|
20
|
+
SubscribedInterface,
|
|
21
|
+
)
|
|
13
22
|
from dbus2mqtt.dbus.dbus_util import (
|
|
14
23
|
camel_to_snake,
|
|
15
24
|
unwrap_dbus_object,
|
|
@@ -19,11 +28,13 @@ from dbus2mqtt.dbus.introspection_patches.mpris_playerctl import (
|
|
|
19
28
|
mpris_introspection_playerctl,
|
|
20
29
|
)
|
|
21
30
|
from dbus2mqtt.dbus.introspection_patches.mpris_vlc import mpris_introspection_vlc
|
|
22
|
-
from dbus2mqtt.event_broker import
|
|
31
|
+
from dbus2mqtt.event_broker import MqttMessage
|
|
23
32
|
from dbus2mqtt.flow.flow_processor import FlowScheduler, FlowTriggerMessage
|
|
24
33
|
|
|
25
34
|
logger = logging.getLogger(__name__)
|
|
26
35
|
|
|
36
|
+
# TODO: Redo flow registration in _handle_bus_name_added, might want to move that to a separate file
|
|
37
|
+
# TODO: deregister signal watcher on shutdown
|
|
27
38
|
|
|
28
39
|
class DbusClient:
|
|
29
40
|
|
|
@@ -36,6 +47,13 @@ class DbusClient:
|
|
|
36
47
|
self.flow_scheduler = flow_scheduler
|
|
37
48
|
self.subscriptions: dict[str, BusNameSubscriptions] = {}
|
|
38
49
|
|
|
50
|
+
self._dbus_signal_queue = janus.Queue[DbusSignalWithState]()
|
|
51
|
+
self._dbus_object_lifecycle_signal_queue = janus.Queue[dbus_message.Message]()
|
|
52
|
+
|
|
53
|
+
self._name_owner_match_rule = "sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',path='/org/freedesktop/DBus',member='NameOwnerChanged'"
|
|
54
|
+
self._interfaces_added_match_rule = "interface='org.freedesktop.DBus.ObjectManager',type='signal',member='InterfacesAdded'"
|
|
55
|
+
self._interfaces_removed_match_rule = "interface='org.freedesktop.DBus.ObjectManager',type='signal',member='InterfacesRemoved'"
|
|
56
|
+
|
|
39
57
|
async def connect(self):
|
|
40
58
|
|
|
41
59
|
if not self.bus.connected:
|
|
@@ -46,37 +64,124 @@ class DbusClient:
|
|
|
46
64
|
else:
|
|
47
65
|
logger.warning(f"Failed to connect to {self.bus._bus_address}")
|
|
48
66
|
|
|
67
|
+
self.bus.add_message_handler(self.object_lifecycle_signal_handler)
|
|
68
|
+
|
|
69
|
+
# Add dbus match rules to get notified of new bus_names or interfaces
|
|
70
|
+
await self._add_match_rule(self._name_owner_match_rule)
|
|
71
|
+
await self._add_match_rule(self._interfaces_added_match_rule)
|
|
72
|
+
await self._add_match_rule(self._interfaces_removed_match_rule)
|
|
73
|
+
|
|
49
74
|
introspection = await self.bus.introspect('org.freedesktop.DBus', '/org/freedesktop/DBus')
|
|
50
75
|
obj = self.bus.get_proxy_object('org.freedesktop.DBus', '/org/freedesktop/DBus', introspection)
|
|
51
76
|
dbus_interface = obj.get_interface('org.freedesktop.DBus')
|
|
52
77
|
|
|
53
|
-
# subscribe to NameOwnerChanged which allows us to detect new bus_names
|
|
54
|
-
dbus_interface.__getattribute__("on_name_owner_changed")(self._dbus_name_owner_changed_callback)
|
|
55
|
-
|
|
56
78
|
# subscribe to existing registered bus_names we are interested in
|
|
57
79
|
connected_bus_names = await dbus_interface.__getattribute__("call_list_names")()
|
|
58
80
|
|
|
59
|
-
|
|
81
|
+
new_subscribed_interfaces: list[SubscribedInterface] = []
|
|
60
82
|
for bus_name in connected_bus_names:
|
|
61
|
-
|
|
83
|
+
new_subscribed_interfaces.extend(await self._handle_bus_name_added(bus_name))
|
|
84
|
+
|
|
85
|
+
logger.info(f"subscriptions on startup: {list(set([si.bus_name for si in new_subscribed_interfaces]))}")
|
|
86
|
+
|
|
87
|
+
async def _add_match_rule(self, match_rule: str):
|
|
88
|
+
reply = await self.bus.call(dbus_message.Message(
|
|
89
|
+
destination='org.freedesktop.DBus',
|
|
90
|
+
path='/org/freedesktop/DBus',
|
|
91
|
+
interface='org.freedesktop.DBus',
|
|
92
|
+
member='AddMatch',
|
|
93
|
+
signature='s',
|
|
94
|
+
body=[(match_rule)]
|
|
95
|
+
))
|
|
96
|
+
assert reply and reply.message_type == dbus_constants.MessageType.METHOD_RETURN
|
|
97
|
+
|
|
98
|
+
async def _remove_match_rule(self, match_rule: str):
|
|
99
|
+
reply = await self.bus.call(dbus_message.Message(
|
|
100
|
+
destination='org.freedesktop.DBus',
|
|
101
|
+
path='/org/freedesktop/DBus',
|
|
102
|
+
interface='org.freedesktop.DBus',
|
|
103
|
+
member='RemoveMatch',
|
|
104
|
+
signature='s',
|
|
105
|
+
body=[(match_rule)]
|
|
106
|
+
))
|
|
107
|
+
assert reply and reply.message_type == dbus_constants.MessageType.METHOD_RETURN
|
|
108
|
+
|
|
109
|
+
def get_well_known_bus_name(self, unique_bus_name: str) -> str:
|
|
110
|
+
|
|
111
|
+
for bns in self.subscriptions.values():
|
|
112
|
+
if unique_bus_name == bns.unique_name:
|
|
113
|
+
return bns.bus_name
|
|
114
|
+
|
|
115
|
+
# # dbus_fast keeps track of well known bus_names for the high-level API.
|
|
116
|
+
# # We can use this to find the bus_name for the sender.
|
|
117
|
+
# for k, v in self.bus._name_owners.items():
|
|
118
|
+
# if v == unique_bus_name:
|
|
119
|
+
# return v
|
|
120
|
+
|
|
121
|
+
return unique_bus_name
|
|
122
|
+
|
|
123
|
+
async def get_unique_name(self, name) -> str | None:
|
|
124
|
+
|
|
125
|
+
if name.startswith(":"):
|
|
126
|
+
return name
|
|
127
|
+
|
|
128
|
+
introspect = await self.bus.introspect("org.freedesktop.DBus", "/org/freedesktop/DBus")
|
|
129
|
+
proxy = self.bus.get_proxy_object("org.freedesktop.DBus", "/org/freedesktop/DBus", introspect)
|
|
130
|
+
dbus_interface = proxy.get_interface("org.freedesktop.DBus")
|
|
131
|
+
|
|
132
|
+
return await dbus_interface.call_get_name_owner(name) # type: ignore
|
|
133
|
+
|
|
134
|
+
def object_lifecycle_signal_handler(self, message: dbus_message.Message) -> None:
|
|
135
|
+
|
|
136
|
+
if not message.message_type == dbus_constants.MessageType.SIGNAL:
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
logger.debug(f'object_lifecycle_signal_handler: interface={message.interface}, member={message.member}, body={message.body}')
|
|
62
140
|
|
|
63
|
-
|
|
141
|
+
if message.interface in ['org.freedesktop.DBus', 'org.freedesktop.DBus.ObjectManager']:
|
|
142
|
+
self._dbus_object_lifecycle_signal_queue.sync_q.put(message)
|
|
64
143
|
|
|
65
|
-
def
|
|
144
|
+
def get_bus_name_subscriptions(self, bus_name: str) -> BusNameSubscriptions | None:
|
|
66
145
|
|
|
67
|
-
|
|
146
|
+
return self.subscriptions.get(bus_name)
|
|
147
|
+
|
|
148
|
+
def get_subscribed_proxy_object(self, bus_name: str, path: str) -> dbus_aio.proxy_object.ProxyObject | None:
|
|
149
|
+
|
|
150
|
+
bus_name_subscriptions = self.get_bus_name_subscriptions(bus_name)
|
|
68
151
|
if bus_name_subscriptions:
|
|
69
152
|
proxy_object = bus_name_subscriptions.path_objects.get(path)
|
|
70
153
|
if proxy_object:
|
|
71
154
|
return proxy_object
|
|
72
155
|
|
|
156
|
+
async def get_subscribed_or_new_proxy_object(self, bus_name: str, path: str) -> dbus_aio.proxy_object.ProxyObject | None:
|
|
157
|
+
|
|
158
|
+
proxy_object = self.get_subscribed_proxy_object(bus_name, path)
|
|
159
|
+
if proxy_object:
|
|
160
|
+
return proxy_object
|
|
161
|
+
|
|
162
|
+
# No existing subscription that contains the requested proxy_object
|
|
163
|
+
logger.warning(f"Returning temporary proxy_object with an additional introspection call, bus_name={bus_name}, path={path}")
|
|
164
|
+
introspection = await self.bus.introspect(bus_name=bus_name, path=path)
|
|
165
|
+
proxy_object = self.bus.get_proxy_object(bus_name, path, introspection)
|
|
166
|
+
if proxy_object:
|
|
167
|
+
return proxy_object
|
|
168
|
+
|
|
73
169
|
return None
|
|
74
170
|
|
|
75
|
-
def
|
|
171
|
+
async def _create_proxy_object_subscription(self, bus_name: str, path: str, introspection: dbus_introspection.Node):
|
|
76
172
|
|
|
77
|
-
bus_name_subscriptions = self.
|
|
173
|
+
bus_name_subscriptions = self.get_bus_name_subscriptions(bus_name)
|
|
78
174
|
if not bus_name_subscriptions:
|
|
79
|
-
|
|
175
|
+
|
|
176
|
+
if bus_name.startswith(":"):
|
|
177
|
+
unique_name = bus_name
|
|
178
|
+
else:
|
|
179
|
+
# make sure we have both the well known and unique bus_name
|
|
180
|
+
unique_name = await self.get_unique_name(bus_name)
|
|
181
|
+
|
|
182
|
+
assert unique_name is not None
|
|
183
|
+
|
|
184
|
+
bus_name_subscriptions = BusNameSubscriptions(bus_name, unique_name)
|
|
80
185
|
self.subscriptions[bus_name] = bus_name_subscriptions
|
|
81
186
|
|
|
82
187
|
proxy_object = bus_name_subscriptions.path_objects.get(path)
|
|
@@ -87,10 +192,25 @@ class DbusClient:
|
|
|
87
192
|
return proxy_object, bus_name_subscriptions
|
|
88
193
|
|
|
89
194
|
def _dbus_fast_signal_publisher(self, dbus_signal_state: dict[str, Any], *args):
|
|
195
|
+
"""publish a dbus signal to the event broker, one for each subscription_config"""
|
|
196
|
+
|
|
90
197
|
unwrapped_args = unwrap_dbus_objects(args)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
198
|
+
|
|
199
|
+
signal_subscriptions = dbus_signal_state["signal_subscriptions"]
|
|
200
|
+
for signal_subscription in signal_subscriptions:
|
|
201
|
+
subscription_config = signal_subscription["subscription_config"]
|
|
202
|
+
signal_config = signal_subscription["signal_config"]
|
|
203
|
+
|
|
204
|
+
self._dbus_signal_queue.sync_q.put(
|
|
205
|
+
DbusSignalWithState(
|
|
206
|
+
bus_name=dbus_signal_state["bus_name"],
|
|
207
|
+
path=dbus_signal_state["path"],
|
|
208
|
+
interface_name=dbus_signal_state["interface_name"],
|
|
209
|
+
subscription_config=subscription_config,
|
|
210
|
+
signal_config=signal_config,
|
|
211
|
+
args=unwrapped_args
|
|
212
|
+
)
|
|
213
|
+
)
|
|
94
214
|
|
|
95
215
|
def _dbus_fast_signal_handler(self, signal: dbus_introspection.Signal, state: dict[str, Any]) -> Any:
|
|
96
216
|
expected_args = len(signal.args)
|
|
@@ -105,58 +225,81 @@ class DbusClient:
|
|
|
105
225
|
return lambda a, b, c, d: self._dbus_fast_signal_publisher(state, a, b, c, d)
|
|
106
226
|
raise ValueError("Unsupported nr of arguments")
|
|
107
227
|
|
|
108
|
-
async def
|
|
228
|
+
async def _subscribe_interface_signals(self, bus_name: str, path: str, interface: dbus_introspection.Interface, configured_signals: dict[str, list[dict]]) -> int:
|
|
229
|
+
|
|
230
|
+
proxy_object = self.get_subscribed_proxy_object(bus_name, path)
|
|
231
|
+
assert proxy_object is not None
|
|
109
232
|
|
|
110
|
-
proxy_object, bus_name_subscriptions = self._ensure_proxy_object_subscription(bus_name, path, introspection)
|
|
111
233
|
obj_interface = proxy_object.get_interface(interface.name)
|
|
112
234
|
|
|
113
235
|
interface_signals = dict((s.name, s) for s in interface.signals)
|
|
114
236
|
|
|
115
237
|
logger.debug(f"subscribe: bus_name={bus_name}, path={path}, interface={interface.name}, proxy_interface: signals={list(interface_signals.keys())}")
|
|
238
|
+
signal_subscription_count = 0
|
|
116
239
|
|
|
117
|
-
for
|
|
118
|
-
interface_signal = interface_signals.get(
|
|
240
|
+
for signal, signal_subscriptions in configured_signals.items():
|
|
241
|
+
interface_signal = interface_signals.get(signal)
|
|
119
242
|
if interface_signal:
|
|
120
243
|
|
|
121
|
-
on_signal_method_name = "on_" + camel_to_snake(
|
|
244
|
+
on_signal_method_name = "on_" + camel_to_snake(signal)
|
|
122
245
|
dbus_signal_state = {
|
|
123
246
|
"bus_name": bus_name,
|
|
124
247
|
"path": path,
|
|
125
248
|
"interface_name": interface.name,
|
|
126
|
-
"
|
|
127
|
-
"signal_config": signal_config,
|
|
249
|
+
"signal_subscriptions": signal_subscriptions
|
|
128
250
|
}
|
|
129
251
|
|
|
130
252
|
handler = self._dbus_fast_signal_handler(interface_signal, dbus_signal_state)
|
|
131
253
|
obj_interface.__getattribute__(on_signal_method_name)(handler)
|
|
132
|
-
logger.info(f"subscribed with signal_handler: signal={
|
|
254
|
+
logger.info(f"subscribed with signal_handler: signal={signal}, bus_name={bus_name}, path={path}, interface={interface.name}")
|
|
255
|
+
|
|
256
|
+
signal_subscription_count += 1
|
|
133
257
|
|
|
134
258
|
else:
|
|
135
|
-
logger.warning(f"Invalid signal: signal={
|
|
259
|
+
logger.warning(f"Invalid signal: signal={signal}, bus_name={bus_name}, path={path}, interface={interface.name}")
|
|
136
260
|
|
|
137
|
-
return
|
|
138
|
-
bus_name=bus_name,
|
|
139
|
-
path=path,
|
|
140
|
-
interface_name=interface.name,
|
|
141
|
-
subscription_config=subscription_config,
|
|
142
|
-
interface_config=si
|
|
143
|
-
)
|
|
261
|
+
return signal_subscription_count
|
|
144
262
|
|
|
145
263
|
async def _process_interface(self, bus_name: str, path: str, introspection: dbus_introspection.Node, interface: dbus_introspection.Interface) -> list[SubscribedInterface]:
|
|
146
264
|
|
|
147
265
|
logger.debug(f"process_interface: {bus_name}, {path}, {interface.name}")
|
|
148
|
-
|
|
266
|
+
|
|
149
267
|
new_subscriptions: list[SubscribedInterface] = []
|
|
268
|
+
configured_signals: dict[str, list[dict[str, Any]]] = {}
|
|
269
|
+
|
|
270
|
+
subscription_configs = self.config.get_subscription_configs(bus_name, path)
|
|
150
271
|
for subscription in subscription_configs:
|
|
151
272
|
logger.debug(f"processing subscription config: {subscription.bus_name}, {subscription.path}")
|
|
152
273
|
for subscription_interface in subscription.interfaces:
|
|
153
274
|
if subscription_interface.interface == interface.name:
|
|
154
275
|
logger.debug(f"matching config found for bus_name={bus_name}, path={path}, interface={interface.name}")
|
|
155
|
-
subscribed_iterface = await self._subscribe_interface(bus_name, path, introspection, interface, subscription, subscription_interface)
|
|
156
276
|
|
|
157
|
-
|
|
277
|
+
# Determine signals we need to subscribe to
|
|
278
|
+
for signal_config in subscription_interface.signals:
|
|
279
|
+
signal_subscriptions = configured_signals.get(signal_config.signal, [])
|
|
280
|
+
signal_subscriptions.append({
|
|
281
|
+
"signal_config": signal_config,
|
|
282
|
+
"subscription_config": subscription
|
|
283
|
+
})
|
|
284
|
+
configured_signals[signal_config.signal] = signal_subscriptions
|
|
285
|
+
|
|
286
|
+
if subscription_interface.signals:
|
|
287
|
+
new_subscriptions.append(SubscribedInterface(
|
|
288
|
+
bus_name=bus_name,
|
|
289
|
+
path=path,
|
|
290
|
+
interface_name=interface.name,
|
|
291
|
+
subscription_config=subscription
|
|
292
|
+
))
|
|
293
|
+
|
|
294
|
+
if len(configured_signals) > 0:
|
|
295
|
+
|
|
296
|
+
signal_subscription_count = await self._subscribe_interface_signals(
|
|
297
|
+
bus_name, path, interface, configured_signals
|
|
298
|
+
)
|
|
299
|
+
if signal_subscription_count > 0:
|
|
300
|
+
return new_subscriptions
|
|
158
301
|
|
|
159
|
-
return
|
|
302
|
+
return []
|
|
160
303
|
|
|
161
304
|
async def _introspect(self, bus_name: str, path: str) -> dbus_introspection.Node:
|
|
162
305
|
|
|
@@ -174,93 +317,107 @@ class DbusClient:
|
|
|
174
317
|
|
|
175
318
|
return introspection
|
|
176
319
|
|
|
177
|
-
async def
|
|
320
|
+
async def _list_bus_name_paths(self, bus_name: str, path: str) -> list[str]:
|
|
321
|
+
"""list all nested paths. Only paths that have interfaces are returned"""
|
|
178
322
|
|
|
179
|
-
|
|
323
|
+
paths: list[str] = []
|
|
180
324
|
|
|
181
325
|
try:
|
|
182
326
|
introspection = await self._introspect(bus_name, path)
|
|
183
327
|
except TypeError as e:
|
|
184
328
|
logger.warning(f"bus.introspect failed, bus_name={bus_name}, path={path}: {e}")
|
|
185
|
-
return
|
|
329
|
+
return paths
|
|
186
330
|
|
|
187
331
|
if len(introspection.nodes) == 0:
|
|
188
332
|
logger.debug(f"leaf node: bus_name={bus_name}, path={path}, is_root={introspection.is_root}, interfaces={[i.name for i in introspection.interfaces]}")
|
|
189
333
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
await self._process_interface(bus_name, path, introspection, interface)
|
|
193
|
-
)
|
|
334
|
+
if len(introspection.interfaces) > 0:
|
|
335
|
+
paths.append(path)
|
|
194
336
|
|
|
195
337
|
for node in introspection.nodes:
|
|
196
338
|
path_seperator = "" if path.endswith('/') else "/"
|
|
197
|
-
|
|
198
|
-
await self.
|
|
339
|
+
paths.extend(
|
|
340
|
+
await self._list_bus_name_paths(bus_name, f"{path}{path_seperator}{node.name}")
|
|
199
341
|
)
|
|
200
342
|
|
|
201
|
-
return
|
|
202
|
-
|
|
203
|
-
async def _handle_bus_name_added(self, bus_name: str) -> list[SubscribedInterface]:
|
|
343
|
+
return paths
|
|
204
344
|
|
|
345
|
+
async def _subscribe_dbus_object(self, bus_name: str, path: str) -> list[SubscribedInterface]:
|
|
346
|
+
"""Subscribes to a dbus object at the given bus_name and path.
|
|
347
|
+
For each matching subscription config, subscribe to all configured interfaces,
|
|
348
|
+
start listening to signals and start/register flows if configured.
|
|
349
|
+
"""
|
|
205
350
|
if not self.config.is_bus_name_configured(bus_name):
|
|
206
351
|
return []
|
|
207
352
|
|
|
208
|
-
|
|
209
|
-
for umh in self.bus._user_message_handlers:
|
|
210
|
-
umh_bus_name = umh.__self__.bus_name
|
|
211
|
-
if umh_bus_name == bus_name:
|
|
212
|
-
logger.warning(f"handle_bus_name_added: {umh_bus_name} already added")
|
|
353
|
+
new_subscriptions: list[SubscribedInterface] = []
|
|
213
354
|
|
|
214
|
-
|
|
355
|
+
try:
|
|
356
|
+
introspection = await self._introspect(bus_name, path)
|
|
357
|
+
except TypeError as e:
|
|
358
|
+
logger.warning(f"bus.introspect failed, bus_name={bus_name}, path={path}: {e}")
|
|
359
|
+
return new_subscriptions
|
|
215
360
|
|
|
216
|
-
|
|
361
|
+
if len(introspection.interfaces) == 0:
|
|
362
|
+
logger.warning(f"Skipping dbus_object subscription, no interfaces found for bus_name={bus_name}, path={path}")
|
|
363
|
+
return new_subscriptions
|
|
217
364
|
|
|
218
|
-
|
|
219
|
-
|
|
365
|
+
interfaces_names = [i.name for i in introspection.interfaces]
|
|
366
|
+
logger.info(f"subscribe_dbus_object: bus_name={bus_name}, path={path}, interfaces={interfaces_names}")
|
|
220
367
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
# e.g.: StartFlows, StopFlows,
|
|
368
|
+
await self._create_proxy_object_subscription(bus_name, path, introspection)
|
|
369
|
+
|
|
370
|
+
for interface in introspection.interfaces:
|
|
371
|
+
new_subscriptions.extend(
|
|
372
|
+
await self._process_interface(bus_name, path, introspection, interface)
|
|
373
|
+
)
|
|
228
374
|
|
|
229
|
-
|
|
375
|
+
return new_subscriptions
|
|
230
376
|
|
|
231
|
-
|
|
232
|
-
if subscription_config.id not in processed_new_subscriptions:
|
|
377
|
+
async def _handle_bus_name_added(self, bus_name: str) -> list[SubscribedInterface]:
|
|
233
378
|
|
|
234
|
-
|
|
235
|
-
self.flow_scheduler.start_flow_set(subscription_config.flows)
|
|
379
|
+
logger.debug(f"_handle_bus_name_added: bus_name={bus_name}")
|
|
236
380
|
|
|
237
|
-
|
|
238
|
-
|
|
381
|
+
if not self.config.is_bus_name_configured(bus_name):
|
|
382
|
+
return []
|
|
239
383
|
|
|
240
|
-
|
|
384
|
+
object_paths = []
|
|
385
|
+
subscription_configs = self.config.get_subscription_configs(bus_name=bus_name)
|
|
386
|
+
for subscription_config in subscription_configs:
|
|
241
387
|
|
|
242
|
-
|
|
388
|
+
# if configured path is not a wildcard, use it
|
|
389
|
+
if "*" not in subscription_config.path:
|
|
390
|
+
object_paths.append(subscription_config.path)
|
|
391
|
+
else:
|
|
392
|
+
# if configured path is a wildcard, use introspection to find all paths
|
|
393
|
+
# and filter by subscription_config.path
|
|
394
|
+
introspected_paths = await self._list_bus_name_paths(bus_name, "/")
|
|
395
|
+
logger.debug(f"introspected paths for bus_name: {bus_name}, paths: {introspected_paths}")
|
|
396
|
+
for path in introspected_paths:
|
|
397
|
+
if fnmatch.fnmatchcase(path, subscription_config.path):
|
|
398
|
+
object_paths.append(path)
|
|
243
399
|
|
|
244
|
-
|
|
400
|
+
# dedupe
|
|
401
|
+
object_paths = list(set(object_paths))
|
|
245
402
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
)
|
|
259
|
-
await self.event_broker.flow_trigger_queue.async_q.put(trigger_message)
|
|
403
|
+
new_subscribed_interfaces = []
|
|
404
|
+
|
|
405
|
+
# for each object path, call _subscribe_dbus_object
|
|
406
|
+
for object_path in object_paths:
|
|
407
|
+
subscribed_object_interfaces = await self._subscribe_dbus_object(bus_name, object_path)
|
|
408
|
+
new_subscribed_interfaces.extend(subscribed_object_interfaces)
|
|
409
|
+
|
|
410
|
+
# start all flows for the new subscriptions
|
|
411
|
+
if len(new_subscribed_interfaces) > 0:
|
|
412
|
+
await self._start_subscription_flows(bus_name, new_subscribed_interfaces)
|
|
413
|
+
|
|
414
|
+
return new_subscribed_interfaces
|
|
260
415
|
|
|
261
416
|
async def _handle_bus_name_removed(self, bus_name: str):
|
|
262
417
|
|
|
263
|
-
|
|
418
|
+
logger.debug(f"_handle_bus_name_removed: bus_name={bus_name}")
|
|
419
|
+
|
|
420
|
+
bus_name_subscriptions = self.get_bus_name_subscriptions(bus_name)
|
|
264
421
|
|
|
265
422
|
if bus_name_subscriptions:
|
|
266
423
|
for path, proxy_object in bus_name_subscriptions.path_objects.items():
|
|
@@ -268,11 +425,16 @@ class DbusClient:
|
|
|
268
425
|
subscription_configs = self.config.get_subscription_configs(bus_name=bus_name, path=path)
|
|
269
426
|
for subscription_config in subscription_configs:
|
|
270
427
|
|
|
271
|
-
#
|
|
428
|
+
# Stop schedule triggers. Only done once per subscription_config
|
|
429
|
+
# TODO: Dont stop when other bus_names are using the same flowset
|
|
430
|
+
self.flow_scheduler.stop_flow_set(subscription_config.flows)
|
|
431
|
+
|
|
432
|
+
# Trigger flows that have a bus_name_removed trigger configured
|
|
272
433
|
await self._trigger_bus_name_removed(subscription_config, bus_name, path)
|
|
273
434
|
|
|
274
|
-
#
|
|
275
|
-
self.
|
|
435
|
+
# Trigger flows that have an object_removed trigger configured
|
|
436
|
+
await self._trigger_object_removed(subscription_config, bus_name, path)
|
|
437
|
+
|
|
276
438
|
|
|
277
439
|
# Wait for completion
|
|
278
440
|
await self.event_broker.flow_trigger_queue.async_q.join()
|
|
@@ -293,39 +455,193 @@ class DbusClient:
|
|
|
293
455
|
|
|
294
456
|
del self.subscriptions[bus_name]
|
|
295
457
|
|
|
296
|
-
async def
|
|
458
|
+
async def _handle_interfaces_added(self, bus_name: str, path: str) -> None:
|
|
459
|
+
"""
|
|
460
|
+
Handles the addition of new D-Bus interfaces for a given bus name and object path.
|
|
461
|
+
|
|
462
|
+
This method checks if there are subscription configurations for the specified bus name and path.
|
|
463
|
+
If so, it subscribes to the D-Bus object and starts the necessary subscription flows for any new interfaces.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
bus_name (str): The well-known name of the D-Bus service where the interface was added.
|
|
467
|
+
path (str): The object path on the D-Bus where the interface was added.
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
logger.debug(f"_handle_interfaces_added: bus_name={bus_name}, path={path}")
|
|
471
|
+
|
|
472
|
+
if not self.config.get_subscription_configs(bus_name=bus_name, path=path):
|
|
473
|
+
return
|
|
474
|
+
|
|
475
|
+
new_subscribed_interfaces = await self._subscribe_dbus_object(bus_name, path)
|
|
476
|
+
|
|
477
|
+
# start all flows for the new subscriptions
|
|
478
|
+
if len(new_subscribed_interfaces) > 0:
|
|
479
|
+
await self._start_subscription_flows(bus_name, new_subscribed_interfaces)
|
|
480
|
+
|
|
481
|
+
async def _handle_interfaces_removed(self, bus_name: str, path: str) -> None:
|
|
482
|
+
|
|
483
|
+
logger.debug(f"_handle_interfaces_removed: bus_name={bus_name}, path={path}")
|
|
484
|
+
|
|
485
|
+
subscription_configs = self.config.get_subscription_configs(bus_name=bus_name, path=path)
|
|
486
|
+
for subscription_config in subscription_configs:
|
|
487
|
+
|
|
488
|
+
# Stop schedule triggers. Only done once per subscription_config and not per path
|
|
489
|
+
# TODO, only stop if this subscription is not used for any other objects / paths
|
|
490
|
+
self.flow_scheduler.stop_flow_set(subscription_config.flows)
|
|
491
|
+
|
|
492
|
+
# Trigger flows that have an object_removed trigger configured
|
|
493
|
+
await self._trigger_object_removed(subscription_config, bus_name, path)
|
|
494
|
+
|
|
495
|
+
proxy_object = self.get_subscribed_proxy_object(bus_name, path)
|
|
496
|
+
if proxy_object is not None:
|
|
497
|
+
|
|
498
|
+
# Wait for completion
|
|
499
|
+
await self.event_broker.flow_trigger_queue.async_q.join()
|
|
500
|
+
|
|
501
|
+
# clean up all dbus matchrules
|
|
502
|
+
for interface in proxy_object._interfaces.values():
|
|
503
|
+
proxy_interface: dbus_aio.proxy_object.ProxyInterface = interface
|
|
504
|
+
|
|
505
|
+
# officially you should do 'off_...' but the below is easier
|
|
506
|
+
# proxy_interface.off_properties_changed(self.on_properties_changed)
|
|
507
|
+
|
|
508
|
+
# clean lingering interface matchrule from bus
|
|
509
|
+
if proxy_interface._signal_match_rule in self.bus._match_rules.keys():
|
|
510
|
+
self.bus._remove_match_rule(proxy_interface._signal_match_rule)
|
|
511
|
+
|
|
512
|
+
# clean lingering interface messgage handler from bus
|
|
513
|
+
self.bus.remove_message_handler(proxy_interface._message_handler)
|
|
514
|
+
|
|
515
|
+
# For now that InterfacesRemoved signal means the entire object is removed form D-Bus
|
|
516
|
+
del self.subscriptions[bus_name].path_objects[path]
|
|
517
|
+
|
|
518
|
+
# cleanup the entire BusNameSubscriptions if no more objects are subscribed
|
|
519
|
+
bus_name_subscriptions = self.get_bus_name_subscriptions(bus_name)
|
|
520
|
+
if bus_name_subscriptions and len(bus_name_subscriptions.path_objects) == 0:
|
|
521
|
+
del self.subscriptions[bus_name]
|
|
522
|
+
|
|
523
|
+
async def _start_subscription_flows(self, bus_name: str, subscribed_interfaces: list[SubscribedInterface]):
|
|
524
|
+
"""Start all flows for the new subscriptions.
|
|
525
|
+
For each matching bus_name-path subscription_config, the following is done:
|
|
526
|
+
1. Ensure the scheduler is started, at most one scheduler will be active for a subscription_config
|
|
527
|
+
2. Trigger flows that have a bus_name_added trigger configured (only once per bus_name)
|
|
528
|
+
3. Trigger flows that have a interfaces_added trigger configured (once for each bus_name-path pair)
|
|
529
|
+
"""
|
|
530
|
+
|
|
531
|
+
bus_name_object_paths = {}
|
|
532
|
+
bus_name_object_path_interfaces = {}
|
|
533
|
+
for si in subscribed_interfaces:
|
|
534
|
+
bus_name_object_paths.setdefault(si.bus_name, [])
|
|
535
|
+
bus_name_object_path_interfaces.setdefault(si.bus_name, {}).setdefault(si.path, [])
|
|
536
|
+
|
|
537
|
+
if si.path not in bus_name_object_paths[si.bus_name]:
|
|
538
|
+
bus_name_object_paths[si.bus_name].append(si.path)
|
|
539
|
+
|
|
540
|
+
bus_name_object_path_interfaces[si.bus_name][si.path].append(si.interface_name)
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
# new_subscribed_bus_names = list(set([si.bus_name for si in subscribed_interfaces]))
|
|
544
|
+
# new_subscribed_bus_names_paths = {
|
|
545
|
+
# bus_name: list(set([si.path for si in subscribed_interfaces if si.bus_name == bus_name]))
|
|
546
|
+
# for bus_name in new_subscribed_bus_names
|
|
547
|
+
# }
|
|
548
|
+
|
|
549
|
+
logger.debug(f"_start_subscription_flows: ew_subscriptions: {list(bus_name_object_paths.keys())}")
|
|
550
|
+
logger.debug(f"_start_subscription_flows: new_bus_name_object_paths: {bus_name_object_paths}")
|
|
551
|
+
|
|
552
|
+
# setup and process triggers for each flow in each subscription
|
|
553
|
+
# just once per subscription_config
|
|
554
|
+
processed_new_subscriptions: set[str] = set()
|
|
555
|
+
|
|
556
|
+
# With all subscriptions in place, we can now ensure schedulers are created
|
|
557
|
+
# create a FlowProcessor per bus_name/path subscription?
|
|
558
|
+
# One global or a per subscription FlowProcessor.flow_processor_task?
|
|
559
|
+
# Start a new timer job, but leverage existing FlowScheduler
|
|
560
|
+
# How does the FlowScheduler now it should invoke the local FlowPocessor?
|
|
561
|
+
# Maybe use queues to communicate from here with the FlowProcessor?
|
|
562
|
+
# e.g.: StartFlows, StopFlows,
|
|
563
|
+
|
|
564
|
+
# for each bus_name
|
|
565
|
+
for bus_name, path_interfaces_map in bus_name_object_path_interfaces.items():
|
|
566
|
+
|
|
567
|
+
paths = list(path_interfaces_map.keys())
|
|
568
|
+
|
|
569
|
+
# for each path in the bus_name
|
|
570
|
+
for object_path in paths:
|
|
571
|
+
|
|
572
|
+
object_interfaces = path_interfaces_map[object_path]
|
|
573
|
+
|
|
574
|
+
# For each subscription_config that matches the bus_name and object_path
|
|
575
|
+
subscription_configs = self.config.get_subscription_configs(bus_name, object_path)
|
|
576
|
+
for subscription_config in subscription_configs:
|
|
577
|
+
|
|
578
|
+
# Only process subscription_config once, no matter how many paths it matches
|
|
579
|
+
if subscription_config.id not in processed_new_subscriptions:
|
|
580
|
+
|
|
581
|
+
# Ensure all schedulers are started
|
|
582
|
+
# If a scheduler is already active for this subscription flow, it will be reused
|
|
583
|
+
self.flow_scheduler.start_flow_set(subscription_config.flows)
|
|
584
|
+
|
|
585
|
+
# Trigger flows that have a bus_name_added trigger configured
|
|
586
|
+
|
|
587
|
+
# TODO: path arg doesn't make sense here, it did work for mpris however where there is only one path
|
|
588
|
+
# leaving it now for backwards compatibility
|
|
589
|
+
await self._trigger_bus_name_added(subscription_config, bus_name, object_path)
|
|
590
|
+
|
|
591
|
+
processed_new_subscriptions.add(subscription_config.id)
|
|
592
|
+
|
|
593
|
+
# Trigger flows that have a object_added trigger configured
|
|
594
|
+
await self._trigger_object_added(subscription_config, bus_name, object_path, object_interfaces)
|
|
595
|
+
|
|
596
|
+
async def _trigger_flows(self, subscription_config: SubscriptionConfig, type: str, context: dict):
|
|
297
597
|
|
|
298
|
-
# Trigger flows that have a bus_name_removed trigger configured
|
|
299
598
|
for flow in subscription_config.flows:
|
|
300
599
|
for trigger in flow.triggers:
|
|
301
|
-
if trigger.type ==
|
|
302
|
-
|
|
303
|
-
"bus_name": bus_name,
|
|
304
|
-
"path": path
|
|
305
|
-
}
|
|
306
|
-
trigger_message = FlowTriggerMessage(
|
|
307
|
-
flow,
|
|
308
|
-
trigger,
|
|
309
|
-
datetime.now(),
|
|
310
|
-
trigger_context=trigger_context
|
|
311
|
-
)
|
|
600
|
+
if trigger.type == type:
|
|
601
|
+
trigger_message = FlowTriggerMessage(flow, trigger, datetime.now(), context)
|
|
312
602
|
await self.event_broker.flow_trigger_queue.async_q.put(trigger_message)
|
|
313
603
|
|
|
314
|
-
async def
|
|
604
|
+
async def _trigger_bus_name_added(self, subscription_config: SubscriptionConfig, bus_name: str, path: str):
|
|
605
|
+
|
|
606
|
+
# Trigger flows that have a bus_name_added trigger configured
|
|
607
|
+
await self._trigger_flows(subscription_config, "bus_name_added", {
|
|
608
|
+
"bus_name": bus_name,
|
|
609
|
+
"path": path
|
|
610
|
+
})
|
|
611
|
+
|
|
612
|
+
async def _trigger_bus_name_removed(self, subscription_config: SubscriptionConfig, bus_name: str, path: str):
|
|
613
|
+
|
|
614
|
+
# Trigger flows that have a bus_name_removed trigger configured
|
|
615
|
+
await self._trigger_flows(subscription_config, "bus_name_removed", {
|
|
616
|
+
"bus_name": bus_name,
|
|
617
|
+
"path": path
|
|
618
|
+
})
|
|
619
|
+
|
|
620
|
+
async def _trigger_object_added(self, subscription_config: SubscriptionConfig, bus_name: str, object_path: str, object_interfaces: list[str]):
|
|
621
|
+
|
|
622
|
+
# Trigger flows that have a object_added trigger configured
|
|
623
|
+
await self._trigger_flows(subscription_config, "object_added", {
|
|
624
|
+
"bus_name": bus_name,
|
|
625
|
+
"path": object_path
|
|
626
|
+
# "interfaces": object_interfaces
|
|
627
|
+
})
|
|
315
628
|
|
|
316
|
-
|
|
629
|
+
async def _trigger_object_removed(self, subscription_config: SubscriptionConfig, bus_name: str, path: str):
|
|
317
630
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
await self._handle_bus_name_removed(name)
|
|
631
|
+
# Trigger flows that have a object_removed trigger configured
|
|
632
|
+
await self._trigger_flows(subscription_config, "object_removed", {
|
|
633
|
+
"bus_name": bus_name,
|
|
634
|
+
"path": path
|
|
635
|
+
})
|
|
324
636
|
|
|
325
637
|
async def call_dbus_interface_method(self, interface: dbus_aio.proxy_object.ProxyInterface, method: str, method_args: list[Any]):
|
|
326
638
|
|
|
327
639
|
call_method_name = "call_" + camel_to_snake(method)
|
|
328
|
-
|
|
640
|
+
try:
|
|
641
|
+
res = await interface.__getattribute__(call_method_name)(*method_args)
|
|
642
|
+
except dbus_errors.DBusError as e:
|
|
643
|
+
logger.warning(f"Error while calling dbus object, bus_name={interface.bus_name}, interface={interface.introspection.name}, method={method}")
|
|
644
|
+
raise e
|
|
329
645
|
|
|
330
646
|
if res:
|
|
331
647
|
res = unwrap_dbus_object(res)
|
|
@@ -367,9 +683,16 @@ class DbusClient:
|
|
|
367
683
|
async def dbus_signal_queue_processor_task(self):
|
|
368
684
|
"""Continuously processes messages from the async queue."""
|
|
369
685
|
while True:
|
|
370
|
-
signal = await self.
|
|
686
|
+
signal = await self._dbus_signal_queue.async_q.get()
|
|
371
687
|
await self._handle_on_dbus_signal(signal)
|
|
372
|
-
self.
|
|
688
|
+
self._dbus_signal_queue.async_q.task_done()
|
|
689
|
+
|
|
690
|
+
async def dbus_object_lifecycle_signal_processor_task(self):
|
|
691
|
+
"""Continuously processes messages from the async queue."""
|
|
692
|
+
while True:
|
|
693
|
+
message = await self._dbus_object_lifecycle_signal_queue.async_q.get()
|
|
694
|
+
await self._handle_dbus_object_lifecycle_signal(message)
|
|
695
|
+
self._dbus_object_lifecycle_signal_queue.async_q.task_done()
|
|
373
696
|
|
|
374
697
|
async def _handle_on_dbus_signal(self, signal: DbusSignalWithState):
|
|
375
698
|
|
|
@@ -390,6 +713,7 @@ class DbusClient:
|
|
|
390
713
|
"bus_name": signal.bus_name,
|
|
391
714
|
"path": signal.path,
|
|
392
715
|
"interface": signal.interface_name,
|
|
716
|
+
"signal": signal.signal_config.signal,
|
|
393
717
|
"args": signal.args
|
|
394
718
|
}
|
|
395
719
|
trigger_message = FlowTriggerMessage(
|
|
@@ -403,11 +727,31 @@ class DbusClient:
|
|
|
403
727
|
except Exception as e:
|
|
404
728
|
logger.warning(f"dbus_signal_queue_processor_task: Exception {e}", exc_info=True)
|
|
405
729
|
|
|
730
|
+
async def _handle_dbus_object_lifecycle_signal(self, message: dbus_message.Message):
|
|
731
|
+
|
|
732
|
+
if message.member == 'NameOwnerChanged':
|
|
733
|
+
name, old_owner, new_owner = message.body
|
|
734
|
+
if new_owner != '' and old_owner == '':
|
|
735
|
+
await self._handle_bus_name_added(name)
|
|
736
|
+
if old_owner != '' and new_owner == '':
|
|
737
|
+
await self._handle_bus_name_removed(name)
|
|
738
|
+
|
|
739
|
+
if message.interface == 'org.freedesktop.DBus.ObjectManager':
|
|
740
|
+
bus_name = self.get_well_known_bus_name(message.sender)
|
|
741
|
+
if message.member == 'InterfacesAdded':
|
|
742
|
+
path = message.body[0]
|
|
743
|
+
await self._handle_interfaces_added(bus_name, path)
|
|
744
|
+
elif message.member == 'InterfacesRemoved':
|
|
745
|
+
path = message.body[0]
|
|
746
|
+
await self._handle_interfaces_removed(bus_name, path)
|
|
747
|
+
|
|
406
748
|
async def _on_mqtt_msg(self, msg: MqttMessage):
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
749
|
+
"""Executes dbus method calls or property updates on objects when messages have
|
|
750
|
+
1. a matching subscription configured
|
|
751
|
+
2. a matching method
|
|
752
|
+
3. a matching bus_name (if provided)
|
|
753
|
+
4. a matching path (if provided)
|
|
754
|
+
"""
|
|
411
755
|
|
|
412
756
|
found_matching_topic = False
|
|
413
757
|
for subscription_configs in self.config.subscriptions:
|
|
@@ -423,6 +767,9 @@ class DbusClient:
|
|
|
423
767
|
matched_method = False
|
|
424
768
|
matched_property = False
|
|
425
769
|
|
|
770
|
+
payload_bus_name = msg.payload.get("bus_name") or "*"
|
|
771
|
+
payload_path = msg.payload.get("path") or "*"
|
|
772
|
+
|
|
426
773
|
payload_method = msg.payload.get("method")
|
|
427
774
|
payload_method_args = msg.payload.get("args") or []
|
|
428
775
|
|
|
@@ -430,47 +777,44 @@ class DbusClient:
|
|
|
430
777
|
payload_value = msg.payload.get("value")
|
|
431
778
|
|
|
432
779
|
if payload_method is None and (payload_property is None or payload_value is None):
|
|
433
|
-
|
|
780
|
+
if msg.payload:
|
|
781
|
+
logger.info(f"on_mqtt_msg: Unsupported payload, missing 'method' or 'property/value', got method={payload_method}, property={payload_property}, value={payload_value} from {msg.payload}")
|
|
434
782
|
return
|
|
435
783
|
|
|
436
784
|
for [bus_name, bus_name_subscription] in self.subscriptions.items():
|
|
437
|
-
|
|
438
|
-
for
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
785
|
+
if fnmatch.fnmatchcase(bus_name, payload_bus_name):
|
|
786
|
+
for [path, proxy_object] in bus_name_subscription.path_objects.items():
|
|
787
|
+
if fnmatch.fnmatchcase(path, payload_path):
|
|
788
|
+
for subscription_configs in self.config.get_subscription_configs(bus_name=bus_name, path=path):
|
|
789
|
+
for interface_config in subscription_configs.interfaces:
|
|
790
|
+
|
|
791
|
+
for method in interface_config.methods:
|
|
792
|
+
|
|
793
|
+
# filter configured method, configured topic, ...
|
|
794
|
+
if method.method == payload_method:
|
|
795
|
+
interface = proxy_object.get_interface(name=interface_config.interface)
|
|
796
|
+
matched_method = True
|
|
797
|
+
|
|
798
|
+
try:
|
|
799
|
+
logger.info(f"on_mqtt_msg: method={method.method}, args={payload_method_args}, bus_name={bus_name}, path={path}, interface={interface_config.interface}")
|
|
800
|
+
await self.call_dbus_interface_method(interface, method.method, payload_method_args)
|
|
801
|
+
except Exception as e:
|
|
802
|
+
logger.warning(f"on_mqtt_msg: method={method.method}, args={payload_method_args}, bus_name={bus_name} failed, exception={e}")
|
|
803
|
+
|
|
804
|
+
for property in interface_config.properties:
|
|
805
|
+
# filter configured property, configured topic, ...
|
|
806
|
+
if property.property == payload_property:
|
|
807
|
+
interface = proxy_object.get_interface(name=interface_config.interface)
|
|
808
|
+
matched_property = True
|
|
809
|
+
|
|
810
|
+
try:
|
|
811
|
+
logger.info(f"on_mqtt_msg: property={property.property}, value={payload_value}, bus_name={bus_name}, path={path}, interface={interface_config.interface}")
|
|
812
|
+
await self.set_dbus_interface_property(interface, property.property, payload_value)
|
|
813
|
+
except Exception as e:
|
|
814
|
+
logger.warning(f"on_mqtt_msg: property={property.property}, value={payload_value}, bus_name={bus_name} failed, exception={e}")
|
|
465
815
|
|
|
466
816
|
if not matched_method and not matched_property:
|
|
467
817
|
if payload_method:
|
|
468
|
-
logger.info(f"No configured or active dbus subscriptions for topic={msg.topic}, method={payload_method}, active bus_names={list(self.subscriptions.keys())}")
|
|
818
|
+
logger.info(f"No configured or active dbus subscriptions for topic={msg.topic}, method={payload_method}, bus_name={payload_bus_name}, path={payload_path or '*'}, active bus_names={list(self.subscriptions.keys())}")
|
|
469
819
|
if payload_property:
|
|
470
|
-
logger.info(f"No configured or active dbus subscriptions for topic={msg.topic}, property={payload_property}, active bus_names={list(self.subscriptions.keys())}")
|
|
471
|
-
|
|
472
|
-
# raw mode, payload contains: bus_name (specific or wildcard), path, interface_name
|
|
473
|
-
# topic: dbus2mqtt/raw (with allowlist check)
|
|
474
|
-
|
|
475
|
-
# predefined mode with topic matching from configuration
|
|
476
|
-
# topic: dbus2mqtt/MediaPlayer/command
|
|
820
|
+
logger.info(f"No configured or active dbus subscriptions for topic={msg.topic}, property={payload_property}, bus_name={payload_bus_name}, path={payload_path or '*'}, active bus_names={list(self.subscriptions.keys())}")
|