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
Exemplo n.º 2
0
	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')
Exemplo n.º 3
0
    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()
Exemplo n.º 4
0
	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 =====")
Exemplo n.º 5
0
    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)
Exemplo n.º 6
0
    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)
Exemplo n.º 7
0
 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)
Exemplo n.º 8
0
    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
            }
        ])
Exemplo n.º 9
0
	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
Exemplo n.º 10
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
            }
        ])
Exemplo n.º 11
0
    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 =====")
Exemplo n.º 12
0
    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
Exemplo n.º 15
0
		'/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
Exemplo n.º 17
0
	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')
Exemplo n.º 18
0
	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
Exemplo n.º 19
0
    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
Exemplo n.º 20
0
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)
Exemplo n.º 22
0
 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)
Exemplo n.º 23
0
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')
Exemplo n.º 24
0
    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
Exemplo n.º 25
0
 def setDefault(self, path):
     item = VeDbusItemImport(self._bus,
                             self._dbus_name,
                             path,
                             createsignal=False)
     item.set_default()