8
|
1 #!/usr/bin/env python3
|
|
2 # -*- coding: utf-8 -*-
|
|
3
|
|
4 ## @package dbus_vrm
|
|
5 # This code takes care of the D-Bus interface (not all of below is implemented yet):
|
|
6 # - on startup it scans the dbus for services we know. For each known service found, it searches for
|
|
7 # objects/paths we know. Everything we find is stored in items{}, and an event is registered: if a
|
|
8 # value changes weĺl be notified and can pass that on to our owner. For example the vrmLogger.
|
|
9 # we know.
|
|
10 # - after startup, it continues to monitor the dbus:
|
|
11 # 1) when services are added we do the same check on that
|
|
12 # 2) when services are removed, we remove any items that we had that referred to that service
|
|
13 # 3) if an existing services adds paths we update ourselves as well: on init, we make a
|
|
14 # VeDbusItemImport for a non-, or not yet existing objectpaths as well1
|
|
15 #
|
|
16 # Code is used by the vrmLogger, and also the pubsub code. Both are other modules in the dbus_vrm repo.
|
|
17
|
|
18 from dbus.mainloop.glib import DBusGMainLoop
|
|
19 from gi.repository import GLib
|
|
20 import dbus
|
|
21 import dbus.service
|
|
22 import inspect
|
|
23 import logging
|
|
24 import argparse
|
|
25 import pprint
|
|
26 import traceback
|
|
27 import os
|
|
28 from collections import defaultdict
|
|
29 from functools import partial
|
|
30
|
|
31 # our own packages
|
|
32 from ve_utils import exit_on_error, wrap_dbus_value, unwrap_dbus_value
|
|
33 notfound = object() # For lookups where None is a valid result
|
|
34
|
|
35 logger = logging.getLogger(__name__)
|
|
36 logger.setLevel(logging.INFO)
|
|
37 class SystemBus(dbus.bus.BusConnection):
|
|
38 def __new__(cls):
|
|
39 return dbus.bus.BusConnection.__new__(cls, dbus.bus.BusConnection.TYPE_SYSTEM)
|
|
40
|
|
41 class SessionBus(dbus.bus.BusConnection):
|
|
42 def __new__(cls):
|
|
43 return dbus.bus.BusConnection.__new__(cls, dbus.bus.BusConnection.TYPE_SESSION)
|
|
44
|
|
45 class MonitoredValue(object):
|
|
46 def __init__(self, value, text, options):
|
|
47 super(MonitoredValue, self).__init__()
|
|
48 self.value = value
|
|
49 self.text = text
|
|
50 self.options = options
|
|
51
|
|
52 # For legacy code, allow treating this as a tuple/list
|
|
53 def __iter__(self):
|
|
54 return iter((self.value, self.text, self.options))
|
|
55
|
|
56 class Service(object):
|
|
57 whentologoptions = ['configChange', 'onIntervalAlwaysAndOnEvent',
|
|
58 'onIntervalOnlyWhenChanged', 'onIntervalAlways', 'never']
|
|
59 def __init__(self, id, serviceName, deviceInstance):
|
|
60 super(Service, self).__init__()
|
|
61 self.id = id
|
|
62 self.name = serviceName
|
|
63 self.paths = {}
|
|
64 self._seen = set()
|
|
65 self.deviceInstance = deviceInstance
|
|
66
|
|
67 self.configChange = []
|
|
68 self.onIntervalAlwaysAndOnEvent = []
|
|
69 self.onIntervalOnlyWhenChanged = []
|
|
70 self.onIntervalAlways = []
|
|
71 self.never = []
|
|
72
|
|
73 # For legacy code, attributes can still be accessed as if keys from a
|
|
74 # dictionary.
|
|
75 def __setitem__(self, key, value):
|
|
76 self.__dict__[key] = value
|
|
77 def __getitem__(self, key):
|
|
78 return self.__dict__[key]
|
|
79
|
|
80 def set_seen(self, path):
|
|
81 self._seen.add(path)
|
|
82
|
|
83 def seen(self, path):
|
|
84 return path in self._seen
|
|
85
|
|
86 @property
|
|
87 def service_class(self):
|
|
88 return '.'.join(self.name.split('.')[:3])
|
|
89
|
|
90 class DbusMonitor(object):
|
|
91 ## Constructor
|
|
92 def __init__(self, dbusTree, valueChangedCallback=None, deviceAddedCallback=None,
|
|
93 deviceRemovedCallback=None, vebusDeviceInstance0=False):
|
|
94 # valueChangedCallback is the callback that we call when something has changed.
|
|
95 # def value_changed_on_dbus(dbusServiceName, dbusPath, options, changes, deviceInstance):
|
|
96 # in which changes is a tuple with GetText() and GetValue()
|
|
97 self.valueChangedCallback = valueChangedCallback
|
|
98 self.deviceAddedCallback = deviceAddedCallback
|
|
99 self.deviceRemovedCallback = deviceRemovedCallback
|
|
100 self.dbusTree = dbusTree
|
|
101 self.vebusDeviceInstance0 = vebusDeviceInstance0
|
|
102
|
|
103 # Lists all tracked services. Stores name, id, device instance, value per path, and whenToLog info
|
|
104 # indexed by service name (eg. com.victronenergy.settings).
|
|
105 self.servicesByName = {}
|
|
106
|
|
107 # Same values as self.servicesByName, but indexed by service id (eg. :1.30)
|
|
108 self.servicesById = {}
|
|
109
|
|
110 # Keep track of services by class to speed up calls to get_service_list
|
|
111 self.servicesByClass = defaultdict(list)
|
|
112
|
|
113 # Keep track of any additional watches placed on items
|
|
114 self.serviceWatches = defaultdict(list)
|
|
115
|
|
116 # For a PC, connect to the SessionBus
|
|
117 # For a CCGX, connect to the SystemBus
|
|
118 self.dbusConn = SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else SystemBus()
|
|
119
|
|
120 # subscribe to NameOwnerChange for bus connect / disconnect events.
|
|
121 (dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ \
|
|
122 else dbus.SystemBus()).add_signal_receiver(
|
|
123 self.dbus_name_owner_changed,
|
|
124 signal_name='NameOwnerChanged')
|
|
125
|
|
126 # Subscribe to PropertiesChanged for all services
|
|
127 self.dbusConn.add_signal_receiver(self.handler_value_changes,
|
|
128 dbus_interface='com.victronenergy.BusItem',
|
|
129 signal_name='PropertiesChanged', path_keyword='path',
|
|
130 sender_keyword='senderId')
|
|
131
|
|
132 # Subscribe to ItemsChanged for all services
|
|
133 self.dbusConn.add_signal_receiver(self.handler_item_changes,
|
|
134 dbus_interface='com.victronenergy.BusItem',
|
|
135 signal_name='ItemsChanged', path='/',
|
|
136 sender_keyword='senderId')
|
|
137
|
|
138 logger.info('===== Search on dbus for services that we will monitor starting... =====')
|
|
139 serviceNames = self.dbusConn.list_names()
|
|
140 for serviceName in serviceNames:
|
|
141 self.scan_dbus_service(serviceName)
|
|
142
|
|
143 logger.info('===== Search on dbus for services that we will monitor finished =====')
|
|
144
|
|
145 def dbus_name_owner_changed(self, name, oldowner, newowner):
|
|
146 if not name.startswith("com.victronenergy."):
|
|
147 return
|
|
148
|
|
149 #decouple, and process in main loop
|
|
150 GLib.idle_add(exit_on_error, self._process_name_owner_changed, name, oldowner, newowner)
|
|
151
|
|
152 def _process_name_owner_changed(self, name, oldowner, newowner):
|
|
153 if newowner != '':
|
|
154 # so we found some new service. Check if we can do something with it.
|
|
155 newdeviceadded = self.scan_dbus_service(name)
|
|
156 if newdeviceadded and self.deviceAddedCallback is not None:
|
|
157 self.deviceAddedCallback(name, self.get_device_instance(name))
|
|
158
|
|
159 elif name in self.servicesByName:
|
|
160 # it disappeared, we need to remove it.
|
|
161 logger.info("%s disappeared from the dbus. Removing it from our lists" % name)
|
|
162 service = self.servicesByName[name]
|
|
163 deviceInstance = service['deviceInstance']
|
|
164 del self.servicesById[service.id]
|
|
165 del self.servicesByName[name]
|
|
166 for watch in self.serviceWatches[name]:
|
|
167 watch.remove()
|
|
168 del self.serviceWatches[name]
|
|
169 self.servicesByClass[service.service_class].remove(service)
|
|
170 if self.deviceRemovedCallback is not None:
|
|
171 self.deviceRemovedCallback(name, deviceInstance)
|
|
172
|
|
173 def scan_dbus_service(self, serviceName):
|
|
174 try:
|
|
175 return self.scan_dbus_service_inner(serviceName)
|
|
176 except:
|
|
177 logger.error("Ignoring %s because of error while scanning:" % (serviceName))
|
|
178 traceback.print_exc()
|
|
179 return False
|
|
180
|
|
181 # Errors 'org.freedesktop.DBus.Error.ServiceUnknown' and
|
|
182 # 'org.freedesktop.DBus.Error.Disconnected' seem to happen when the service
|
|
183 # disappears while its being scanned. Which might happen, but is not really
|
|
184 # normal either, so letting them go into the logs.
|
|
185
|
|
186 # Scans the given dbus service to see if it contains anything interesting for us. If it does, add
|
|
187 # it to our list of monitored D-Bus services.
|
|
188 def scan_dbus_service_inner(self, serviceName):
|
|
189
|
|
190 # make it a normal string instead of dbus string
|
|
191 serviceName = str(serviceName)
|
|
192
|
|
193 paths = self.dbusTree.get('.'.join(serviceName.split('.')[0:3]), None)
|
|
194 if paths is None:
|
|
195 logger.debug("Ignoring service %s, not in the tree" % serviceName)
|
|
196 return False
|
|
197
|
|
198 logger.info("Found: %s, scanning and storing items" % serviceName)
|
|
199 serviceId = self.dbusConn.get_name_owner(serviceName)
|
|
200
|
|
201 # we should never be notified to add a D-Bus service that we already have. If this assertion
|
|
202 # raises, check process_name_owner_changed, and D-Bus workings.
|
|
203 assert serviceName not in self.servicesByName
|
|
204 assert serviceId not in self.servicesById
|
|
205
|
|
206 # for vebus.ttyO1, this is workaround, since VRM Portal expects the main vebus
|
|
207 # devices at instance 0. Not sure how to fix this yet.
|
|
208 if serviceName == 'com.victronenergy.vebus.ttyO1' and self.vebusDeviceInstance0:
|
|
209 di = 0
|
|
210 elif serviceName == 'com.victronenergy.settings':
|
|
211 di = 0
|
|
212 elif serviceName.startswith('com.victronenergy.vecan.'):
|
|
213 di = 0
|
|
214 else:
|
|
215 try:
|
|
216 di = self.dbusConn.call_blocking(serviceName,
|
|
217 '/DeviceInstance', None, 'GetValue', '', [])
|
|
218 except dbus.exceptions.DBusException:
|
|
219 logger.info(" %s was skipped because it has no device instance" % serviceName)
|
|
220 return False # Skip it
|
|
221 else:
|
|
222 di = int(di)
|
|
223
|
|
224 logger.info(" %s has device instance %s" % (serviceName, di))
|
|
225 service = Service(serviceId, serviceName, di)
|
|
226
|
|
227 # Let's try to fetch everything in one go
|
|
228 values = {}
|
|
229 texts = {}
|
|
230 try:
|
|
231 values.update(self.dbusConn.call_blocking(serviceName, '/', None, 'GetValue', '', []))
|
|
232 texts.update(self.dbusConn.call_blocking(serviceName, '/', None, 'GetText', '', []))
|
|
233 except:
|
|
234 pass
|
|
235
|
|
236 for path, options in paths.items():
|
|
237 # path will be the D-Bus path: '/Ac/ActiveIn/L1/V'
|
|
238 # options will be a dictionary: {'code': 'V', 'whenToLog': 'onIntervalAlways'}
|
|
239 # check that the whenToLog setting is set to something we expect
|
|
240 assert options['whenToLog'] is None or options['whenToLog'] in Service.whentologoptions
|
|
241
|
|
242 # Try to obtain the value we want from our bulk fetch. If we
|
|
243 # cannot find it there, do an individual query.
|
|
244 value = values.get(path[1:], notfound)
|
|
245 if value != notfound:
|
|
246 service.set_seen(path)
|
|
247 text = texts.get(path[1:], notfound)
|
|
248 if value is notfound or text is notfound:
|
|
249 try:
|
|
250 value = self.dbusConn.call_blocking(serviceName, path, None, 'GetValue', '', [])
|
|
251 service.set_seen(path)
|
|
252 text = self.dbusConn.call_blocking(serviceName, path, None, 'GetText', '', [])
|
|
253 except dbus.exceptions.DBusException as e:
|
|
254 if e.get_dbus_name() in (
|
|
255 'org.freedesktop.DBus.Error.ServiceUnknown',
|
|
256 'org.freedesktop.DBus.Error.Disconnected'):
|
|
257 raise # This exception will be handled below
|
|
258
|
|
259 # TODO org.freedesktop.DBus.Error.UnknownMethod really
|
|
260 # shouldn't happen but sometimes does.
|
|
261 logger.debug("%s %s does not exist (yet)" % (serviceName, path))
|
|
262 value = None
|
|
263 text = None
|
|
264
|
|
265 service.paths[path] = MonitoredValue(unwrap_dbus_value(value), unwrap_dbus_value(text), options)
|
|
266
|
|
267 if options['whenToLog']:
|
|
268 service[options['whenToLog']].append(path)
|
|
269
|
|
270
|
|
271 logger.debug("Finished scanning and storing items for %s" % serviceName)
|
|
272
|
|
273 # Adjust self at the end of the scan, so we don't have an incomplete set of
|
|
274 # data if an exception occurs during the scan.
|
|
275 self.servicesByName[serviceName] = service
|
|
276 self.servicesById[serviceId] = service
|
|
277 self.servicesByClass[service.service_class].append(service)
|
|
278
|
|
279 return True
|
|
280
|
|
281 def handler_item_changes(self, items, senderId):
|
|
282 if not isinstance(items, dict):
|
|
283 return
|
|
284
|
|
285 try:
|
|
286 service = self.servicesById[senderId]
|
|
287 except KeyError:
|
|
288 # senderId isn't there, which means it hasn't been scanned yet.
|
|
289 return
|
|
290
|
|
291 for path, changes in items.items():
|
|
292 try:
|
|
293 v = unwrap_dbus_value(changes['Value'])
|
|
294 except (KeyError, TypeError):
|
|
295 continue
|
|
296
|
|
297 try:
|
|
298 t = changes['Text']
|
|
299 except KeyError:
|
|
300 t = str(v)
|
|
301 self._handler_value_changes(service, path, v, t)
|
|
302
|
|
303 def handler_value_changes(self, changes, path, senderId):
|
|
304 # If this properyChange does not involve a value, our work is done.
|
|
305 if 'Value' not in changes:
|
|
306 return
|
|
307
|
|
308 try:
|
|
309 service = self.servicesById[senderId]
|
|
310 except KeyError:
|
|
311 # senderId isn't there, which means it hasn't been scanned yet.
|
|
312 return
|
|
313
|
|
314 v = unwrap_dbus_value(changes['Value'])
|
|
315 # Some services don't send Text with their PropertiesChanged events.
|
|
316 try:
|
|
317 t = changes['Text']
|
|
318 except KeyError:
|
|
319 t = str(v)
|
|
320 self._handler_value_changes(service, path, v, t)
|
|
321
|
|
322 def _handler_value_changes(self, service, path, value, text):
|
|
323 try:
|
|
324 a = service.paths[path]
|
|
325 except KeyError:
|
|
326 # path isn't there, which means it hasn't been scanned yet.
|
|
327 return
|
|
328
|
|
329 service.set_seen(path)
|
|
330
|
|
331 # First update our store to the new value
|
|
332 if a.value == value:
|
|
333 return
|
|
334
|
|
335 a.value = value
|
|
336 a.text = text
|
|
337
|
|
338 # And do the rest of the processing in on the mainloop
|
|
339 if self.valueChangedCallback is not None:
|
|
340 GLib.idle_add(exit_on_error, self._execute_value_changes, service.name, path, {
|
|
341 'Value': value, 'Text': text}, a.options)
|
|
342
|
|
343 def _execute_value_changes(self, serviceName, objectPath, changes, options):
|
|
344 # double check that the service still exists, as it might have
|
|
345 # disappeared between scheduling-for and executing this function.
|
|
346 if serviceName not in self.servicesByName:
|
|
347 return
|
|
348
|
|
349 self.valueChangedCallback(serviceName, objectPath,
|
|
350 options, changes, self.get_device_instance(serviceName))
|
|
351
|
|
352 # Gets the value for a certain servicename and path
|
|
353 # The default_value is returned when:
|
|
354 # 1. When the service doesn't exist.
|
|
355 # 2. When the path asked for isn't being monitored.
|
|
356 # 3. When the path exists, but has dbus-invalid, ie an empty byte array.
|
|
357 # 4. When the path asked for is being monitored, but doesn't exist for that service.
|
|
358 def get_value(self, serviceName, objectPath, default_value=None):
|
|
359 service = self.servicesByName.get(serviceName, None)
|
|
360 if service is None:
|
|
361 return default_value
|
|
362
|
|
363 value = service.paths.get(objectPath, None)
|
|
364 if value is None or value.value is None:
|
|
365 return default_value
|
|
366
|
|
367 return value.value
|
|
368
|
|
369 # returns if a dbus exists now, by doing a blocking dbus call.
|
|
370 # Typically seen will be sufficient and doesn't need access to the dbus.
|
|
371 def exists(self, serviceName, objectPath):
|
|
372 try:
|
|
373 self.dbusConn.call_blocking(serviceName, objectPath, None, 'GetValue', '', [])
|
|
374 return True
|
|
375 except dbus.exceptions.DBusException as e:
|
|
376 return False
|
|
377
|
|
378 # Returns if there ever was a successful GetValue or valueChanged event.
|
|
379 # Unlike get_value this return True also if the actual value is invalid.
|
|
380 #
|
|
381 # Note: the path might no longer exists anymore, but that doesn't happen in
|
|
382 # practice. If a service really wants to reconfigure itself typically it should
|
|
383 # reconnect to the dbus which causes it to be rescanned and seen will be updated.
|
|
384 # If it is really needed to know if a path still exists, use exists.
|
|
385 def seen(self, serviceName, objectPath):
|
|
386 try:
|
|
387 return self.servicesByName[serviceName].seen(objectPath)
|
|
388 except KeyError:
|
|
389 return False
|
|
390
|
|
391 # Sets the value for a certain servicename and path, returns the return value of the D-Bus SetValue
|
|
392 # method. If the underlying item does not exist (the service does not exist, or the objectPath was not
|
|
393 # registered) the function will return -1
|
|
394 def set_value(self, serviceName, objectPath, value):
|
|
395 # Check if the D-Bus object referenced by serviceName and objectPath is registered. There is no
|
|
396 # necessity to do this, but it is in line with previous implementations which kept VeDbusItemImport
|
|
397 # objects for registers items only.
|
|
398 service = self.servicesByName.get(serviceName, None)
|
|
399 if service is None:
|
|
400 return -1
|
|
401 if objectPath not in service.paths:
|
|
402 return -1
|
|
403 # We do not catch D-Bus exceptions here, because the previous implementation did not do that either.
|
|
404 return self.dbusConn.call_blocking(serviceName, objectPath,
|
|
405 dbus_interface='com.victronenergy.BusItem',
|
|
406 method='SetValue', signature=None,
|
|
407 args=[wrap_dbus_value(value)])
|
|
408
|
|
409 # Similar to set_value, but operates asynchronously
|
|
410 def set_value_async(self, serviceName, objectPath, value,
|
|
411 reply_handler=None, error_handler=None):
|
|
412 service = self.servicesByName.get(serviceName, None)
|
|
413 if service is not None:
|
|
414 if objectPath in service.paths:
|
|
415 self.dbusConn.call_async(serviceName, objectPath,
|
|
416 dbus_interface='com.victronenergy.BusItem',
|
|
417 method='SetValue', signature=None,
|
|
418 args=[wrap_dbus_value(value)],
|
|
419 reply_handler=reply_handler, error_handler=error_handler)
|
|
420 return
|
|
421
|
|
422 if error_handler is not None:
|
|
423 error_handler(TypeError('Service or path not found, '
|
|
424 'service=%s, path=%s' % (serviceName, objectPath)))
|
|
425
|
|
426 # returns a dictionary, keys are the servicenames, value the instances
|
|
427 # optionally use the classfilter to get only a certain type of services, for
|
|
428 # example com.victronenergy.battery.
|
|
429 def get_service_list(self, classfilter=None):
|
|
430 if classfilter is None:
|
|
431 return { servicename: service.deviceInstance \
|
|
432 for servicename, service in self.servicesByName.items() }
|
|
433
|
|
434 if classfilter not in self.servicesByClass:
|
|
435 return {}
|
|
436
|
|
437 return { service.name: service.deviceInstance \
|
|
438 for service in self.servicesByClass[classfilter] }
|
|
439
|
|
440 def get_device_instance(self, serviceName):
|
|
441 return self.servicesByName[serviceName].deviceInstance
|
|
442
|
|
443 # Parameter categoryfilter is to be a list, containing the categories you want (configChange,
|
|
444 # onIntervalAlways, etc).
|
|
445 # Returns a dictionary, keys are codes + instance, in VRM querystring format. For example vvt[0]. And
|
|
446 # values are the value.
|
|
447 def get_values(self, categoryfilter, converter=None):
|
|
448
|
|
449 result = {}
|
|
450
|
|
451 for serviceName in self.servicesByName:
|
|
452 result.update(self.get_values_for_service(categoryfilter, serviceName, converter))
|
|
453
|
|
454 return result
|
|
455
|
|
456 # same as get_values above, but then for one service only
|
|
457 def get_values_for_service(self, categoryfilter, servicename, converter=None):
|
|
458 deviceInstance = self.get_device_instance(servicename)
|
|
459 result = {}
|
|
460
|
|
461 service = self.servicesByName[servicename]
|
|
462
|
|
463 for category in categoryfilter:
|
|
464
|
|
465 for path in service[category]:
|
|
466
|
|
467 value, text, options = service.paths[path]
|
|
468
|
|
469 if value is not None:
|
|
470
|
|
471 value = value if converter is None else converter.convert(path, options['code'], value, text)
|
|
472
|
|
473 precision = options.get('precision')
|
|
474 if precision:
|
|
475 value = round(value, precision)
|
|
476
|
|
477 result[options['code'] + "[" + str(deviceInstance) + "]"] = value
|
|
478
|
|
479 return result
|
|
480
|
|
481 def track_value(self, serviceName, objectPath, callback, *args, **kwargs):
|
|
482 """ A DbusMonitor can watch specific service/path combos for changes
|
|
483 so that it is not fully reliant on the global handler_value_changes
|
|
484 in this class. Additional watches are deleted automatically when
|
|
485 the service disappears from dbus. """
|
|
486 cb = partial(callback, *args, **kwargs)
|
|
487
|
|
488 def root_tracker(items):
|
|
489 # Check if objectPath in dict
|
|
490 try:
|
|
491 v = items[objectPath]
|
|
492 _v = unwrap_dbus_value(v['Value'])
|
|
493 except (KeyError, TypeError):
|
|
494 return # not in this dict
|
|
495
|
|
496 try:
|
|
497 t = v['Text']
|
|
498 except KeyError:
|
|
499 cb({'Value': _v })
|
|
500 else:
|
|
501 cb({'Value': _v, 'Text': t})
|
|
502
|
|
503 # Track changes on the path, and also on root
|
|
504 self.serviceWatches[serviceName].extend((
|
|
505 self.dbusConn.add_signal_receiver(cb,
|
|
506 dbus_interface='com.victronenergy.BusItem',
|
|
507 signal_name='PropertiesChanged',
|
|
508 path=objectPath, bus_name=serviceName),
|
|
509 self.dbusConn.add_signal_receiver(root_tracker,
|
|
510 dbus_interface='com.victronenergy.BusItem',
|
|
511 signal_name='ItemsChanged',
|
|
512 path="/", bus_name=serviceName),
|
|
513 ))
|
|
514
|
|
515
|
|
516 # ====== ALL CODE BELOW THIS LINE IS PURELY FOR DEVELOPING THIS CLASS ======
|
|
517
|
|
518 # Example function that can be used as a starting point to use this code
|
|
519 def value_changed_on_dbus(dbusServiceName, dbusPath, dict, changes, deviceInstance):
|
|
520 logger.debug("0 ----------------")
|
|
521 logger.debug("1 %s%s changed" % (dbusServiceName, dbusPath))
|
|
522 logger.debug("2 vrm dict : %s" % dict)
|
|
523 logger.debug("3 changes-text: %s" % changes['Text'])
|
|
524 logger.debug("4 changes-value: %s" % changes['Value'])
|
|
525 logger.debug("5 deviceInstance: %s" % deviceInstance)
|
|
526 logger.debug("6 - end")
|
|
527
|
|
528
|
|
529 def nameownerchange(a, b):
|
|
530 # used to find memory leaks in dbusmonitor and VeDbusItemImport
|
|
531 import gc
|
|
532 gc.collect()
|
|
533 objects = gc.get_objects()
|
|
534 print (len([o for o in objects if type(o).__name__ == 'VeDbusItemImport']))
|
|
535 print (len([o for o in objects if type(o).__name__ == 'SignalMatch']))
|
|
536 print (len(objects))
|
|
537
|
|
538
|
|
539 def print_values(dbusmonitor):
|
|
540 a = dbusmonitor.get_value('wrongservice', '/DbusInvalid', default_value=1000)
|
|
541 b = dbusmonitor.get_value('com.victronenergy.dummyservice.ttyO1', '/NotInTheMonitorList', default_value=1000)
|
|
542 c = dbusmonitor.get_value('com.victronenergy.dummyservice.ttyO1', '/DbusInvalid', default_value=1000)
|
|
543 d = dbusmonitor.get_value('com.victronenergy.dummyservice.ttyO1', '/NonExistingButMonitored', default_value=1000)
|
|
544
|
|
545 print ("All should be 1000: Wrong Service: %s, NotInTheMonitorList: %s, DbusInvalid: %s, NonExistingButMonitored: %s" % (a, b, c, d))
|
|
546 return True
|
|
547
|
|
548 # We have a mainloop, but that is just for developing this code. Normally above class & code is used from
|
|
549 # some other class, such as vrmLogger or the pubsub Implementation.
|
|
550 def main():
|
|
551 # Init logging
|
|
552 logging.basicConfig(level=logging.DEBUG)
|
|
553 logger.info(__file__ + " is starting up")
|
|
554
|
|
555 # Have a mainloop, so we can send/receive asynchronous calls to and from dbus
|
|
556 DBusGMainLoop(set_as_default=True)
|
|
557
|
|
558 import os
|
|
559 import sys
|
|
560 sys.path.insert(1, os.path.join(os.path.dirname(__file__), '../../'))
|
|
561
|
|
562 dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None}
|
|
563 monitorlist = {'com.victronenergy.dummyservice': {
|
|
564 '/Connected': dummy,
|
|
565 '/ProductName': dummy,
|
|
566 '/Mgmt/Connection': dummy,
|
|
567 '/Dc/0/Voltage': dummy,
|
|
568 '/Dc/0/Current': dummy,
|
|
569 '/Dc/0/Temperature': dummy,
|
|
570 '/Load/I': dummy,
|
|
571 '/FirmwareVersion': dummy,
|
|
572 '/DbusInvalid': dummy,
|
|
573 '/NonExistingButMonitored': dummy}}
|
|
574
|
|
575 d = DbusMonitor(monitorlist, value_changed_on_dbus,
|
|
576 deviceAddedCallback=nameownerchange, deviceRemovedCallback=nameownerchange)
|
|
577
|
|
578 # logger.info("==configchange values==")
|
|
579 # logger.info(pprint.pformat(d.get_values(['configChange'])))
|
|
580
|
|
581 # logger.info("==onIntervalAlways and onIntervalOnlyWhenChanged==")
|
|
582 # logger.info(pprint.pformat(d.get_values(['onIntervalAlways', 'onIntervalAlwaysAndOnEvent'])))
|
|
583
|
|
584 GLib.timeout_add(1000, print_values, d)
|
|
585
|
|
586 # Start and run the mainloop
|
|
587 logger.info("Starting mainloop, responding on only events")
|
|
588 mainloop = GLib.MainLoop()
|
|
589 mainloop.run()
|
|
590
|
|
591 if __name__ == "__main__":
|
|
592 main()
|