def addSetting(self, path, value, _min, _max, silent=False, callback=None): busitem = VeDbusItemImport(self._bus, self._dbus_name, path, callback) if busitem.exists and busitem._proxy.GetSilent() == silent: logging.debug("Setting %s found" % path) else: logging.info("Setting %s does not exist yet or must be adjusted" % path) # Prepare to add the setting. Most dbus types extend the python # type so it is only necessary to additionally test for Int64. if isinstance(value, (int, dbus.Int64)): itemType = 'i' elif isinstance(value, float): itemType = 'f' else: itemType = 's' # Add the setting # TODO, make an object that inherits VeDbusItemImport, and complete the D-Bus settingsitem interface settings_item = VeDbusItemImport(self._bus, self._dbus_name, '/Settings', createsignal=False) setting_path = path.replace('/Settings/', '', 1) if silent: settings_item._proxy.AddSilentSetting('', setting_path, value, itemType, _min, _max) else: settings_item._proxy.AddSetting('', setting_path, value, itemType, _min, _max) busitem = VeDbusItemImport(self._bus, self._dbus_name, path, callback) return busitem
def _evaluate_if_we_are_needed(self): if self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/Relay/Function') == 3: if self._dbusservice is None: logger.info('Action! Going on dbus and taking control of the relay.') relay_polarity_import = VeDbusItemImport( bus=self._bus, serviceName='com.victronenergy.settings', path='/Settings/Relay/Polarity', eventCallback=None, createsignal=True) if not self._relay_state_import: logger.info('Getting relay from systemcalc.') try: self._relay_state_import = VeDbusItemImport( bus=self._bus, serviceName='com.victronenergy.system', path='/Relay/0/State', eventCallback=None, createsignal=True) except dbus.exceptions.DBusException: logger.info('Systemcalc relay not available.') self._relay_state_import = None pass # As is not possible to keep the relay state during the CCGX power cycles, # set the relay polarity to normally open. if relay_polarity_import.get_value() == 1: relay_polarity_import.set_value(0) logger.info('Setting relay polarity to normally open.') # put ourselves on the dbus self._dbusservice = VeDbusService('com.victronenergy.pump.startstop0') self._dbusservice.add_mandatory_paths( processname=__file__, processversion=softwareversion, connection='pump', deviceinstance=0, productid=None, productname=None, firmwareversion=None, hardwareversion=None, connected=1) # State: None = invalid, 0 = stopped, 1 = running self._dbusservice.add_path('/State', value=0) self._dbusservice.add_path('/AvailableTankServices', value=None) self._dbusservice.add_path('/ActiveTankService', value=None) self._update_relay() self._handleservicechange() else: if self._dbusservice is not None: self._stop_pump() self._dbusservice.__del__() self._dbusservice = None self._relay_state_import = None logger.info('Relay function is no longer set to pump startstop: made sure pump is off and going off dbus')
def __init__(self, fpath, bus, serviceName, metrics): self.metrics = metrics self.metric_imports = {} self.metric_values = {} self.bus = bus self.fpath = os.path.join(os.path.dirname(fpath), "by-minute-%s" % os.path.basename(fpath)) if os.path.isfile(fpath): self.tf = TeaFile.openwrite(fpath) else: logging.debug('%s: fields=%s types=%s' % (fpath, ' '.join( [m.name() for m in metrics]), ''.join([m.datatype() for m in metrics]))) now = time.gmtime() self.tf = TeaFile.create(fpath, ' '.join([m.name() for m in metrics]), ''.join([m.datatype() for m in metrics]), serviceName, { 'year': now.tm_year, 'month': now.tm_mon, 'day': now.tm_mday, }) self.startdate = datetime.date( int(self.tf.description.namevalues['year']), int(self.tf.description.namevalues['month']), int(self.tf.description.namevalues['day'])) for m in metrics: logging.debug("Watch %s%s" % (serviceName, m.path())) self.metric_imports[m.path()] = VeDbusItemImport( self.bus, serviceName, m.path(), lambda x, y, z: self.import_value_changed(m, x, y, z)) self.install_update()
def __init__(self, bus, supportedSettings, eventCallback, name='com.victronenergy.settings', timeout=0): logging.debug("===== Settings device init starting... =====") self._bus = bus self._dbus_name = name self._eventCallback = eventCallback self._supportedSettings = supportedSettings self._values = {} # stored the values, used to pass the old value along on a setting change self._settings = {} count = 0 while True: if 'com.victronenergy.settings' in self._bus.list_names(): break if count == timeout: raise Exception("The settings service com.victronenergy.settings does not exist!") count += 1 logging.info('waiting for settings') time.sleep(1) # Add the items. for setting, options in self._supportedSettings.items(): busitem = VeDbusItemImport(self._bus, self._dbus_name, options[PATH], self.handleChangedSetting) if busitem.exists: logging.debug("Setting %s found" % options[PATH]) else: logging.info("Setting %s does not exist yet, adding it" % options[PATH]) # Prepare to add the setting. path = options[PATH].replace('/Settings/', '', 1) value = options[VALUE] if type(value) == int or type(value) == dbus.Int16 or type(value) == dbus.Int32 or type(value) == dbus.Int64: itemType = 'i' elif type(value) == float or type(value) == dbus.Double: itemType = 'f' else: itemType = 's' # Add the setting # TODO, make an object that inherits VeDbusItemImport, and complete the D-Bus settingsitem interface VeDbusItemImport(self._bus, self._dbus_name, '/Settings', createsignal=False)._proxy.AddSetting('', path, value, itemType, options[MINIMUM], options[MAXIMUM]) busitem = VeDbusItemImport(self._bus, self._dbus_name, options[PATH], self.handleChangedSetting) self._settings[setting] = busitem self._values[setting] = busitem.get_value() logging.debug("===== Settings device init finished =====")
def setvalues(self, inputpower): VeDbusItemImport( bus=self.bus, serviceName=self.DbusServices['AcSetpoint']['Service'], path=self.DbusServices['AcSetpoint']['Path'], eventCallback=None, createsignal=False).set_value(inputpower)
def setrelay(self, relayvalue): VeDbusItemImport( bus=self.bus, serviceName=self.DbusServices['CCGXRelay']['Service'], path=self.DbusServices['CCGXRelay']['Path'], eventCallback=None, createsignal=False).set_value(relayvalue)
def _add_setting(self, group, setting, value, type, minimum, maximum, rpc_name='AddSetting'): item = VeDbusItemImport(self._dbus, 'com.victronenergy.settings', '/Settings', createsignal=False) return item._proxy.get_dbus_method(rpc_name)(group, setting, value, type, minimum, maximum)
def add_new_setting_creates_signal(self, rpc_name='AddSetting'): details = { 'group': 'g', 'setting': 's', 'value': 100, 'type': 'i', 'min': 0, 'max': 0 } monitor = VeDbusItemImport(self._dbus, 'com.victronenergy.settings', '/Settings/' + details['group'] + '/' + details['setting'], eventCallback=self._call_me, createsignal=True) self.updateSettingsStamp() self._add_setting(details['group'], details['setting'], details['value'], details['type'], details['min'], details['max']) self.waitForSettingsStored() # restart localsettings self._stopLocalSettings() self._startLocalSettings() # manually iterate the mainloop main_context = GLib.MainContext.default() while main_context.pending(): main_context.iteration(False) self.assertEqual(self._called, [ 'com.victronenergy.settings', '/Settings/' + details['group'] + '/' + details['setting'], { 'Text': '100', 'Value': 100, 'Min': 0, 'Max': 0, 'Default': 100 } ])
def getvalues(self): for service in self.DbusServices: try: self.DbusServices[service]['Value'] = VeDbusItemImport( bus=self.bus, serviceName=self.DbusServices[service]['Service'], path=self.DbusServices[service]['Path'], eventCallback=None, createsignal=False).get_value() # print 'New value of ', self.DbusServices[service]['Value'], 'for', service except dbus.DBusException: print 'Error with DBus' print service try: self.DbusServices[service]['Value'] *= 1 self.DbusServices[service]['Value'] = max(self.DbusServices[service]['Value'], -5000) except: if service == 'L1Power' or service == 'L2Power' or service == 'L3Power': self.DbusServices[service]['Value'] = 0
def test_change_max_creates_signal(self): details = { 'group': 'g', 'setting': 'f', 'value': 103.0, 'type': 'f', 'min': 2.0, 'max': 1002.0 } self._called = [] monitor = VeDbusItemImport(self._dbus, 'com.victronenergy.settings', '/Settings/' + details['group'] + '/' + details['setting'], eventCallback=self._call_me, createsignal=True) self._add_setting(details['group'], details['setting'], details['value'], details['type'], details['min'], details['max']) # manually iterate the mainloop main_context = GLib.MainContext.default() while main_context.pending(): main_context.iteration(False) self.assertEqual(self._called, [ 'com.victronenergy.settings', '/Settings/' + details['group'] + '/' + details['setting'], { 'Default': 103.0, 'Text': '103.0', 'Min': '2.0', 'Max': '1002.0', 'Value': 103 } ])
def __init__(self, bus, supportedSettings, eventCallback, name='com.victronenergy.settings', timeout=0): logging.debug("===== Settings device init starting... =====") self._bus = bus self._dbus_name = name self._eventCallback = eventCallback self._supportedSettings = supportedSettings self._values = { } # stored the values, used to pass the old value along on a setting change self._settings = {} count = 0 while True: if 'com.victronenergy.settings' in self._bus.list_names(): break if count == timeout: raise Exception( "The settings service com.victronenergy.settings does not exist!" ) count += 1 logging.info('waiting for settings') time.sleep(1) # Add the items. for setting, options in self._supportedSettings.items(): busitem = VeDbusItemImport(self._bus, self._dbus_name, options[PATH], self.handleChangedSetting) silent = len(options) > SILENT and options[SILENT] if busitem.exists and busitem._proxy.GetSilent() == silent: logging.debug("Setting %s found" % options[PATH]) else: logging.info( "Setting %s does not exist yet or must be adjusted" % options[PATH]) # Prepare to add the setting. path = options[PATH].replace('/Settings/', '', 1) value = options[VALUE] if type(value) == int or type(value) == dbus.Int16 or type( value) == dbus.Int32 or type(value) == dbus.Int64: itemType = 'i' elif type(value) == float or type(value) == dbus.Double: itemType = 'f' else: itemType = 's' # Add the setting # TODO, make an object that inherits VeDbusItemImport, and complete the D-Bus settingsitem interface settings_item = VeDbusItemImport(self._bus, self._dbus_name, '/Settings', createsignal=False) if silent: settings_item._proxy.AddSilentSetting( '', path, value, itemType, options[MINIMUM], options[MAXIMUM]) else: settings_item._proxy.AddSetting('', path, value, itemType, options[MINIMUM], options[MAXIMUM]) busitem = VeDbusItemImport(self._bus, self._dbus_name, options[PATH], self.handleChangedSetting) self._settings[setting] = busitem self._values[setting] = busitem.get_value() logging.debug("===== Settings device init finished =====")
def add_new_settings_and_readback(self, rpc_name='AddSetting'): from collections import OrderedDict testsets = OrderedDict() testsets['int-no-min-max'] = { 'group': 'g', 'setting': 'in', 'default': 100, 'value': 100, 'type': 'i', 'min': 0, 'max': 0 } testsets['int-with-min-max'] = { 'group': 'g', 'setting': 'iw', 'default': 101, 'value': 101, 'type': 'i', 'min': 0, 'max': 101 } # float-no-min-max doesn't work, because localsettings does not recognize 0.0 and 0.0 as no min max, only 0 and 0 works. # testsets['float-no-min-max'] = {'group': 'g', 'setting': 'f', 'default': 102.0, 'value': 102.0, 'type': 'f', 'min': 0.0, 'max': 0.0} testsets['float-with-min-max'] = { 'group': 'g', 'setting': 'f', 'default': 103.0, 'value': 103.0, 'type': 'f', 'min': 0.0, 'max': 1000.0 } testsets['start-group-with-digit'] = { 'group': '0g', 'setting': 's', 'default': 104, 'value': 104, 'type': 'i', 'min': 0, 'max': 0 } testsets['start-setting-with-digit'] = { 'group': 'g', 'setting': '0s', 'default': 105, 'value': 105, 'type': 'i', 'min': 0, 'max': 0 } testsets['int-re-add-same-min-max'] = { 'group': 'g', 'setting': 'in', 'default': 200, 'value': 100, 'type': 'i', 'min': 0, 'max': 0 } testsets['int-re-add-other-min-max'] = { 'group': 'g', 'setting': 'in', 'default': 201, 'value': 100, 'type': 'i', 'min': 10, 'max': 1000 } testsets['float-re-add-other-min-max'] = { 'group': 'g', 'setting': 'f', 'default': 103.0, 'value': 103.0, 'type': 'f', 'min': 1.0, 'max': 1001.0 } for name, details in testsets.iteritems(): print "\n\n===Testing %s===\n" % name self.updateSettingsStamp() setting = details['setting'] + '/' + rpc_name self.assertEqual( 0, self._add_setting(details['group'], setting, details['default'], details['type'], details['min'], details['max'], rpc_name=rpc_name)) self.waitForSettingsStored() # restart localsettings self._stopLocalSettings() self._startLocalSettings() # read the results i = VeDbusItemImport(self._dbus, 'com.victronenergy.settings', '/Settings/' + details['group'] + '/' + setting, eventCallback=None, createsignal=False) result = copy.deepcopy(details) result['value'] = i.get_value().real result['default'] = i._proxy.GetDefault().real # don't ask me why, but getMin() and getMax() return a string... result['min'] = int( i._proxy.GetMin()) if details['type'] == 'i' else float( i._proxy.GetMin()) result['max'] = int( i._proxy.GetMax()) if details['type'] == 'i' else float( i._proxy.GetMax()) # don't check the type, as there is no GetType() available # result['type'] = i._proxy.GetType() self.assertEqual(details, result)
class DbusGenerator: def __init__(self, retries=300): self._bus = dbus.SystemBus() if ( platform.machine() == 'armv7l' or 'DBUS_SESSION_BUS_ADDRESS' not in environ) else dbus.SessionBus() self.RELAY_GPIO_FILE = '/sys/class/gpio/gpio182/value' self.HISTORY_DAYS = 30 # One second per retry self.RETRIES_ON_ERROR = retries self._testrun_soc_retries = 0 self._last_counters_check = 0 self._dbusservice = None self._starttime = 0 self._manualstarttimer = 0 self._last_runtime_update = 0 self._timer_runnning = 0 self._battery_measurement_voltage_import = None self._battery_measurement_current_import = None self._battery_measurement_soc_import = None self._battery_measurement_available = True self._vebusservice_high_temperature_import = None self._vebusservice_overload_import = None self._vebusservice = None self._vebusservice_available = False self._relay_state_import = None self._condition_stack = { 'batteryvoltage': { 'name': 'batteryvoltage', 'reached': False, 'boolean': False, 'timed': True, 'start_timer': 0, 'stop_timer': 0, 'valid': True, 'enabled': False, 'retries': 0, 'monitoring': 'battery' }, 'batterycurrent': { 'name': 'batterycurrent', 'reached': False, 'boolean': False, 'timed': True, 'start_timer': 0, 'stop_timer': 0, 'valid': True, 'enabled': False, 'retries': 0, 'monitoring': 'battery' }, 'acload': { 'name': 'acload', 'reached': False, 'boolean': False, 'timed': True, 'start_timer': 0, 'stop_timer': 0, 'valid': True, 'enabled': False, 'retries': 0, 'monitoring': 'vebus' }, 'inverterhightemp': { 'name': 'inverterhightemp', 'reached': False, 'boolean': True, 'timed': True, 'start_timer': 0, 'stop_timer': 0, 'valid': True, 'enabled': False, 'retries': 0, 'monitoring': 'vebus' }, 'inverteroverload': { 'name': 'inverteroverload', 'reached': False, 'boolean': True, 'timed': True, 'start_timer': 0, 'stop_timer': 0, 'valid': True, 'enabled': False, 'retries': 0, 'monitoring': 'vebus' }, 'soc': { 'name': 'soc', 'reached': False, 'boolean': False, 'timed': False, 'valid': True, 'enabled': False, 'retries': 0, 'monitoring': 'battery' } } # DbusMonitor expects these values to be there, even though we don need them. So just # add some dummy data. This can go away when DbusMonitor is more generic. dummy = { 'code': None, 'whenToLog': 'configChange', 'accessLevel': None } # TODO: possible improvement: don't use the DbusMonitor it all, since we are only monitoring # a set of static values which will always be available. DbusMonitor watches for services # that come and go, and takes care of automatic signal subscribtions etc. etc: all not necessary # in this use case where we have fixed services names (com.victronenergy.settings, and c # com.victronenergy.system). self._dbusmonitor = DbusMonitor({ 'com.victronenergy.settings': { # This is not our setting so do it here. not in supportedSettings '/Settings/Relay/Function': dummy, '/Settings/Relay/Polarity': dummy, '/Settings/System/TimeZone': dummy, }, 'com.victronenergy.system': { # This is not our setting so do it here. not in supportedSettings '/Ac/Consumption/Total/Power': dummy, '/Ac/PvOnOutput/Total/Power': dummy, '/Ac/PvOnGrid/Total/Power': dummy, '/Ac/PvOnGenset/Total/Power': dummy, '/Dc/Pv/Power': dummy, '/AutoSelectedBatteryMeasurement': dummy, } }, self._dbus_value_changed, self._device_added, self._device_removed) # Set timezone to user selected timezone environ['TZ'] = self._dbusmonitor.get_value( 'com.victronenergy.settings', '/Settings/System/TimeZone') # Connect to localsettings self._settings = SettingsDevice( bus=self._bus, supportedSettings={ 'autostart': ['/Settings/Generator0/AutoStartEnabled', 1, 0, 1], 'accumulateddaily': ['/Settings/Generator0/AccumulatedDaily', '', 0, 0], 'accumulatedtotal': ['/Settings/Generator0/AccumulatedTotal', 0, 0, 0], 'batterymeasurement': ['/Settings/Generator0/BatteryService', "default", 0, 0], 'minimumruntime': ['/Settings/Generator0/MinimumRuntime', 0, 0, 86400], # minutes # On permanent loss of communication: 0 = Stop, 1 = Start, 2 = keep running 'onlosscommunication': ['/Settings/Generator0/OnLossCommunication', 0, 0, 2], # Quiet hours 'quiethoursenabled': ['/Settings/Generator0/QuietHours/Enabled', 0, 0, 1], 'quiethoursstarttime': ['/Settings/Generator0/QuietHours/StartTime', 75600, 0, 86400], 'quiethoursendtime': ['/Settings/Generator0/QuietHours/EndTime', 21600, 0, 86400], # SOC 'socenabled': ['/Settings/Generator0/Soc/Enabled', 0, 0, 1], 'socstart': ['/Settings/Generator0/Soc/StartValue', 90, 0, 100], 'socstop': ['/Settings/Generator0/Soc/StopValue', 90, 0, 100], 'qh_socstart': ['/Settings/Generator0/Soc/QuietHoursStartValue', 90, 0, 100], 'qh_socstop': ['/Settings/Generator0/Soc/QuietHoursStopValue', 90, 0, 100], # Voltage 'batteryvoltageenabled': [ '/Settings/Generator0/BatteryVoltage/Enabled', 0, 0, 1 ], 'batteryvoltagestart': [ '/Settings/Generator0/BatteryVoltage/StartValue', 11.5, 0, 150 ], 'batteryvoltagestop': [ '/Settings/Generator0/BatteryVoltage/StopValue', 12.4, 0, 150 ], 'batteryvoltagestarttimer': [ '/Settings/Generator0/BatteryVoltage/StartTimer', 20, 0, 10000 ], 'batteryvoltagestoptimer': [ '/Settings/Generator0/BatteryVoltage/StopTimer', 20, 0, 10000 ], 'qh_batteryvoltagestart': [ '/Settings/Generator0/BatteryVoltage/QuietHoursStartValue', 11.9, 0, 100 ], 'qh_batteryvoltagestop': [ '/Settings/Generator0/BatteryVoltage/QuietHoursStopValue', 12.4, 0, 100 ], # Current 'batterycurrentenabled': [ '/Settings/Generator0/BatteryCurrent/Enabled', 0, 0, 1 ], 'batterycurrentstart': [ '/Settings/Generator0/BatteryCurrent/StartValue', 10.5, 0.5, 1000 ], 'batterycurrentstop': [ '/Settings/Generator0/BatteryCurrent/StopValue', 5.5, 0, 1000 ], 'batterycurrentstarttimer': [ '/Settings/Generator0/BatteryCurrent/StartTimer', 20, 0, 10000 ], 'batterycurrentstoptimer': [ '/Settings/Generator0/BatteryCurrent/StopTimer', 20, 0, 10000 ], 'qh_batterycurrentstart': [ '/Settings/Generator0/BatteryCurrent/QuietHoursStartValue', 20.5, 0, 1000 ], 'qh_batterycurrentstop': [ '/Settings/Generator0/BatteryCurrent/QuietHoursStopValue', 15.5, 0, 1000 ], # AC load 'acloadenabled': [ '/Settings/Generator0/AcLoad/Enabled', 0, 0, 1 ], 'acloadstart': [ '/Settings/Generator0/AcLoad/StartValue', 1600, 5, 100000 ], 'acloadstop': [ '/Settings/Generator0/AcLoad/StopValue', 800, 0, 100000 ], 'acloadstarttimer': [ '/Settings/Generator0/AcLoad/StartTimer', 20, 0, 10000 ], 'acloadstoptimer': [ '/Settings/Generator0/AcLoad/StopTimer', 20, 0, 10000 ], 'qh_acloadstart': [ '/Settings/Generator0/AcLoad/QuietHoursStartValue', 1900, 0, 100000 ], 'qh_acloadstop': [ '/Settings/Generator0/AcLoad/QuietHoursStopValue', 1200, 0, 100000 ], # VE.Bus high temperature 'inverterhightempenabled': [ '/Settings/Generator0/InverterHighTemp/Enabled', 0, 0, 1 ], 'inverterhightempstarttimer': [ '/Settings/Generator0/InverterHighTemp/StartTimer', 20, 0, 10000 ], 'inverterhightempstoptimer': [ '/Settings/Generator0/InverterHighTemp/StopTimer', 20, 0, 10000 ], # VE.Bus overload 'inverteroverloadenabled': [ '/Settings/Generator0/InverterOverload/Enabled', 0, 0, 1 ], 'inverteroverloadstarttimer': [ '/Settings/Generator0/InverterOverload/StartTimer', 20, 0, 10000 ], 'inverteroverloadstoptimer': [ '/Settings/Generator0/InverterOverload/StopTimer', 20, 0, 10000 ], # TestRun 'testrunenabled': [ '/Settings/Generator0/TestRun/Enabled', 0, 0, 1 ], 'testrunstartdate': [ '/Settings/Generator0/TestRun/StartDate', time.time(), 0, 10000000000.1 ], 'testrunstarttimer': [ '/Settings/Generator0/TestRun/StartTime', 54000, 0, 86400 ], 'testruninterval': [ '/Settings/Generator0/TestRun/Interval', 28, 1, 365 ], 'testrunruntime': [ '/Settings/Generator0/TestRun/Duration', 7200, 1, 86400 ], 'testrunskipruntime': [ '/Settings/Generator0/TestRun/SkipRuntime', 0, 0, 100000 ], 'testruntillbatteryfull': [ '/Settings/Generator0/TestRun/RunTillBatteryFull', 0, 0, 1 ] }, eventCallback=self._handle_changed_setting) # Whenever services come or go, we need to check if it was a service we use. Note that this # is a bit double: DbusMonitor does the same thing. But since we don't use DbusMonitor to # monitor for com.victronenergy.battery, .vebus, .charger or any other possible source of # battery data, it is necessary to monitor for changes in the available dbus services. self._bus.add_signal_receiver(self._dbus_name_owner_changed, signal_name='NameOwnerChanged') self._evaluate_if_we_are_needed() gobject.timeout_add(1000, self._handletimertick) self._update_relay() self._changed = True def _evaluate_if_we_are_needed(self): if self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/Relay/Function') == 1: if not self._relay_state_import: logger.info('Getting relay from systemcalc.') try: self._relay_state_import = VeDbusItemImport( bus=self._bus, serviceName='com.victronenergy.system', path='/Relay/0/State', eventCallback=None, createsignal=True) except dbus.exceptions.DBusException: logger.info('Systemcalc relay not available.') self._relay_state_import = None pass if self._dbusservice is None: logger.info( 'Action! Going on dbus and taking control of the relay.') relay_polarity_import = VeDbusItemImport( bus=self._bus, serviceName='com.victronenergy.settings', path='/Settings/Relay/Polarity', eventCallback=None, createsignal=True) # As is not possible to keep the relay state during the CCGX power cycles, # set the relay polarity to normally open. if relay_polarity_import.get_value() == 1: relay_polarity_import.set_value(0) logger.info('Setting relay polarity to normally open.') # put ourselves on the dbus self._dbusservice = VeDbusService( 'com.victronenergy.generator.startstop0') self._dbusservice.add_mandatory_paths( processname=__file__, processversion=softwareversion, connection='generator', deviceinstance=0, productid=None, productname=None, firmwareversion=None, hardwareversion=None, connected=1) # State: None = invalid, 0 = stopped, 1 = running self._dbusservice.add_path('/State', value=0) # Condition that made the generator start self._dbusservice.add_path('/RunningByCondition', value='') # Runtime self._dbusservice.add_path('/Runtime', value=0, gettextcallback=self._gettext) # Today runtime self._dbusservice.add_path('/TodayRuntime', value=0, gettextcallback=self._gettext) # Test run runtime self._dbusservice.add_path( '/TestRunIntervalRuntime', value=self._interval_runtime( self._settings['testruninterval']), gettextcallback=self._gettext) # Next tes trun date, values is 0 for test run disabled self._dbusservice.add_path('/NextTestRun', value=None, gettextcallback=self._gettext) # Next tes trun is needed 1, not needed 0 self._dbusservice.add_path('/SkipTestRun', value=None) # Manual start self._dbusservice.add_path('/ManualStart', value=0, writeable=True) # Manual start timer self._dbusservice.add_path('/ManualStartTimer', value=0, writeable=True) # Silent mode active self._dbusservice.add_path('/QuietHours', value=0) self._determineservices() else: if self._dbusservice is not None: self._stop_generator() self._dbusservice.__del__() self._dbusservice = None # Reset conditions for condition in self._condition_stack: self._reset_condition(self._condition_stack[condition]) logger.info( 'Relay function is no longer set to generator start/stop: made sure generator is off ' + 'and now going off dbus') self._relay_state_import = None def _device_added(self, dbusservicename, instance): self._evaluate_if_we_are_needed() self._determineservices() def _device_removed(self, dbusservicename, instance): self._evaluate_if_we_are_needed() # Relay handling depends on systemcalc, if the service disappears restart # the relay state import if dbusservicename == "com.victronenergy.system": self._relay_state_import = None self._determineservices() def _dbus_value_changed(self, dbusServiceName, dbusPath, options, changes, deviceInstance): if dbusPath == '/AutoSelectedBatteryMeasurement' and self._settings[ 'batterymeasurement'] == 'default': self._determineservices() if dbusPath == '/Settings/Relay/Function': self._evaluate_if_we_are_needed() self._changed = True # Update relay state when polarity is changed if dbusPath == '/Settings/Relay/Polarity': self._update_relay() def _handle_changed_setting(self, setting, oldvalue, newvalue): self._changed = True self._evaluate_if_we_are_needed() if setting == 'batterymeasurement': self._determineservices() # Reset retries and valid if service changes for condition in self._condition_stack: if self._condition_stack[condition]['monitoring'] == 'battery': self._condition_stack[condition]['valid'] = True self._condition_stack[condition]['retries'] = 0 if setting == 'autostart': logger.info('Autostart function %s.' % ('enabled' if newvalue == 1 else 'disabled')) if self._dbusservice is not None and setting == 'testruninterval': self._dbusservice[ '/TestRunIntervalRuntime'] = self._interval_runtime( self._settings['testruninterval']) def _dbus_name_owner_changed(self, name, oldowner, newowner): self._determineservices() def _gettext(self, path, value): if path == '/NextTestRun': # Locale format date d = datetime.datetime.fromtimestamp(value) return d.strftime('%c') elif path in ['/Runtime', '/TestRunIntervalRuntime', '/TodayRuntime']: m, s = divmod(value, 60) h, m = divmod(m, 60) return '%dh, %dm, %ds' % (h, m, s) else: return value def _handletimertick(self): # try catch, to make sure that we kill ourselves on an error. Without this try-catch, there would # be an error written to stdout, and then the timer would not be restarted, resulting in a dead- # lock waiting for manual intervention -> not good! try: if self._dbusservice is not None: self._evaluate_startstop_conditions() self._changed = False except: self._stop_generator() import traceback traceback.print_exc() sys.exit(1) return True def _evaluate_startstop_conditions(self): # Conditions will be evaluated in this order conditions = [ 'soc', 'acload', 'batterycurrent', 'batteryvoltage', 'inverterhightemp', 'inverteroverload' ] start = False runningbycondition = None today = calendar.timegm(datetime.date.today().timetuple()) self._timer_runnning = False values = self._get_updated_values() connection_lost = False self._check_quiet_hours() # New day, register it if self._last_counters_check < today and self._dbusservice[ '/State'] == 0: self._last_counters_check = today self._update_accumulated_time() # Update current and accumulated runtime. if self._dbusservice['/State'] == 1: self._dbusservice['/Runtime'] = int(time.time() - self._starttime) # By performance reasons, accumulated runtime is only updated # once per 10s. When the generator stops is also updated. if self._dbusservice['/Runtime'] - self._last_runtime_update >= 10: self._update_accumulated_time() if self._evaluate_manual_start(): runningbycondition = 'manual' start = True # Autostart conditions will only be evaluated if the autostart functionality is enabled if self._settings['autostart'] == 1: if self._evaluate_testrun_condition(): runningbycondition = 'testrun' start = True # Evaluate value conditions for condition in conditions: start = self._evaluate_condition( self._condition_stack[condition], values[condition]) or start runningbycondition = condition if start and runningbycondition is None else runningbycondition # Connection lost is set to true if the numbear of retries of one or more enabled conditions # >= RETRIES_ON_ERROR if self._condition_stack[condition]['enabled']: connection_lost = self._condition_stack[condition][ 'retries'] >= self.RETRIES_ON_ERROR # If none condition is reached check if connection is lost and start/keep running the generator # depending on '/OnLossCommunication' setting if not start and connection_lost: # Start always if self._settings['onlosscommunication'] == 1: start = True runningbycondition = 'lossofcommunication' # Keep running if generator already started if self._dbusservice['/State'] == 1 and self._settings[ 'onlosscommunication'] == 2: start = True runningbycondition = 'lossofcommunication' if start: self._start_generator(runningbycondition) elif (self._dbusservice['/Runtime'] >= self._settings['minimumruntime'] * 60 or self._dbusservice['/RunningByCondition'] == 'manual'): self._stop_generator() def _reset_condition(self, condition): condition['reached'] = False if condition['timed']: condition['start_timer'] = 0 condition['stop_timer'] = 0 def _check_condition(self, condition, value): name = condition['name'] if self._settings[name + 'enabled'] == 0: if condition['enabled']: condition['enabled'] = False logger.info('Disabling (%s) condition' % name) condition['retries'] = 0 condition['valid'] = True self._reset_condition(condition) return False elif not condition['enabled']: condition['enabled'] = True logger.info('Enabling (%s) condition' % name) if (condition['monitoring'] == 'battery') and (self._settings['batterymeasurement'] == 'nobattery'): return False if value is None and condition['valid']: if condition['retries'] >= self.RETRIES_ON_ERROR: logger.info( 'Error getting (%s) value, skipping evaluation till get a valid value' % name) self._reset_condition(condition) self._comunnication_lost = True condition['valid'] = False else: condition['retries'] += 1 if condition['retries'] == 1 or (condition['retries'] % 10) == 0: logger.info('Error getting (%s) value, retrying(#%i)' % (name, condition['retries'])) return False elif value is not None and not condition['valid']: logger.info('Success getting (%s) value, resuming evaluation' % name) condition['valid'] = True condition['retries'] = 0 # Reset retries if value is valid if value is not None: condition['retries'] = 0 return condition['valid'] def _evaluate_condition(self, condition, value): name = condition['name'] setting = ('qh_' if self._dbusservice['/QuietHours'] == 1 else '') + name startvalue = self._settings[setting + 'start'] if not condition['boolean'] else 1 stopvalue = self._settings[setting + 'stop'] if not condition['boolean'] else 0 # Check if the condition has to be evaluated if not self._check_condition(condition, value): # If generator is started by this condition and value is invalid # wait till RETRIES_ON_ERROR to skip the condition if condition['reached'] and condition[ 'retries'] <= self.RETRIES_ON_ERROR: return True return False # As this is a generic evaluation method, we need to know how to compare the values # first check if start value should be greater than stop value and then compare start_is_greater = startvalue > stopvalue # When the condition is already reached only the stop value can set it to False start = condition['reached'] or ( value >= startvalue if start_is_greater else value <= startvalue) stop = value <= stopvalue if start_is_greater else value >= stopvalue # Timed conditions must start/stop after the condition has been reached for a minimum # time. if condition['timed']: if not condition['reached'] and start: condition['start_timer'] += time.time( ) if condition['start_timer'] == 0 else 0 start = time.time( ) - condition['start_timer'] >= self._settings[name + 'starttimer'] condition['stop_timer'] *= int(not start) self._timer_runnning = True else: condition['start_timer'] = 0 if condition['reached'] and stop: condition['stop_timer'] += time.time( ) if condition['stop_timer'] == 0 else 0 stop = time.time() - condition['stop_timer'] >= self._settings[ name + 'stoptimer'] condition['stop_timer'] *= int(not stop) self._timer_runnning = True else: condition['stop_timer'] = 0 condition['reached'] = start and not stop return condition['reached'] def _evaluate_manual_start(self): if self._dbusservice['/ManualStart'] == 0: if self._dbusservice['/RunningByCondition'] == 'manual': self._dbusservice['/ManualStartTimer'] = 0 return False start = True # If /ManualStartTimer has a value greater than zero will use it to set a stop timer. # If no timer is set, the generator will not stop until the user stops it manually. # Once started by manual start, each evaluation the timer is decreased if self._dbusservice['/ManualStartTimer'] != 0: self._manualstarttimer += time.time( ) if self._manualstarttimer == 0 else 0 self._dbusservice['/ManualStartTimer'] -= int(time.time()) - int( self._manualstarttimer) self._manualstarttimer = time.time() start = self._dbusservice['/ManualStartTimer'] > 0 self._dbusservice['/ManualStart'] = int(start) # Reset if timer is finished self._manualstarttimer *= int(start) self._dbusservice['/ManualStartTimer'] *= int(start) return start def _evaluate_testrun_condition(self): if self._settings['testrunenabled'] == 0: self._dbusservice['/SkipTestRun'] = None self._dbusservice['/NextTestRun'] = None return False today = datetime.date.today() runtillbatteryfull = self._settings['testruntillbatteryfull'] == 1 soc = self._get_updated_values()['soc'] batteryisfull = runtillbatteryfull and soc == 100 try: startdate = datetime.date.fromtimestamp( self._settings['testrunstartdate']) starttime = time.mktime( today.timetuple()) + self._settings['testrunstarttimer'] except ValueError: logger.debug('Invalid dates, skipping testrun') return False # If start date is in the future set as NextTestRun and stop evaluating if startdate > today: self._dbusservice['/NextTestRun'] = time.mktime( startdate.timetuple()) return False start = False # If the accumulated runtime during the tes trun interval is greater than '/TestRunIntervalRuntime' # the tes trun must be skipped needed = (self._settings['testrunskipruntime'] > self._dbusservice['/TestRunIntervalRuntime'] or self._settings['testrunskipruntime'] == 0) self._dbusservice['/SkipTestRun'] = int(not needed) interval = self._settings['testruninterval'] stoptime = (starttime + self._settings['testrunruntime'] ) if not runtillbatteryfull else (starttime + 60) elapseddays = (today - startdate).days mod = elapseddays % interval start = (not bool(mod) and (time.time() >= starttime) and (time.time() <= stoptime)) if runtillbatteryfull: if soc is not None: self._testrun_soc_retries = 0 start = (start or self._dbusservice['/RunningByCondition'] == 'testrun') and not batteryisfull elif self._dbusservice['/RunningByCondition'] == 'testrun': if self._testrun_soc_retries < self.RETRIES_ON_ERROR: self._testrun_soc_retries += 1 start = True if (self._testrun_soc_retries % 10) == 0: logger.info( 'Test run failed to get SOC value, retrying(#%i)' % self._testrun_soc_retries) else: logger.info( 'Failed to get SOC after %i retries, terminating test run condition' % self._testrun_soc_retries) start = False else: start = False if not bool(mod) and (time.time() <= stoptime): self._dbusservice['/NextTestRun'] = starttime else: self._dbusservice['/NextTestRun'] = ( time.mktime( (today + datetime.timedelta(days=interval - mod)).timetuple()) + self._settings['testrunstarttimer']) return start and needed def _check_quiet_hours(self): active = False if self._settings['quiethoursenabled'] == 1: # Seconds after today 00:00 timeinseconds = time.time() - time.mktime( datetime.date.today().timetuple()) quiethoursstart = self._settings['quiethoursstarttime'] quiethoursend = self._settings['quiethoursendtime'] # Check if the current time is between the start time and end time if quiethoursstart < quiethoursend: active = quiethoursstart <= timeinseconds and timeinseconds < quiethoursend else: # End time is lower than start time, example Start: 21:00, end: 08:00 active = not (quiethoursend < timeinseconds and timeinseconds < quiethoursstart) if self._dbusservice['/QuietHours'] == 0 and active: logger.info('Entering to quiet mode') elif self._dbusservice['/QuietHours'] == 1 and not active: logger.info('Leaving secondary quiet mode') self._dbusservice['/QuietHours'] = int(active) return active def _update_accumulated_time(self): seconds = self._dbusservice['/Runtime'] accumulated = seconds - self._last_runtime_update self._settings['accumulatedtotal'] = int( self._settings['accumulatedtotal']) + accumulated # Using calendar to get timestamp in UTC, not local time today_date = str(calendar.timegm(datetime.date.today().timetuple())) # If something goes wrong getting the json string create a new one try: accumulated_days = json.loads(self._settings['accumulateddaily']) except ValueError: accumulated_days = {today_date: 0} if (today_date in accumulated_days): accumulated_days[today_date] += accumulated else: accumulated_days[today_date] = accumulated self._last_runtime_update = seconds # Keep the historical with a maximum of HISTORY_DAYS while len(accumulated_days) > self.HISTORY_DAYS: accumulated_days.pop(min(accumulated_days.keys()), None) # Upadate settings self._settings['accumulateddaily'] = json.dumps(accumulated_days, sort_keys=True) self._dbusservice['/TodayRuntime'] = self._interval_runtime(0) self._dbusservice['/TestRunIntervalRuntime'] = self._interval_runtime( self._settings['testruninterval']) def _interval_runtime(self, days): summ = 0 try: daily_record = json.loads(self._settings['accumulateddaily']) except ValueError: return 0 for i in range(days + 1): previous_day = calendar.timegm( (datetime.date.today() - datetime.timedelta(days=i)).timetuple()) if str(previous_day) in daily_record.keys(): summ += daily_record[str(previous_day)] if str( previous_day) in daily_record.keys() else 0 return summ def _get_updated_values(self): values = { 'batteryvoltage': (self._battery_measurement_voltage_import.get_value() if self._battery_measurement_voltage_import else None), 'batterycurrent': (self._battery_measurement_current_import.get_value() if self._battery_measurement_current_import else None), 'soc': self._battery_measurement_soc_import.get_value() if self._battery_measurement_soc_import else None, 'acload': self._dbusmonitor.get_value('com.victronenergy.system', '/Ac/Consumption/Total/Power'), 'inverterhightemp': (self._vebusservice_high_temperature_import.get_value() if self._vebusservice_high_temperature_import else None), 'inverteroverload': (self._vebusservice_overload_import.get_value() if self._vebusservice_overload_import else None) } if values['batterycurrent']: values['batterycurrent'] *= -1 return values def _determineservices(self): # batterymeasurement is either 'default' or 'com_victronenergy_battery_288/Dc/0'. # In case it is set to default, we use the AutoSelected battery measurement, given by # SystemCalc. batterymeasurement = None batteryservicename = None newbatteryservice = None batteryprefix = "" selectedbattery = self._settings['batterymeasurement'] vebusservice = None if selectedbattery == 'default': batterymeasurement = self._dbusmonitor.get_value( 'com.victronenergy.system', '/AutoSelectedBatteryMeasurement') elif len(selectedbattery.split( "/", 1)) == 2: # Only very basic sanity checking.. batterymeasurement = self._settings['batterymeasurement'] elif selectedbattery == 'nobattery': batterymeasurement = None else: # Exception: unexpected value for batterymeasurement pass if batterymeasurement: batteryprefix = "/" + batterymeasurement.split("/", 1)[1] # Get the current battery servicename if self._battery_measurement_voltage_import: oldservice = ( self._battery_measurement_voltage_import.serviceName + self._battery_measurement_voltage_import.path.replace( "/Voltage", "")) else: oldservice = None if batterymeasurement: try: batteryservicename = VeDbusItemImport( bus=self._bus, serviceName="com.victronenergy.system", path='/ServiceMapping/' + batterymeasurement.split("/", 1)[0], eventCallback=None, createsignal=False) if batteryservicename.get_value(): newbatteryservice = batteryservicename.get_value( ) + batteryprefix except dbus.exceptions.DBusException: pass else: newbatteryservice = None if batteryservicename and batteryservicename.get_value(): self._battery_measurement_available = True logger.info( 'Battery service we need (%s) found! Using it for generator start/stop' % batterymeasurement) try: self._battery_measurement_voltage_import = VeDbusItemImport( bus=self._bus, serviceName=batteryservicename.get_value(), path=batteryprefix + '/Voltage', eventCallback=None, createsignal=True) self._battery_measurement_current_import = VeDbusItemImport( bus=self._bus, serviceName=batteryservicename.get_value(), path=batteryprefix + '/Current', eventCallback=None, createsignal=True) # Exception caused by Matthijs :), we forgot to batteryprefix the Soc during the big path-change... self._battery_measurement_soc_import = VeDbusItemImport( bus=self._bus, serviceName=batteryservicename.get_value(), path='/Soc', eventCallback=None, createsignal=True) except Exception: logger.debug('Error getting battery service!') self._battery_measurement_voltage_import = None self._battery_measurement_current_import = None self._battery_measurement_soc_import = None elif selectedbattery == 'nobattery' and self._battery_measurement_available: logger.info( 'Battery monitoring disabled! Stop evaluating related conditions' ) self._battery_measurement_voltage_import = None self._battery_measurement_current_import = None self._battery_measurement_soc_import = None self._battery_measurement_available = False elif batteryservicename and batteryservicename.get_value( ) is None and self._battery_measurement_available: logger.info( 'Battery service we need (%s) is not available! Stop evaluating related conditions' % batterymeasurement) self._battery_measurement_voltage_import = None self._battery_measurement_current_import = None self._battery_measurement_soc_import = None self._battery_measurement_available = False # Get the default VE.Bus service and import high temperature and overload warnings try: vebusservice = VeDbusItemImport( bus=self._bus, serviceName="com.victronenergy.system", path='/VebusService', eventCallback=None, createsignal=False) if vebusservice.get_value() and ( vebusservice.get_value() != self._vebusservice or not self._vebusservice_available): self._vebusservice = vebusservice.get_value() self._vebusservice_available = True logger.info( 'Vebus service (%s) found! Using it for generator start/stop' % vebusservice.get_value()) self._vebusservice_high_temperature_import = VeDbusItemImport( bus=self._bus, serviceName=vebusservice.get_value(), path='/Alarms/HighTemperature', eventCallback=None, createsignal=True) self._vebusservice_overload_import = VeDbusItemImport( bus=self._bus, serviceName=vebusservice.get_value(), path='/Alarms/Overload', eventCallback=None, createsignal=True) except Exception: logger.info('Error getting Vebus service!') self._vebusservice_available = False self._vebusservice_high_temperature_import = None self._vebusservice_overload_import = None logger.info( 'Vebus service (%s) dissapeared! Stop evaluating related conditions' % self._vebusservice) # Trigger an immediate check of system status self._changed = True def _start_generator(self, condition): if not self._relay_state_import: logger.info( "Relay import not available, can't start generator by %s condition" % condition) return systemcalc_relay_state = 0 state = self._dbusservice['/State'] try: systemcalc_relay_state = self._relay_state_import.get_value() except dbus.exceptions.DBusException: logger.info('Error getting relay state') # This function will start the generator in the case generator not # already running. When differs, the RunningByCondition is updated if state == 0 or systemcalc_relay_state != state: self._dbusservice['/State'] = 1 self._update_relay() self._starttime = time.time() logger.info('Starting generator by %s condition' % condition) elif self._dbusservice['/RunningByCondition'] != condition: logger.info( 'Generator previously running by %s condition is now running by %s condition' % (self._dbusservice['/RunningByCondition'], condition)) self._dbusservice['/RunningByCondition'] = condition def _stop_generator(self): if not self._relay_state_import: logger.info("Relay import not available, can't stop generator") return systemcalc_relay_state = 1 state = self._dbusservice['/State'] try: systemcalc_relay_state = self._relay_state_import.get_value() except dbus.exceptions.DBusException: logger.info('Error getting relay state') if state == 1 or systemcalc_relay_state != state: self._dbusservice['/State'] = 0 self._update_relay() logger.info('Stopping generator that was running by %s condition' % str(self._dbusservice['/RunningByCondition'])) self._dbusservice['/RunningByCondition'] = '' self._update_accumulated_time() self._starttime = 0 self._dbusservice['/Runtime'] = 0 self._dbusservice['/ManualStartTimer'] = 0 self._manualstarttimer = 0 self._last_runtime_update = 0 def _update_relay(self): if not self._relay_state_import: logger.info("Relay import not available") return # Relay polarity 0 = NO, 1 = NC polarity = bool( self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/Relay/Polarity')) w = int(not polarity) if bool( self._dbusservice['/State']) else int(polarity) try: self._relay_state_import.set_value(dbus.Int32(w, variant_level=1)) except dbus.exceptions.DBusException: logger.info('Error setting relay state')
def _determineservices(self): # batterymeasurement is either 'default' or 'com_victronenergy_battery_288/Dc/0'. # In case it is set to default, we use the AutoSelected battery measurement, given by # SystemCalc. batterymeasurement = None batteryservicename = None newbatteryservice = None batteryprefix = "" selectedbattery = self._settings['batterymeasurement'] vebusservice = None if selectedbattery == 'default': batterymeasurement = self._dbusmonitor.get_value( 'com.victronenergy.system', '/AutoSelectedBatteryMeasurement') elif len(selectedbattery.split( "/", 1)) == 2: # Only very basic sanity checking.. batterymeasurement = self._settings['batterymeasurement'] elif selectedbattery == 'nobattery': batterymeasurement = None else: # Exception: unexpected value for batterymeasurement pass if batterymeasurement: batteryprefix = "/" + batterymeasurement.split("/", 1)[1] # Get the current battery servicename if self._battery_measurement_voltage_import: oldservice = ( self._battery_measurement_voltage_import.serviceName + self._battery_measurement_voltage_import.path.replace( "/Voltage", "")) else: oldservice = None if batterymeasurement: try: batteryservicename = VeDbusItemImport( bus=self._bus, serviceName="com.victronenergy.system", path='/ServiceMapping/' + batterymeasurement.split("/", 1)[0], eventCallback=None, createsignal=False) if batteryservicename.get_value(): newbatteryservice = batteryservicename.get_value( ) + batteryprefix except dbus.exceptions.DBusException: pass else: newbatteryservice = None if batteryservicename and batteryservicename.get_value(): self._battery_measurement_available = True logger.info( 'Battery service we need (%s) found! Using it for generator start/stop' % batterymeasurement) try: self._battery_measurement_voltage_import = VeDbusItemImport( bus=self._bus, serviceName=batteryservicename.get_value(), path=batteryprefix + '/Voltage', eventCallback=None, createsignal=True) self._battery_measurement_current_import = VeDbusItemImport( bus=self._bus, serviceName=batteryservicename.get_value(), path=batteryprefix + '/Current', eventCallback=None, createsignal=True) # Exception caused by Matthijs :), we forgot to batteryprefix the Soc during the big path-change... self._battery_measurement_soc_import = VeDbusItemImport( bus=self._bus, serviceName=batteryservicename.get_value(), path='/Soc', eventCallback=None, createsignal=True) except Exception: logger.debug('Error getting battery service!') self._battery_measurement_voltage_import = None self._battery_measurement_current_import = None self._battery_measurement_soc_import = None elif selectedbattery == 'nobattery' and self._battery_measurement_available: logger.info( 'Battery monitoring disabled! Stop evaluating related conditions' ) self._battery_measurement_voltage_import = None self._battery_measurement_current_import = None self._battery_measurement_soc_import = None self._battery_measurement_available = False elif batteryservicename and batteryservicename.get_value( ) is None and self._battery_measurement_available: logger.info( 'Battery service we need (%s) is not available! Stop evaluating related conditions' % batterymeasurement) self._battery_measurement_voltage_import = None self._battery_measurement_current_import = None self._battery_measurement_soc_import = None self._battery_measurement_available = False # Get the default VE.Bus service and import high temperature and overload warnings try: vebusservice = VeDbusItemImport( bus=self._bus, serviceName="com.victronenergy.system", path='/VebusService', eventCallback=None, createsignal=False) if vebusservice.get_value() and ( vebusservice.get_value() != self._vebusservice or not self._vebusservice_available): self._vebusservice = vebusservice.get_value() self._vebusservice_available = True logger.info( 'Vebus service (%s) found! Using it for generator start/stop' % vebusservice.get_value()) self._vebusservice_high_temperature_import = VeDbusItemImport( bus=self._bus, serviceName=vebusservice.get_value(), path='/Alarms/HighTemperature', eventCallback=None, createsignal=True) self._vebusservice_overload_import = VeDbusItemImport( bus=self._bus, serviceName=vebusservice.get_value(), path='/Alarms/Overload', eventCallback=None, createsignal=True) except Exception: logger.info('Error getting Vebus service!') self._vebusservice_available = False self._vebusservice_high_temperature_import = None self._vebusservice_overload_import = None logger.info( 'Vebus service (%s) dissapeared! Stop evaluating related conditions' % self._vebusservice) # Trigger an immediate check of system status self._changed = True
'/Fix', '/Position/Latitude', '/Position/Longitude', '/NrOfSatellites', '/Speed', '/Position/Course'] } resp = {} dbusConn = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() for bus,list in query.items(): resp[bus] = {} for path in list: resp[bus][path] = VeDbusItemImport(dbusConn, bus, path).get_value() data = resp[gps] # Set up a client for InfluxDB dbclient = InfluxDBClient(config[influx_server]['host'], config[influx_server]['port'], config[influx_server]['username'], config[influx_server]['password'], config[influx_server]['database']) json_body = [{ "measurement": "gps", "fields": { "alt": float(data['/Altitude']), "lat": float(data['/Position/Latitude']), "lon": float(data['/Position/Longitude']), "sats": float(data['/NrOfSatellites']), "speed": float(data['/Speed']), }
def _evaluate_if_we_are_needed(self): if self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/Relay/Function') == 1: if not self._relay_state_import: logger.info('Getting relay from systemcalc.') try: self._relay_state_import = VeDbusItemImport( bus=self._bus, serviceName='com.victronenergy.system', path='/Relay/0/State', eventCallback=None, createsignal=True) except dbus.exceptions.DBusException: logger.info('Systemcalc relay not available.') self._relay_state_import = None pass if self._dbusservice is None: logger.info( 'Action! Going on dbus and taking control of the relay.') relay_polarity_import = VeDbusItemImport( bus=self._bus, serviceName='com.victronenergy.settings', path='/Settings/Relay/Polarity', eventCallback=None, createsignal=True) # As is not possible to keep the relay state during the CCGX power cycles, # set the relay polarity to normally open. if relay_polarity_import.get_value() == 1: relay_polarity_import.set_value(0) logger.info('Setting relay polarity to normally open.') # put ourselves on the dbus self._dbusservice = VeDbusService( 'com.victronenergy.generator.startstop0') self._dbusservice.add_mandatory_paths( processname=__file__, processversion=softwareversion, connection='generator', deviceinstance=0, productid=None, productname=None, firmwareversion=None, hardwareversion=None, connected=1) # State: None = invalid, 0 = stopped, 1 = running self._dbusservice.add_path('/State', value=0) # Condition that made the generator start self._dbusservice.add_path('/RunningByCondition', value='') # Runtime self._dbusservice.add_path('/Runtime', value=0, gettextcallback=self._gettext) # Today runtime self._dbusservice.add_path('/TodayRuntime', value=0, gettextcallback=self._gettext) # Test run runtime self._dbusservice.add_path( '/TestRunIntervalRuntime', value=self._interval_runtime( self._settings['testruninterval']), gettextcallback=self._gettext) # Next tes trun date, values is 0 for test run disabled self._dbusservice.add_path('/NextTestRun', value=None, gettextcallback=self._gettext) # Next tes trun is needed 1, not needed 0 self._dbusservice.add_path('/SkipTestRun', value=None) # Manual start self._dbusservice.add_path('/ManualStart', value=0, writeable=True) # Manual start timer self._dbusservice.add_path('/ManualStartTimer', value=0, writeable=True) # Silent mode active self._dbusservice.add_path('/QuietHours', value=0) self._determineservices() else: if self._dbusservice is not None: self._stop_generator() self._dbusservice.__del__() self._dbusservice = None # Reset conditions for condition in self._condition_stack: self._reset_condition(self._condition_stack[condition]) logger.info( 'Relay function is no longer set to generator start/stop: made sure generator is off ' + 'and now going off dbus') self._relay_state_import = None
def _evaluate_if_we_are_needed(self): if self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/Relay/Function') == 1: if self._dbusservice is None: logger.info('Action! Going on dbus and taking control of the relay.') relay_polarity_import = VeDbusItemImport( bus=self._bus, serviceName='com.victronenergy.settings', path='/Settings/Relay/Polarity', eventCallback=None, createsignal=True) # As is not possible to keep the relay state during the CCGX power cycles, # set the relay polarity to normally open. if relay_polarity_import.get_value() == 1: relay_polarity_import.set_value(0) logger.info('Setting relay polarity to normally open.') # put ourselves on the dbus self._dbusservice = VeDbusService('com.victronenergy.generator.startstop0') self._dbusservice.add_mandatory_paths( processname=__file__, processversion=softwareversion, connection='generator', deviceinstance=0, productid=None, productname=None, firmwareversion=None, hardwareversion=None, connected=1) # State: None = invalid, 0 = stopped, 1 = running self._dbusservice.add_path('/State', value=0) # Condition that made the generator start self._dbusservice.add_path('/RunningByCondition', value='') # Runtime self._dbusservice.add_path('/Runtime', value=0, gettextcallback=self._gettext) # Today runtime self._dbusservice.add_path('/TodayRuntime', value=0, gettextcallback=self._gettext) # Test run runtime self._dbusservice.add_path('/TestRunIntervalRuntime', value=self._interval_runtime(self._settings['testruninterval']), gettextcallback=self._gettext) # Next tes trun date, values is 0 for test run disabled self._dbusservice.add_path('/NextTestRun', value=None, gettextcallback=self._gettext) # Next tes trun is needed 1, not needed 0 self._dbusservice.add_path('/SkipTestRun', value=None) # Manual start self._dbusservice.add_path('/ManualStart', value=0, writeable=True) # Manual start timer self._dbusservice.add_path('/ManualStartTimer', value=0, writeable=True) # Silent mode active self._dbusservice.add_path('/QuietHours', value=0) self._determineservices() else: if self._dbusservice is not None: self._stop_generator() self._dbusservice.__del__() self._dbusservice = None # Reset conditions for condition in self._condition_stack: self._reset_condition(self._condition_stack[condition]) logger.info('Relay function is no longer set to generator start/stop: made sure generator is off ' + 'and now going off dbus')
def _determineservices(self): # batterymeasurement is either 'default' or 'com_victronenergy_battery_288/Dc/0'. # In case it is set to default, we use the AutoSelected battery measurement, given by # SystemCalc. batterymeasurement = None batteryservicename = None newbatteryservice = None batteryprefix = "" selectedbattery = self._settings['batterymeasurement'] vebusservice = None if selectedbattery == 'default': batterymeasurement = self._dbusmonitor.get_value('com.victronenergy.system', '/AutoSelectedBatteryMeasurement') elif len(selectedbattery.split("/", 1)) == 2: # Only very basic sanity checking.. batterymeasurement = self._settings['batterymeasurement'] elif selectedbattery == 'nobattery': batterymeasurement = None else: # Exception: unexpected value for batterymeasurement pass if batterymeasurement: batteryprefix = "/" + batterymeasurement.split("/", 1)[1] # Get the current battery servicename if self._battery_measurement_voltage_import: oldservice = (self._battery_measurement_voltage_import.serviceName + self._battery_measurement_voltage_import.path.replace("/Voltage", "")) else: oldservice = None if batterymeasurement: batteryservicename = VeDbusItemImport( bus=self._bus, serviceName="com.victronenergy.system", path='/ServiceMapping/' + batterymeasurement.split("/", 1)[0], eventCallback=None, createsignal=False) if batteryservicename.get_value(): newbatteryservice = batteryservicename.get_value() + batteryprefix else: newbatteryservice = None if batteryservicename and batteryservicename.get_value(): self._battery_measurement_available = True logger.info('Battery service we need (%s) found! Using it for generator start/stop' % batterymeasurement) try: self._battery_measurement_voltage_import = VeDbusItemImport( bus=self._bus, serviceName=batteryservicename.get_value(), path=batteryprefix + '/Voltage', eventCallback=None, createsignal=True) self._battery_measurement_current_import = VeDbusItemImport( bus=self._bus, serviceName=batteryservicename.get_value(), path=batteryprefix + '/Current', eventCallback=None, createsignal=True) # Exception caused by Matthijs :), we forgot to batteryprefix the Soc during the big path-change... self._battery_measurement_soc_import = VeDbusItemImport( bus=self._bus, serviceName=batteryservicename.get_value(), path='/Soc', eventCallback=None, createsignal=True) except Exception: logger.debug('Error getting battery service!') self._battery_measurement_voltage_import = None self._battery_measurement_current_import = None self._battery_measurement_soc_import = None elif selectedbattery == 'nobattery' and self._battery_measurement_available: logger.info('Battery monitoring disabled! Stop evaluating related conditions') self._battery_measurement_voltage_import = None self._battery_measurement_current_import = None self._battery_measurement_soc_import = None self._battery_measurement_available = False elif batteryservicename and batteryservicename.get_value() is None and self._battery_measurement_available: logger.info('Battery service we need (%s) is not available! Stop evaluating related conditions' % batterymeasurement) self._battery_measurement_voltage_import = None self._battery_measurement_current_import = None self._battery_measurement_soc_import = None self._battery_measurement_available = False # Get the default VE.Bus service and import high temperature and overload warnings vebusservice = VeDbusItemImport( bus=self._bus, serviceName="com.victronenergy.system", path='/VebusService', eventCallback=None, createsignal=False) if vebusservice.get_value() and (vebusservice.get_value() != self._vebusservice or not self._vebusservice_available): self._vebusservice = vebusservice.get_value() self._vebusservice_available = True logger.info('Vebus service (%s) found! Using it for generator start/stop' % vebusservice.get_value()) try: self._vebusservice_high_temperature_import = VeDbusItemImport( bus=self._bus, serviceName=vebusservice.get_value(), path='/Alarms/HighTemperature', eventCallback=None, createsignal=True) self._vebusservice_overload_import = VeDbusItemImport( bus=self._bus, serviceName=vebusservice.get_value(), path='/Alarms/Overload', eventCallback=None, createsignal=True) except Exception: logger.info('Error getting Vebus service!') self._vebusservice_available = False self._vebusservice_high_temperature_import = None self._vebusservice_overload_import = None elif not vebusservice.get_value() and self._vebusservice_available: logger.info('Vebus service (%s) dissapeared! Stop evaluating related conditions' % self._vebusservice) self._vebusservice_available = False self._vebusservice_high_temperature_import = None self._vebusservice_overload_import = None # Trigger an immediate check of system status self._changed = True
else: try: goeCharger.setAllowCharging(1) status = goeCharger.setMaxCurrent(i) print("--> %d Ampere gesetzt um %s" % (i, currentTime)) return True except Exception: print("Nicht gesetzt! %s" % i) return False iGoe_old = 0 while True: batteryOffset = args.battery_offset solarPower = VeDbusItemImport(dbusConn, 'com.victronenergy.system', '/Ac/PvOnGrid/L1/Power').get_value() if solarPower == None: solarPower = 0 solarPower = int(solarPower) try: battery_soc = VeDbusItemImport(dbusConn, 'com.victronenergy.battery.ttyO2', '/Soc').get_value() except Exception: print("Konnte SOC nicht bestimmen") batteryOffset += getBatteryOffsetFromFile() if args.goe_ampere > 0: iGoe = args.goe_ampere
from vedbus import VeDbusItemExport, VeDbusItemImport DBusGMainLoop(set_as_default=True) # Connect to the sessionbus. Note that on ccgx we use systembus instead. dbusConn = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus() # dictionary containing the different items dbusObjects = {} # check if the vbus.ttyO1 exists (it normally does on a ccgx, and for linux a pc, there is # some emulator. hasVEBus = 'com.victronenergy.vebus.ttyO1' in dbusConn.list_names() dbusObjects['PyString'] = VeDbusItemImport(dbusConn, 'com.victronenergy.example', '/String') if hasVEBus: dbusObjects['C_string'] = VeDbusItemImport(dbusConn, 'com.victronenergy.vebus.ttyO1', '/Mgmt/ProcessName') dbusObjects['PyFloat'] = VeDbusItemImport(dbusConn, 'com.victronenergy.example', '/Float') if hasVEBus: dbusObjects['C_float'] = VeDbusItemImport(dbusConn, 'com.victronenergy.vebus.ttyO1', '/Dc/V') dbusObjects['PyInt'] = VeDbusItemImport(dbusConn, 'com.victronenergy.example', '/Int') if hasVEBus: dbusObjects['C_int'] = VeDbusItemImport(dbusConn, 'com.victronenergy.vebus.ttyO1', '/State') dbusObjects['PyNegativeInt'] = VeDbusItemImport(dbusConn, 'com.victronenergy.example', '/NegativeInt') if hasVEBus: dbusObjects['C_negativeInt'] = VeDbusItemImport(dbusConn, 'com.victronenergy.vebus.ttyO1', '/Dc/I') # print the results print '----' for key, o in dbusObjects.items(): print key + ' at ' + o.serviceName + o.path
def test_adding_new_settings_and_readback(self): from collections import OrderedDict testsets = OrderedDict() testsets["int-no-min-max"] = { "group": "g", "setting": "in", "default": 100, "value": 100, "type": "i", "min": 0, "max": 0, } testsets["int-with-min-max"] = { "group": "g", "setting": "iw", "default": 101, "value": 101, "type": "i", "min": 0, "max": 101, } # float-no-min-max doesn't work, because localsettings does not recognize 0.0 and 0.0 as no min max, only 0 and 0 works. # testsets['float-no-min-max'] = {'group': 'g', 'setting': 'f', 'default': 102.0, 'value': 102.0, 'type': 'f', 'min': 0.0, 'max': 0.0} testsets["float-with-min-max"] = { "group": "g", "setting": "f", "default": 103.0, "value": 103.0, "type": "f", "min": 0.0, "max": 1000.0, } testsets["start-group-with-digit"] = { "group": "0g", "setting": "s", "default": 104, "value": 104, "type": "i", "min": 0, "max": 0, } testsets["start-setting-with-digit"] = { "group": "g", "setting": "0s", "default": 105, "value": 105, "type": "i", "min": 0, "max": 0, } testsets["int-re-add-same-min-max"] = { "group": "g", "setting": "in", "default": 200, "value": 100, "type": "i", "min": 0, "max": 0, } testsets["int-re-add-other-min-max"] = { "group": "g", "setting": "in", "default": 201, "value": 100, "type": "i", "min": 10, "max": 1000, } testsets["float-re-add-other-min-max"] = { "group": "g", "setting": "f", "default": 103.0, "value": 103.0, "type": "f", "min": 1.0, "max": 1001.0, } for name, details in testsets.iteritems(): print "\n\n===Testing %s===\n" % name self.assertEqual( 0, self._add_setting( details["group"], details["setting"], details["default"], details["type"], details["min"], details["max"], ), ) # wait 2.5 seconds, since local settings itself waits 2 seconds before it stores the data. time.sleep(2.5) # restart localsettings self._stopLocalSettings() time.sleep(2) self._startLocalSettings() # read the results i = VeDbusItemImport( self._dbus, "com.victronenergy.settings", "/Settings/" + details["group"] + "/" + details["setting"], eventCallback=None, createsignal=False, ) result = copy.deepcopy(details) result["value"] = i.get_value().real result["default"] = i._proxy.GetDefault().real # don't ask me why, but getMin() and getMax() return a string... result["min"] = int(i._proxy.GetMin()) if details["type"] == "i" else float(i._proxy.GetMin()) result["max"] = int(i._proxy.GetMax()) if details["type"] == "i" else float(i._proxy.GetMax()) # don't check the type, as there is no GetType() available # result['type'] = i._proxy.GetType() self.assertEqual(details, result)
def import_value(self, serviceName, path): if serviceName not in self.imported: self.imported[serviceName] = {} if path in self.imported[serviceName]: return self.imported[serviceName][path] = VeDbusItemImport( self.bus, serviceName, path, self.import_value_changed)
class DbusPump: def __init__(self, retries=300): self._bus = dbus.SystemBus() if (platform.machine() == 'armv7l') else dbus.SessionBus() self.RELAY_GPIO_FILE = '/sys/class/gpio/gpio182/value' self.HISTORY_DAYS = 30 # One second per retry self.RETRIES_ON_ERROR = retries self._current_retries = 0 self.TANKSERVICE_DEFAULT = 'default' self.TANKSERVICE_NOTANK = 'notanksensor' self._dbusservice = None self._tankservice = self.TANKSERVICE_NOTANK self._valid_tank_level = True self._relay_state_import = None # DbusMonitor expects these values to be there, even though we don need them. So just # add some dummy data. This can go away when DbusMonitor is more generic. dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None} # TODO: possible improvement: don't use the DbusMonitor it all, since we are only monitoring # a set of static values which will always be available. DbusMonitor watches for services # that come and go, and takes care of automatic signal subscribtions etc. etc: all not necessary # in this use case where we have fixed services names (com.victronenergy.settings, and c # com.victronenergy.system). self._dbusmonitor = DbusMonitor({ 'com.victronenergy.settings': { # This is not our setting so do it here. not in supportedSettings '/Settings/Relay/Function': dummy, '/Settings/Relay/Polarity': dummy }, 'com.victronenergy.tank': { # This is not our setting so do it here. not in supportedSettings '/Level': dummy, '/FluidType': dummy, '/ProductName': dummy, '/Mgmt/Connection': dummy } }, self._dbus_value_changed, self._device_added, self._device_removed) # Connect to localsettings self._settings = SettingsDevice( bus=self._bus, supportedSettings={ 'tankservice': ['/Settings/Pump0/TankService', self.TANKSERVICE_NOTANK, 0, 1], 'autostart': ['/Settings/Pump0/AutoStartEnabled', 1, 0, 1], 'startvalue': ['/Settings/Pump0/StartValue', 50, 0, 100], 'stopvalue': ['/Settings/Pump0/StopValue', 80, 0, 100], 'mode': ['/Settings/Pump0/Mode', 0, 0, 100] # Auto = 0, On = 1, Off = 2 }, eventCallback=self._handle_changed_setting) # Whenever services come or go, we need to check if it was a service we use. Note that this # is a bit double: DbusMonitor does the same thing. But since we don't use DbusMonitor to # monitor for com.victronenergy.battery, .vebus, .charger or any other possible source of # battery data, it is necessary to monitor for changes in the available dbus services. self._bus.add_signal_receiver(self._dbus_name_owner_changed, signal_name='NameOwnerChanged') self._evaluate_if_we_are_needed() gobject.timeout_add(1000, self._handletimertick) self._changed = True def _evaluate_if_we_are_needed(self): if self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/Relay/Function') == 3: if self._dbusservice is None: logger.info('Action! Going on dbus and taking control of the relay.') relay_polarity_import = VeDbusItemImport( bus=self._bus, serviceName='com.victronenergy.settings', path='/Settings/Relay/Polarity', eventCallback=None, createsignal=True) if not self._relay_state_import: logger.info('Getting relay from systemcalc.') try: self._relay_state_import = VeDbusItemImport( bus=self._bus, serviceName='com.victronenergy.system', path='/Relay/0/State', eventCallback=None, createsignal=True) except dbus.exceptions.DBusException: logger.info('Systemcalc relay not available.') self._relay_state_import = None pass # As is not possible to keep the relay state during the CCGX power cycles, # set the relay polarity to normally open. if relay_polarity_import.get_value() == 1: relay_polarity_import.set_value(0) logger.info('Setting relay polarity to normally open.') # put ourselves on the dbus self._dbusservice = VeDbusService('com.victronenergy.pump.startstop0') self._dbusservice.add_mandatory_paths( processname=__file__, processversion=softwareversion, connection='pump', deviceinstance=0, productid=None, productname=None, firmwareversion=None, hardwareversion=None, connected=1) # State: None = invalid, 0 = stopped, 1 = running self._dbusservice.add_path('/State', value=0) self._dbusservice.add_path('/AvailableTankServices', value=None) self._dbusservice.add_path('/ActiveTankService', value=None) self._update_relay() self._handleservicechange() else: if self._dbusservice is not None: self._stop_pump() self._dbusservice.__del__() self._dbusservice = None self._relay_state_import = None logger.info('Relay function is no longer set to pump startstop: made sure pump is off and going off dbus') def _device_added(self, dbusservicename, instance): self._handleservicechange() self._evaluate_if_we_are_needed() def _device_removed(self, dbusservicename, instance): self._handleservicechange() # Relay handling depends on systemcalc, if the service disappears restart # the relay state import if dbusservicename == "com.victronenergy.system": self._relay_state_import = None self._evaluate_if_we_are_needed() def _dbus_value_changed(self, dbusServiceName, dbusPath, options, changes, deviceInstance): if dbusPath == '/Settings/Relay/Function': self._evaluate_if_we_are_needed() self._changed = True # Update relay state when polarity changes if dbusPath == '/Settings/Relay/Polarity': self._update_relay() def _handle_changed_setting(self, setting, oldvalue, newvalue): self._changed = True self._evaluate_if_we_are_needed() if setting == "tankservice": self._handleservicechange() if setting == 'autostart': logger.info('Autostart function %s.' % ('enabled' if newvalue == 1 else 'disabled')) def _dbus_name_owner_changed(self, name, oldowner, newowner): return True def _handletimertick(self): # try catch, to make sure that we kill ourselves on an error. Without this try-catch, there would # be an error written to stdout, and then the timer would not be restarted, resulting in a dead- # lock waiting for manual intervention -> not good! try: if self._dbusservice is not None: self._evaluate_startstop_conditions() self._changed = False except: self._stop_pump() import traceback traceback.print_exc() sys.exit(1) return True def _evaluate_startstop_conditions(self): if self._settings['tankservice'] == self.TANKSERVICE_NOTANK: self._stop_pump() return value = self._dbusmonitor.get_value(self._tankservice, "/Level") startvalue = self._settings['startvalue'] stopvalue = self._settings['stopvalue'] started = self._dbusservice['/State'] == 1 mode = self._settings['mode'] # On mode if mode == 1: if not started: self._start_pump() self._current_retries = 0 return # Off mode if mode == 2: if started: self._stop_pump() self._current_retries = 0 return # Auto mode, in case of an invalid reading start the retrying mechanism if started and value is None and mode == 0: # Keep the pump running during RETRIES_ON_ERROR(default 300) retries if started and self._current_retries < self.RETRIES_ON_ERROR: self._current_retries += 1 logger.info("Unable to get tank level, retrying (%i)" % self._current_retries) return # Stop the pump after RETRIES_ON_ERROR(default 300) retries logger.info("Unable to get tank level after %i retries, stopping pump." % self._current_retries) self._stop_pump() return if self._current_retries > 0 and value is not None: logger.info("Tank level successfuly obtained after %i retries." % self._current_retries) self._current_retries = 0 # Tank level not valid, check if is the first invalid reading # and print a log message if value is None: if self._valid_tank_level: self._valid_tank_level = False logger.info("Unable to get tank level, skipping evaluation.") return # Valid reading after a previous invalid one elif value is not None and not self._valid_tank_level: self._valid_tank_level = True logger.info("Tank level successfuly obtained, resuming evaluation.") start_is_greater = startvalue > stopvalue start = started or (value >= startvalue if start_is_greater else value <= startvalue) stop = value <= stopvalue if start_is_greater else value >= stopvalue if start and not stop: self._start_pump() else: self._stop_pump() def _determinetankservice(self): s = self._settings['tankservice'].split('/') if len(s) != 2: logger.error("The tank setting (%s) is invalid!" % self._settings['tankservice']) serviceclass = s[0] instance = int(s[1]) if len(s) == 2 else None services = self._dbusmonitor.get_service_list(classfilter=serviceclass) if instance not in services.values(): # Once chosen tank does not exist. Don't auto change the setting (it might come # back). And also don't autoselect another. newtankservice = None else: # According to https://www.python.org/dev/peps/pep-3106/, dict.keys() and dict.values() # always have the same order. newtankservice = services.keys()[services.values().index(instance)] if newtankservice != self._tankservice: services = self._dbusmonitor.get_service_list() instance = services.get(newtankservice, None) if instance is None: tank_service = None else: tank_service = self._get_instance_service_name(newtankservice, instance) self._dbusservice['/ActiveTankService'] = newtankservice logger.info("Tank service, setting == %s, changed from %s to %s (%s)" % (self._settings['tankservice'], self._tankservice, newtankservice, instance)) self._tankservice = newtankservice def _handleservicechange(self): services = self._get_connected_service_list('com.victronenergy.tank') ul = {self.TANKSERVICE_NOTANK: 'No tank sensor'} for servicename, instance in services.items(): key = self._get_instance_service_name(servicename, instance) ul[key] = self._get_readable_service_name(servicename) self._dbusservice['/AvailableTankServices'] = dbus.Dictionary(ul, signature='sv') self._determinetankservice() def _get_readable_service_name(self, servicename): fluidType = ['Fuel', 'Fresh water', 'Waste water', 'Live well', 'Oil', 'Black water'] fluid = fluidType[self._dbusmonitor.get_value(servicename, '/FluidType')] return (fluid + ' on ' + self._dbusmonitor.get_value(servicename, '/Mgmt/Connection')) def _get_instance_service_name(self, service, instance): return '%s/%s' % ('.'.join(service.split('.')[0:3]), instance) def _get_connected_service_list(self, classfilter=None): services = self._dbusmonitor.get_service_list(classfilter=classfilter) return services def _start_pump(self): if not self._relay_state_import: logger.info("Relay import not available, can't start pump by %s condition" % condition) return systemcalc_relay_state = 0 state = self._dbusservice['/State'] try: systemcalc_relay_state = self._relay_state_import.get_value() except dbus.exceptions.DBusException: logger.info('Error getting relay state') # This function will start the pump in the case the pump not # already running. if state == 0 or systemcalc_relay_state != state: self._dbusservice['/State'] = 1 self._update_relay() self._starttime = time.time() logger.info('Starting pump') def _stop_pump(self): if not self._relay_state_import: logger.info("Relay import not available, can't stop the pump") return systemcalc_relay_state = 1 state = self._dbusservice['/State'] try: systemcalc_relay_state = self._relay_state_import.get_value() except dbus.exceptions.DBusException: logger.info('Error getting relay state') if state == 1 or systemcalc_relay_state != state: self._dbusservice['/State'] = 0 logger.info('Stopping pump') self._update_relay() def _update_relay(self): if not self._relay_state_import: logger.info("Relay import not available") return # Relay polarity 0 = NO, 1 = NC polarity = bool(self._dbusmonitor.get_value('com.victronenergy.settings', '/Settings/Relay/Polarity')) w = int(not polarity) if bool(self._dbusservice['/State']) else int(polarity) try: self._relay_state_import.set_value(dbus.Int32(w, variant_level=1)) except dbus.exceptions.DBusException: logger.info('Error setting relay state')
def scan_dbus_service(self, serviceName): newDeviceAdded = False # make it a normal string instead of dbus string serviceName = str(serviceName) for s in self.dbusTree.keys(): if serviceName.split('.')[0:3] == s.split('.')[0:3]: logger.info( "Found: %s matches %s, scanning and storing items" % (serviceName, s)) # we should never be notified to add a D-Bus service that we already have. If this assertion # raises, check process_name_owner_changed, and D-Bus workings. assert serviceName not in self.items service = {} # create the empty list items. whentologoptions = [ 'configChange', 'onIntervalAlwaysAndOnEvent', 'onIntervalOnlyWhenChanged', 'onIntervalAlways' ] # these lists will contain the VeDbusItemImport objects with that whenToLog setting. Used to for whentolog in whentologoptions: service[whentolog] = [] service['paths'] = {} try: # for vebus.ttyO1, this is workaround, since VRM Portal expects the main vebus devices at # instance 0. Not sure how to fix this yet. if serviceName == 'com.victronenergy.vebus.ttyO1' and self.vebusDeviceInstance0: device_instance = 0 else: device_instance = VeDbusItemImport( self.dbusConn, serviceName, '/DeviceInstance', createsignal=False).get_value() device_instance = 0 if device_instance is None else int( device_instance) service['deviceInstance'] = device_instance logger.info(" %s has device instance %s" % (serviceName, service['deviceInstance'])) for path, options in self.dbusTree[s].items(): # path will be the D-Bus path: '/Ac/ActiveIn/L1/V' # options will be a dictionary: {'code': 'V', 'whenToLog': 'onIntervalAlways'} # check that the whenToLog setting is set to something we expect assert options['whenToLog'] is None or options[ 'whenToLog'] in whentologoptions # create and store the VeDbusItemImport. Store it both searchable by names, and in the # relevant whenToLog list. o = VeDbusItemImport(self.dbusConn, serviceName, path, self.handler_value_changes) if options['whenToLog']: service[options['whenToLog']].append(o) service['paths'][path] = { 'dbusObject': o, 'vrmDict': options } logger.debug(" Added %s%s" % (serviceName, path)) # Adjust self at the end of the scan, so we don't have an incomplete set of # data if an exception occurs during the scan. logger.debug("Finished scanning and storing items for %s" % serviceName) self.items[serviceName] = service newDeviceAdded = True except dbus.exceptions.DBusException, e: if e.get_dbus_name() == 'org.freedesktop.DBus.Error.ServiceUnknown' or \ e.get_dbus_name() == 'org.freedesktop.DBus.Error.Disconnected': logger.info( "Service disappeared while being scanned: %s" % serviceName) else: raise
def setDefault(self, path): item = VeDbusItemImport(self._bus, self._dbus_name, path, createsignal=False) item.set_default()