8
|
1 import dbus
|
|
2 import logging
|
|
3 import time
|
|
4 from functools import partial
|
|
5
|
|
6 # Local imports
|
|
7 from vedbus import VeDbusItemImport
|
|
8
|
|
9 ## Indexes for the setting dictonary.
|
|
10 PATH = 0
|
|
11 VALUE = 1
|
|
12 MINIMUM = 2
|
|
13 MAXIMUM = 3
|
|
14 SILENT = 4
|
|
15
|
|
16 ## The Settings Device class.
|
|
17 # Used by python programs, such as the vrm-logger, to read and write settings they
|
|
18 # need to store on disk. And since these settings might be changed from a different
|
|
19 # source, such as the GUI, the program can pass an eventCallback that will be called
|
|
20 # as soon as some setting is changed.
|
|
21 #
|
|
22 # The settings are stored in flash via the com.victronenergy.settings service on dbus.
|
|
23 # See https://github.com/victronenergy/localsettings for more info.
|
|
24 #
|
|
25 # If there are settings in de supportSettings list which are not yet on the dbus,
|
|
26 # and therefore not yet in the xml file, they will be added through the dbus-addSetting
|
|
27 # interface of com.victronenergy.settings.
|
|
28 class SettingsDevice(object):
|
|
29 ## The constructor processes the tree of dbus-items.
|
|
30 # @param bus the system-dbus object
|
|
31 # @param name the dbus-service-name of the settings dbus service, 'com.victronenergy.settings'
|
|
32 # @param supportedSettings dictionary with all setting-names, and their defaultvalue, min, max and whether
|
|
33 # the setting is silent. The 'silent' entry is optional. If set to true, no changes in the setting will
|
|
34 # be logged by localsettings.
|
|
35 # @param eventCallback function that will be called on changes on any of these settings
|
|
36 # @param timeout Maximum interval to wait for localsettings. An exception is thrown at the end of the
|
|
37 # interval if the localsettings D-Bus service has not appeared yet.
|
|
38 def __init__(self, bus, supportedSettings, eventCallback, name='com.victronenergy.settings', timeout=0):
|
|
39 logging.debug("===== Settings device init starting... =====")
|
|
40 self._bus = bus
|
|
41 self._dbus_name = name
|
|
42 self._eventCallback = eventCallback
|
|
43 self._values = {} # stored the values, used to pass the old value along on a setting change
|
|
44 self._settings = {}
|
|
45
|
|
46 count = 0
|
|
47 while True:
|
|
48 if 'com.victronenergy.settings' in self._bus.list_names():
|
|
49 break
|
|
50 if count == timeout:
|
|
51 raise Exception("The settings service com.victronenergy.settings does not exist!")
|
|
52 count += 1
|
|
53 logging.info('waiting for settings')
|
|
54 time.sleep(1)
|
|
55
|
|
56 # Add the items.
|
|
57 self.addSettings(supportedSettings)
|
|
58
|
|
59 logging.debug("===== Settings device init finished =====")
|
|
60
|
|
61 def addSettings(self, settings):
|
|
62 for setting, options in settings.items():
|
|
63 silent = len(options) > SILENT and options[SILENT]
|
|
64 busitem = self.addSetting(options[PATH], options[VALUE],
|
|
65 options[MINIMUM], options[MAXIMUM], silent, callback=partial(self.handleChangedSetting, setting))
|
|
66 self._settings[setting] = busitem
|
|
67 self._values[setting] = busitem.get_value()
|
|
68
|
|
69 def addSetting(self, path, value, _min, _max, silent=False, callback=None):
|
|
70 busitem = VeDbusItemImport(self._bus, self._dbus_name, path, callback)
|
|
71 if busitem.exists and (value, _min, _max, silent) == busitem._proxy.GetAttributes():
|
|
72 logging.debug("Setting %s found" % path)
|
|
73 else:
|
|
74 logging.info("Setting %s does not exist yet or must be adjusted" % path)
|
|
75
|
|
76 # Prepare to add the setting. Most dbus types extend the python
|
|
77 # type so it is only necessary to additionally test for Int64.
|
|
78 if isinstance(value, (int, dbus.Int64)):
|
|
79 itemType = 'i'
|
|
80 elif isinstance(value, float):
|
|
81 itemType = 'f'
|
|
82 else:
|
|
83 itemType = 's'
|
|
84
|
|
85 # Add the setting
|
|
86 # TODO, make an object that inherits VeDbusItemImport, and complete the D-Bus settingsitem interface
|
|
87 settings_item = VeDbusItemImport(self._bus, self._dbus_name, '/Settings', createsignal=False)
|
|
88 setting_path = path.replace('/Settings/', '', 1)
|
|
89 if silent:
|
|
90 settings_item._proxy.AddSilentSetting('', setting_path, value, itemType, _min, _max)
|
|
91 else:
|
|
92 settings_item._proxy.AddSetting('', setting_path, value, itemType, _min, _max)
|
|
93
|
|
94 busitem = VeDbusItemImport(self._bus, self._dbus_name, path, callback)
|
|
95
|
|
96 return busitem
|
|
97
|
|
98 def handleChangedSetting(self, setting, servicename, path, changes):
|
|
99 oldvalue = self._values[setting] if setting in self._values else None
|
|
100 self._values[setting] = changes['Value']
|
|
101
|
|
102 if self._eventCallback is None:
|
|
103 return
|
|
104
|
|
105 self._eventCallback(setting, oldvalue, changes['Value'])
|
|
106
|
|
107 def setDefault(self, path):
|
|
108 item = VeDbusItemImport(self._bus, self._dbus_name, path, createsignal=False)
|
|
109 item.set_default()
|
|
110
|
|
111 def __getitem__(self, setting):
|
|
112 return self._settings[setting].get_value()
|
|
113
|
|
114 def __setitem__(self, setting, newvalue):
|
|
115 result = self._settings[setting].set_value(newvalue)
|
|
116 if result != 0:
|
|
117 # Trying to make some false change to our own settings? How dumb!
|
|
118 assert False
|