def Init(self):
		try:
			DBusGMainLoop(set_as_default=True)
			serial = os.path.basename(self.args.serial)
			self.dbusservice = VeDbusService('com.victronenergy.windcharger.bornay_' + serial)
			self.__mandatory__()
			self.__objects_dbus__()
		except:
			log.warn("Bornay wind+ has been created before")
			self.__mandatory__()
    def __init__(self,
                 servicename,
                 deviceinstance,
                 paths,
                 productname='Dummy product',
                 connection='Dummy service'):
        self._dbusservice = VeDbusService(servicename)
        self._paths = paths

        logging.debug("%s /DeviceInstance = %d" %
                      (servicename, deviceinstance))

        # Create the management objects, as specified in the ccgx dbus-api document
        self._dbusservice.add_path('/Mgmt/ProcessName', __file__)
        self._dbusservice.add_path(
            '/Mgmt/ProcessVersion', 'Unkown version, and running on Python ' +
            platform.python_version())
        self._dbusservice.add_path('/Mgmt/Connection', connection)

        # Create the mandatory objects
        self._dbusservice.add_path('/DeviceInstance', deviceinstance)
        self._dbusservice.add_path('/ProductId', 0)
        self._dbusservice.add_path('/ProductName', productname)
        self._dbusservice.add_path('/FirmwareVersion', 0)
        self._dbusservice.add_path('/HardwareVersion', 0)
        self._dbusservice.add_path('/Connected', 1)

        for path, settings in self._paths.iteritems():
            self._dbusservice.add_path(
                path,
                settings['initial'],
                writeable=True,
                onchangecallback=self._handlechangedvalue)

        gobject.timeout_add(1000, self._update)
    def __init__(self, bus, base, path, gpio, settings):
        self.gpio = gpio
        self.path = path
        self.bus = bus
        self.settings = settings
        self._level = 0  # Remember last state

        self.service = VeDbusService("{}.{}.input{:02d}".format(
            base, self.dbus_name, gpio),
                                     bus=bus)

        # Add objects required by ve-api
        self.service.add_path('/Mgmt/ProcessName', __file__)
        self.service.add_path('/Mgmt/ProcessVersion', VERSION)
        self.service.add_path('/Mgmt/Connection', path)
        self.service.add_path('/DeviceInstance', gpio)
        self.service.add_path('/ProductId', self.product_id)
        self.service.add_path('/ProductName', self.product_name)
        self.service.add_path('/Connected', 1)

        # Custom name setting
        def _change_name(p, v):
            # This should fire a change event that will update product_name
            # below.
            settings['name'] = v
            return True

        self.service.add_path('/CustomName',
                              settings['name'],
                              writeable=True,
                              onchangecallback=_change_name)

        # We'll count the pulses for all types of services
        self.service.add_path('/Count', value=settings['count'])
Example #4
0
 def Init(self):
     try:
         self.dbusservice = VeDbusService(
             'com.victronenergy.windcharger.bornay_ttyUSB0')
         self.__mandatory__()
         self.__objects_dbus__()
     except:
         log.warn("Bornay wind+ has been created before")
         self.__mandatory__()
Example #5
0
 def _create_dbus_service(self):
     dbusservice = VeDbusService(driver['connection'])
     dbusservice.add_mandatory_paths(processname=__file__,
                                     processversion=softwareVersion,
                                     connection=driver['connection'],
                                     deviceinstance=driver['instance'],
                                     productid=driver['id'],
                                     productname=driver['name'],
                                     firmwareversion=driver['version'],
                                     hardwareversion=driver['version'],
                                     connected=1)
     return dbusservice
Example #6
0
 def _create_dbus_service(self):
     dbusservice = VeDbusService("com.victronenergy.generator.startstop0")
     dbusservice.add_mandatory_paths(processname=__file__,
                                     processversion=softwareversion,
                                     connection='generator',
                                     deviceinstance=0,
                                     productid=None,
                                     productname=None,
                                     firmwareversion=None,
                                     hardwareversion=None,
                                     connected=1)
     return dbusservice
	def _create_dbus_service(self):
		dbusservice = VeDbusService('com.victronenergy.system')
		dbusservice.add_mandatory_paths(
			processname=__file__,
			processversion=softwareVersion,
			connection='data from other dbus processes',
			deviceinstance=0,
			productid=None,
			productname=None,
			firmwareversion=None,
			hardwareversion=None,
			connected=1)
		return dbusservice
 def _create_dbus_service(self):
     dbusservice = VeDbusService('com.victronenergy.system')
     dbusservice.add_mandatory_paths(
         processname=__file__,
         processversion=softwareVersion,
         connection='data from other dbus processes',
         deviceinstance=0,
         productid=None,
         productname=None,
         firmwareversion=None,
         hardwareversion=None,
         connected=1)
     return dbusservice
Example #9
0
def create_dbus_service():
    dbusservice = VeDbusService('com.victronenergy.pvinverter.envoy')
    dbusservice.add_mandatory_paths(
        processname=__file__,
        processversion=0.1,
        connection='com.victronenergy.pvinverter.envoy',
        deviceinstance=driver['instance'],
        productid=driver['id'],
        productname=driver['name'],
        firmwareversion=driver['version'],
        hardwareversion=driver['version'],
        connected=1)

    return dbusservice
Example #10
0
    def __init__(self,
                 n2kfluidtype,
                 n2ktankinstance,
                 paths,
                 productname='Signal K tank',
                 connection='Signal K tank service'):
        self._dbus = dbusconnection()
        self._servicename = '%s_%s_%s_%s' % (
            APPLICATION_SERVICE_NAME, SIGNALK_SERVER.replace('.', '_').replace(
                ':', '_'), str(n2kfluidtype), str(n2ktankinstance))
        self._dbusservicename = 'com.victronenergy.tank.%s' % self._servicename
        self._paths = paths

        # Process settings and recover our VRM instance number
        appsettingspath = '%s/%s' % (SETTINGS_ROOT, APPLICATION_SERVICE_NAME)
        servicesettingspath = '%s/%s' % (SETTINGS_ROOT, self._servicename)
        proposedclassdeviceinstance = '%s:%s' % ('tank', n2ktankinstance)
        SETTINGS = {
            'instance': [
                servicesettingspath + '/ClassAndVrmInstance',
                proposedclassdeviceinstance, 0, 0
            ],
            'customname': [servicesettingspath + '/CustomName', '', 0, 0]
        }
        self._settings = SettingsDevice(self._dbus, SETTINGS,
                                        self._handlesettingschanged)

        self._dbusservice = VeDbusService(self._dbusservicename, self._dbus)
        self._dbusservice.add_path('/Mgmt/ProcessName', __file__)
        self._dbusservice.add_path(
            '/Mgmt/ProcessVersion',
            VERSION + ' on Python ' + platform.python_version())
        self._dbusservice.add_path('/Mgmt/Connection',
                                   'SignalK ' + self._settings['instance'])

        self._dbusservice.add_path('/DeviceInstance',
                                   self._settings['instance'].split(':')[1])
        self._dbusservice.add_path('/ProductId', 0)
        self._dbusservice.add_path('/ProductName', '')
        self._dbusservice.add_path('/FirmwareVersion', 0)
        self._dbusservice.add_path('/HardwareVersion', 0)
        self._dbusservice.add_path('/Connected', 1)

        for path, settings in self._paths.iteritems():
            self._dbusservice.add_path(
                path,
                settings['initial'],
                writeable=True,
                onchangecallback=self._handlechangedvalue)
    def init(self, *args):
        super(NetClient, self).init(*args)

        svcname = 'com.victronenergy.modbusclient.%s' % self.name
        self.svc = VeDbusService(svcname, self.dbusconn)
        self.svc.add_path('/Scan',
                          False,
                          writeable=True,
                          onchangecallback=self.set_scan)
        self.svc.add_path('/ScanProgress', None, gettextcallback=percent)

        self.mdns = mdns.MDNS()
        self.mdns.start()
        self.mdns_check_time = 0
        self.mdns_query_time = 0
    def __init__(self, devname, address):
        bus = (dbus.SessionBus(private=True) if 'DBUS_SESSION_BUS_ADDRESS'
               in os.environ else dbus.SystemBus(private=True))
        self._dbusname = "fr.mildred.pzemvictron2020.pzem016.%s-%d" % (devname,
                                                                       address)
        self._dbusservice = VeDbusService(self._dbusname, bus=bus)
        self._error_message = ""
        self._disconnect = 0

        # Create the management objects, as specified in the ccgx dbus-api document
        self._dbusservice.add_path('/Mgmt/ProcessName', __file__)
        self._dbusservice.add_path('/Mgmt/ProcessVersion', softwareVersion)
        self._dbusservice.add_path(
            '/Mgmt/Connection',
            "Device %d on Modbus-RTU %s" % (address, devname))

        # Create the mandatory objects
        self._dbusservice.add_path('/DeviceInstance', address)
        self._dbusservice.add_path('/ProductId', address)
        self._dbusservice.add_path('/ProductName', "PZEM-016")
        self._dbusservice.add_path('/FirmwareVersion', 0)
        self._dbusservice.add_path('/HardwareVersion', 0)
        self._dbusservice.add_path('/Connected', 0)

        # Readings
        self._dbusservice.add_path('/Ac/Current',
                                   0,
                                   gettextcallback=self._get_text)
        self._dbusservice.add_path('/Ac/TotalEnergy',
                                   0,
                                   gettextcallback=self._get_text)
        self._dbusservice.add_path('/Ac/Power',
                                   0,
                                   gettextcallback=self._get_text)
        self._dbusservice.add_path('/Ac/Voltage',
                                   0,
                                   gettextcallback=self._get_text)
        self._dbusservice.add_path('/Ac/Frequency',
                                   0,
                                   gettextcallback=self._get_text)
        self._dbusservice.add_path('/Ac/PowerFactor',
                                   0,
                                   gettextcallback=self._get_text)
        self._dbusservice.add_path('/DeviceType', "PZEM-016")
        self._dbusservice.add_path('/ErrorCode',
                                   0,
                                   gettextcallback=self._get_text)
        self._dbusservice.add_path('/ErrorMessage', "")
Example #13
0
def create_dbus_service(instance):
	# Use a private bus, so we can have multiple services
	bus = dbus.Bus.get_session(private=True) if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.Bus.get_system(private=True)

	dbusservice = VeDbusService("com.victronenergy.generator.startstop{}".format(instance), bus=bus)
	dbusservice.add_mandatory_paths(
		processname=sys.argv[0],
		processversion=softwareversion,
		connection='generator',
		deviceinstance=instance,
		productid=None,
		productname=None,
		firmwareversion=None,
		hardwareversion=None,
		connected=1)
	return dbusservice
Example #14
0
    def __init__(self, host, base, instance):
        self.instance = instance
        self.service = service = VeDbusService("{}.smappee_{:02d}".format(
            base, instance),
                                               bus=dbusconnection())

        # Add objects required by ve-api
        service.add_path('/Management/ProcessName', __file__)
        service.add_path('/Management/ProcessVersion', VERSION)
        service.add_path('/Management/Connection', host)
        service.add_path('/DeviceInstance', instance)
        service.add_path('/ProductId', 0xFFFF)  # 0xB012 ?
        service.add_path('/ProductName', "SMAPPEE current meter")
        service.add_path('/FirmwareVersion', None)
        service.add_path('/Serial', None)
        service.add_path('/Connected', 1)

        service.add_path('/Ac/Energy/Forward', None)
        service.add_path('/Ac/Energy/Reverse', None)
        service.add_path('/Ac/L1/Current', None)
        service.add_path('/Ac/L1/Energy/Forward', None)
        service.add_path('/Ac/L1/Energy/Reverse', None)
        service.add_path('/Ac/L1/Power', None)
        service.add_path('/Ac/L1/Voltage', None)
        service.add_path('/Ac/L2/Current', None)
        service.add_path('/Ac/L2/Energy/Forward', None)
        service.add_path('/Ac/L2/Energy/Reverse', None)
        service.add_path('/Ac/L2/Power', None)
        service.add_path('/Ac/L2/Voltage', None)
        service.add_path('/Ac/L3/Current', None)
        service.add_path('/Ac/L3/Energy/Forward', None)
        service.add_path('/Ac/L3/Energy/Reverse', None)
        service.add_path('/Ac/L3/Power', None)
        service.add_path('/Ac/L3/Voltage', None)
        service.add_path('/Ac/Power', None)
class NetClient(Client):
    def __init__(self, proto):
        Client.__init__(self, proto)
        self.proto = proto

    def new_scanner(self, full):
        return NetScanner(self.proto, MODBUS_TCP_PORT, MODBUS_TCP_UNIT,
                          if_blacklist)

    def init(self, *args):
        super(NetClient, self).init(*args)

        svcname = 'com.victronenergy.modbusclient.%s' % self.name
        self.svc = VeDbusService(svcname, self.dbusconn)
        self.svc.add_path('/Scan',
                          False,
                          writeable=True,
                          onchangecallback=self.set_scan)
        self.svc.add_path('/ScanProgress', None, gettextcallback=percent)

        self.mdns = mdns.MDNS()
        self.mdns.start()
        self.mdns_check_time = 0
        self.mdns_query_time = 0

    def update(self):
        super(NetClient, self).update()

        now = time.time()

        if now - self.mdns_query_time > MDNS_QUERY_INTERVAL:
            self.mdns_query_time = now
            self.mdns.req()

        if now - self.mdns_check_time > MDNS_CHECK_INTERVAL:
            self.mdns_check_time = now
            maddr = self.mdns.get_devices()
            if maddr:
                units = probe.get_units('tcp')
                d = []
                for a in maddr:
                    d += ['tcp:%s:%s:%d' % (a[0], a[1], u) for u in units]
                self.probe_devices(d, nosave=True)

        return True
Example #16
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')
    def __init__(self, devname, address):
        self.imported = {}
        self.bus = (dbus.SessionBus(private=True) if 'DBUS_SESSION_BUS_ADDRESS'
                    in os.environ else dbus.SystemBus(private=True))
        self._dbusservice = VeDbusService(
            "com.victronenergy.vebus.mock-multiplus-%s-%s" %
            (devname, address),
            bus=self.bus)
        self.bus.add_signal_receiver(self.dbus_name_owner_changed,
                                     signal_name='NameOwnerChanged')

        logging.info('Searching dbus for vebus devices...')
        for serviceName in self.bus.list_names():
            self.scan_dbus_service(serviceName)
        logging.info('Finished search for vebus devices')

        self._error_message = ""
        self._disconnect = 0

        # Create the management objects, as specified in the ccgx dbus-api document
        self._dbusservice.add_path('/Mgmt/ProcessName', __file__)
        self._dbusservice.add_path('/Mgmt/ProcessVersion', softwareVersion)
        self._dbusservice.add_path(
            '/Mgmt/Connection', "Mock-Multiplus spawned by %s" % (devname, ))

        # Create the mandatory objects
        self._dbusservice.add_path('/DeviceInstance', address)
        self._dbusservice.add_path('/ProductId', address)
        self._dbusservice.add_path('/ProductName', "MockMultiplus")
        self._dbusservice.add_path('/FirmwareVersion', 0)
        self._dbusservice.add_path('/HardwareVersion', 0)
        self._dbusservice.add_path('/Connected', 0)

        # Readings
        self._dbusservice.add_path('/Energy/InverterToAcOut',
                                   0,
                                   gettextcallback=self._get_text)
        self._dbusservice.add_path('/DeviceType', "MockMultiplus")
        self._dbusservice.add_path('/ErrorCode',
                                   0,
                                   gettextcallback=self._get_text)
        self._dbusservice.add_path('/ErrorMessage', "")
Example #18
0
    def __init__(self, name, host, base, instance, cts):
        self.instance = instance
        self.cts = cts
        self.service = service = VeDbusService("{}.smappee_{:02d}".format(
            base, instance),
                                               bus=dbusconnection())

        # Add objects required by ve-api
        service.add_path('/Management/ProcessName', __file__)
        service.add_path('/Management/ProcessVersion', VERSION)
        service.add_path('/Management/Connection', host)
        service.add_path('/DeviceInstance', instance)
        service.add_path('/ProductId', 0xFFFF)  # 0xB012 ?
        service.add_path('/ProductName', "Smappee - {}".format(name))
        service.add_path('/FirmwareVersion', None)
        service.add_path('/Serial', None)
        service.add_path('/Connected', 1)

        _kwh = lambda p, v: (str(v) + 'KWh')
        _a = lambda p, v: (str(v) + 'A')
        _w = lambda p, v: (str(v) + 'W')
        _v = lambda p, v: (str(v) + 'V')

        service.add_path('/Ac/Energy/Forward', None, gettextcallback=_kwh)
        service.add_path('/Ac/Energy/Reverse', None, gettextcallback=_kwh)
        service.add_path('/Ac/L1/Current', None, gettextcallback=_a)
        service.add_path('/Ac/L1/Energy/Forward', None, gettextcallback=_kwh)
        service.add_path('/Ac/L1/Energy/Reverse', None, gettextcallback=_kwh)
        service.add_path('/Ac/L1/Power', None, gettextcallback=_w)
        service.add_path('/Ac/L1/Voltage', None, gettextcallback=_v)
        service.add_path('/Ac/L2/Current', None, gettextcallback=_a)
        service.add_path('/Ac/L2/Energy/Forward', None, gettextcallback=_kwh)
        service.add_path('/Ac/L2/Energy/Reverse', None, gettextcallback=_kwh)
        service.add_path('/Ac/L2/Power', None, gettextcallback=_w)
        service.add_path('/Ac/L2/Voltage', None, gettextcallback=_v)
        service.add_path('/Ac/L3/Current', None, gettextcallback=_a)
        service.add_path('/Ac/L3/Energy/Forward', None, gettextcallback=_kwh)
        service.add_path('/Ac/L3/Energy/Reverse', None, gettextcallback=_kwh)
        service.add_path('/Ac/L3/Power', None, gettextcallback=_w)
        service.add_path('/Ac/L3/Voltage', None, gettextcallback=_v)
        service.add_path('/Ac/Power', None, gettextcallback=_w)

        # Provide debug info about what cts make up what meter
        service.add_path('/Debug/Cts', ','.join(str(c) for c in cts))
Example #19
0
    def __init__(self, servicename, deviceinstance, paths, productname='Dummy product', connection='Dummy service'):
        self._dbusservice = VeDbusService(servicename)
        self._paths = paths

        logging.debug("%s /DeviceInstance = %d" % (servicename, deviceinstance))

        # Create the management objects, as specified in the ccgx dbus-api document
        self._dbusservice.add_path('/Mgmt/ProcessName', __file__)
        self._dbusservice.add_path('/Mgmt/ProcessVersion', 'Unkown version, and running on Python ' + platform.python_version())
        self._dbusservice.add_path('/Mgmt/Connection', connection)

        # Create the mandatory objects
        self._dbusservice.add_path('/DeviceInstance', deviceinstance)
        self._dbusservice.add_path('/ProductId', 0)
        self._dbusservice.add_path('/ProductName', productname)
        self._dbusservice.add_path('/FirmwareVersion', 0)
        self._dbusservice.add_path('/HardwareVersion', 0)
        self._dbusservice.add_path('/Connected', 1)

        for path, settings in self._paths.iteritems():
            self._dbusservice.add_path(
                path, settings['initial'], writeable=True, onchangecallback=self._handlechangedvalue)

        gobject.timeout_add(1000, self._update)
	def update_dbus_service(self):
		if (len(self._acSensors['L1']) > 0 or len(self._acSensors['L2']) > 0 or
			len(self._acSensors['L3']) > 0):

			if self._dbusService is None:

				pf = {0: 'input1', 1: 'output', 2: 'input2'}
				self._dbusService = VeDbusService('com.victronenergy.pvinverter.vebusacsensor_' + pf[self._name])
				#, self._dbusConn)

				self._dbusService.add_path('/Position', self._name, description=None, gettextcallback=self.gettextforposition)

				# Create the mandatory objects, as per victron dbus api document
				self._dbusService.add_path('/Mgmt/ProcessName', __file__)
				self._dbusService.add_path('/Mgmt/ProcessVersion', softwareVersion)
				self._dbusService.add_path('/Mgmt/Connection', 'AC Sensor on VE.Bus device')
				self._dbusService.add_path('/DeviceInstance', int(self._name) + 10)
				self._dbusService.add_path('/ProductId', 0xA141)
				self._dbusService.add_path('/ProductName', self._names[self._name])
				self._dbusService.add_path('/Connected', 1)

				logging.info('Added to D-Bus: ' + self.__str__())

			self.update_values()
Example #21
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')
Example #22
0
class DbusBatteryService:
    def __init__(self,
                 servicename,
                 deviceinstance,
                 voltage,
                 capacity,
                 productname='Valence U-BMS',
                 connection='can0'):
        self.minUpdateDone = 0
        self.dailyResetDone = 0

        self._bat = UbmsBattery(capacity=capacity,
                                voltage=voltage,
                                connection=connection)

        self._dbusservice = VeDbusService(servicename + '.socketcan_' +
                                          connection + '_di' +
                                          str(deviceinstance))

        logging.debug("%s /DeviceInstance = %d" %
                      (servicename + '.socketcan_' + connection + '_di' +
                       str(deviceinstance), deviceinstance))

        # Create the management objects, as specified in the ccgx dbus-api document
        self._dbusservice.add_path('/Mgmt/ProcessName', __file__)
        self._dbusservice.add_path(
            '/Mgmt/ProcessVersion',
            VERSION + ' running on Python ' + platform.python_version())
        self._dbusservice.add_path('/Mgmt/Connection', connection)

        # Create the mandatory objects
        self._dbusservice.add_path('/DeviceInstance', deviceinstance)
        self._dbusservice.add_path('/ProductId', 0)
        self._dbusservice.add_path('/ProductName', productname)
        self._dbusservice.add_path('/FirmwareVersion', 'unknown')
        self._dbusservice.add_path('/HardwareVersion', 'unknown')
        self._dbusservice.add_path('/Connected', 0)
        # Create battery specific objects
        self._dbusservice.add_path('/Status', 0)
        self._dbusservice.add_path('/Mode',
                                   1,
                                   writeable=True,
                                   onchangecallback=self._transmit_mode)
        self._dbusservice.add_path('/Soh', 100)
        self._dbusservice.add_path('/Capacity', int(capacity))
        self._dbusservice.add_path('/InstalledCapacity', int(capacity))
        self._dbusservice.add_path('/Dc/0/Temperature', 25)
        self._dbusservice.add_path('/Info/MaxChargeCurrent', 70)
        self._dbusservice.add_path('/Info/MaxDischargeCurrent', 150)
        self._dbusservice.add_path('/Info/MaxChargeVoltage', float(voltage))
        self._dbusservice.add_path('/Info/BatteryLowVoltage', 24.0)
        self._dbusservice.add_path('/Alarms/CellImbalance', 0)
        self._dbusservice.add_path('/Alarms/LowVoltage', 0)
        self._dbusservice.add_path('/Alarms/HighVoltage', 0)
        self._dbusservice.add_path('/Alarms/HighDischargeCurrent', 0)
        self._dbusservice.add_path('/Alarms/HighChargeCurrent', 0)
        self._dbusservice.add_path('/Alarms/LowSoc', 0)
        self._dbusservice.add_path('/Alarms/LowTemperature', 0)
        self._dbusservice.add_path('/Alarms/HighTemperature', 0)
        self._dbusservice.add_path('/Balancing', 0)
        self._dbusservice.add_path('/System/HasTemperature', 1)
        self._dbusservice.add_path('/System/NrOfBatteries', 10)
        self._dbusservice.add_path('/System/NrOfModulesOnline', 10)
        self._dbusservice.add_path('/System/NrOfModulesOffline', 0)
        self._dbusservice.add_path('/System/NrOfModulesBlockingDischarge', 0)
        self._dbusservice.add_path('/System/NrOfModulesBlockingCharge', 0)
        self._dbusservice.add_path('/System/NrOfBatteriesBalancing', 0)
        self._dbusservice.add_path('/System/BatteriesParallel', 5)
        self._dbusservice.add_path('/System/BatteriesSeries', 2)
        self._dbusservice.add_path('/System/NrOfCellsPerBattery', 4)
        self._dbusservice.add_path('/System/MinVoltageCellId', 'M_C_')
        self._dbusservice.add_path('/System/MaxVoltageCellId', 'M_C_')
        self._dbusservice.add_path('/System/MinCellTemperature', 10.0)
        self._dbusservice.add_path('/System/MaxCellTemperature', 10.0)
        self._dbusservice.add_path('/System/MaxPcbTemperature', 10.0)

        self._settings = SettingsDevice(
            bus=dbus.SystemBus() if
            (platform.machine() == 'armv7l') else dbus.SessionBus(),
            supportedSettings={
                'AvgDischarge':
                ['/Settings/Ubms/AvgerageDischarge', 0.0, 0, 0],
                'TotalAhDrawn': ['/Settings/Ubms/TotalAhDrawn', 0.0, 0, 0],
                'TimeLastFull': ['/Settings/Ubms/TimeLastFull', 0.0, 0, 0],
                'MinCellVoltage':
                ['/Settings/Ubms/MinCellVoltage', 4.0, 2.0, 4.0],
                'MaxCellVoltage':
                ['/Settings/Ubms/MaxCellVoltage', 2.0, 2.0, 4.0],
                'interval': ['/Settings/Ubms/Interval', 50, 50, 200]
            },
            eventCallback=handle_changed_setting)

        self._summeditems = {
            '/System/MaxCellVoltage': {
                'gettext': '%.2F V'
            },
            '/System/MinCellVoltage': {
                'gettext': '%.2F V'
            },
            '/Dc/0/Voltage': {
                'gettext': '%.2F V'
            },
            '/Dc/0/Current': {
                'gettext': '%.1F A'
            },
            '/Dc/0/Power': {
                'gettext': '%.0F W'
            },
            '/Soc': {
                'gettext': '%.0F %%'
            },
            '/History/TotalAhDrawn': {
                'gettext': '%.0F Ah'
            },
            '/History/DischargedEnergy': {
                'gettext': '%.2F kWh'
            },
            '/History/ChargedEnergy': {
                'gettext': '%.2F kWh'
            },
            '/History/AverageDischarge': {
                'gettext': '%.2F kWh'
            },
            '/TimeToGo': {
                'gettext': '%.0F s'
            },
            '/ConsumedAmphours': {
                'gettext': '%.1F Ah'
            }
        }
        for path in self._summeditems.keys():
            self._dbusservice.add_path(path,
                                       value=None,
                                       gettextcallback=self._gettext)

        self._dbusservice['/History/AverageDischarge'] = self._settings[
            'AvgDischarge']
        self._dbusservice['/History/TotalAhDrawn'] = self._settings[
            'TotalAhDrawn']
        self._dbusservice.add_path('/History/TimeSinceLastFullCharge', 0)
        self._dbusservice.add_path('/History/MinCellVoltage',
                                   self._settings['MinCellVoltage'])
        self._dbusservice.add_path('/History/MaxCellVoltage',
                                   self._settings['MaxCellVoltage'])
        self._dbusservice['/ConsumedAmphours'] = 0

        logging.info(
            "History cell voltage min: %.3f, max: %.3f, totalAhDrawn: %d",
            self._settings['MinCellVoltage'], self._settings['MaxCellVoltage'],
            self._settings['TotalAhDrawn'])

        self._dbusservice['/History/ChargedEnergy'] = 0
        self._dbusservice['/History/DischargedEnergy'] = 0

        gobject.timeout_add(self._settings['interval'], exit_on_error,
                            self._update)

    def _gettext(self, path, value):
        item = self._summeditems.get(path)
        if item is not None:
            return item['gettext'] % value
        return str(value)

    def _transmit_mode(self, path, value):
        self._bat.set_mode(value)

    def __del__(self):
        self._safe_history()
        logging.info('Stopping dbus_ubms')

    def _safe_history(self):
        logging.debug('Saving history to localsettings')
        self._settings['AvgDischarge'] = self._dbusservice[
            '/History/AverageDischarge']
        self._settings['TotalAhDrawn'] = self._dbusservice[
            '/History/TotalAhDrawn']
        self._settings['MinCellVoltage'] = self._dbusservice[
            '/History/MinCellVoltage']
        self._settings['MaxCellVoltage'] = self._dbusservice[
            '/History/MaxCellVoltage']

    def _daily_stats(self):
        if (self._dbusservice['/History/DischargedEnergy'] == 0): return
        logging.info(
            "Updating stats, SOC: %d, Discharged: %.2f, Charged: %.2f ",
            self._bat.soc, self._dbusservice['/History/DischargedEnergy'],
            self._dbusservice['/History/ChargedEnergy'])
        self._dbusservice['/History/AverageDischarge'] = (
            6 * self._dbusservice['/History/AverageDischarge'] +
            self._dbusservice['/History/DischargedEnergy']) / 7  #rolling week
        self._dbusservice['/History/ChargedEnergy'] = 0
        self._dbusservice['/History/DischargedEnergy'] = 0
        dt = datetime.now() - datetime.fromtimestamp(
            float(self._settings['TimeLastFull']))
        #if full within the last 24h and more than *0% consumed, estimate actual capacity and SOH based on consumed amphours from full and SOC reported
        if dt.total_seconds() < 24 * 3600 and self._bat.soc < 70:
            self._dbusservice['/Capacity'] = int(
                -self._dbusservice['/ConsumedAmphours'] * 100 /
                (100 - self._bat.soc))
            self._dbusservice['/Soh'] = int(
                self._dbusservice['/Capacity'] * 100 /
                self._dbusservice['/InstalledCapacity'])
            logging.info("SOH: %d, Capacity: %d ", self._dbusservice['/Soh'],
                         self._dbusservice['/Capacity'])
        self.dailyResetDone = datetime.now().day

    def _update(self):
        if self._bat.updated != -1:
            self._dbusservice['/Connected'] = 1
        else:
            self._dbusservice['/Connected'] = 0

#	self._dbusservice['/Alarms/CellImbalance'] = (self._bat.internalErrors & 0x20)>>5
        deltaCellVoltage = self._bat.maxCellVoltage - self._bat.minCellVoltage

        # flag cell imbalance, only log first occurence
        if (deltaCellVoltage > 0.25):
            self._dbusservice['/Alarms/CellImbalance'] = 2
            if self._bat.balanced:
                logging.error(
                    "Cell voltage imbalance: %.2fV, SOC: %d, @Module: %d ",
                    deltaCellVoltage, self._bat.soc,
                    self.moduleSoc.index(min(self.moduleSoc)))
                logging.info("SOC: %d ", self._bat.soc)
            self._bat.balanced = False
        elif (deltaCellVoltage >= 0.18):
            # warn only if not already balancing (UBMS threshold is 0.15V)
            if self._bat.numberOfModulesBalancing == 0:
                self._dbusservice['/Alarms/CellImbalance'] = 1
            if self._bat.balanced:
                chain = itertools.chain(*self._bat.cellVoltages)
                flatVList = list(chain)
                iMax = flatVList.index(max(flatVList))
                iMin = flatVList.index(min(flatVList))
                logging.info(
                    "Cell voltage imbalance: %.2fV, iMin: %d, iMax %d, SOC: %d ",
                    deltaCellVoltage, iMin, iMax, self._bat.soc)
            self._bat.balanced = False
        else:
            self._dbusservice['/Alarms/CellImbalance'] = 0
            self._bat.balanced = True

        self._dbusservice['/Alarms/LowVoltage'] = (
            self._bat.voltageAndCellTAlarms & 0x10) >> 3
        self._dbusservice['/Alarms/HighVoltage'] = (
            self._bat.voltageAndCellTAlarms & 0x20) >> 4
        self._dbusservice['/Alarms/LowSoc'] = (self._bat.voltageAndCellTAlarms
                                               & 0x08) >> 3
        self._dbusservice['/Alarms/HighDischargeCurrent'] = (
            self._bat.currentAndPcbTAlarms & 0x3)

        #       flag high cell temperature alarm and high pcb temperature alarm
        self._dbusservice['/Alarms/HighTemperature'] = (
            self._bat.voltageAndCellTAlarms
            & 0x6) >> 1 | (self._bat.currentAndPcbTAlarms & 0x18) >> 3
        self._dbusservice['/Alarms/LowTemperature'] = (self._bat.mode
                                                       & 0x60) >> 5

        self._dbusservice['/Soc'] = self._bat.soc
        dt = datetime.now() - datetime.fromtimestamp(
            float(self._settings['TimeLastFull']))
        self._dbusservice['/History/TimeSinceLastFullCharge'] = (
            dt.seconds + dt.days * 24 * 3600)

        if self._bat.soc == 100 or self._bat.chargeComplete:
            #reset used Amphours to zero
            self._dbusservice['/ConsumedAmphours'] = 0
            if datetime.fromtimestamp(time()).day != datetime.fromtimestamp(
                    float(self._settings['TimeLastFull'])).day:
                #and if it is the first time that day also create log entry
                logging.info("Fully charged, Discharged: %.2f, Charged: %.2f ",
                             self._dbusservice['/History/DischargedEnergy'],
                             self._dbusservice['/History/ChargedEnergy'])
                self._settings['TimeLastFull'] = time()

        self._dbusservice[
            '/Status'] = self._bat.mode & 0xC  #self._bat.opState[self._bat.mode & 0xC]
        self._dbusservice['/Mode'] = self._bat.opModes[self._bat.mode & 0x3]
        self._dbusservice['/Balancing'] = (self._bat.mode & 0x10) >> 4
        self._dbusservice['/Dc/0/Current'] = self._bat.current
        self._dbusservice['/Dc/0/Voltage'] = self._bat.voltage
        power = self._bat.voltage * self._bat.current
        self._dbusservice['/Dc/0/Power'] = power
        self._dbusservice['/Dc/0/Temperature'] = self._bat.maxCellTemperature

        #only update the below every 10s to reduce load
        #	if datetime.now().second not in [0, 20, 40]:
        #		return True

        chain = itertools.chain(*self._bat.cellVoltages)
        flatVList = list(chain)
        index = flatVList.index(max(flatVList))
        m = index / 4
        c = index % 4
        self._dbusservice['/System/MaxVoltageCellId'] = 'M' + str(
            m + 1) + 'C' + str(c + 1)
        index = flatVList.index(min(flatVList))
        m = index / 4
        c = index % 4
        self._dbusservice['/System/MinVoltageCellId'] = 'M' + str(
            m + 1) + 'C' + str(c + 1)
        self._dbusservice['/System/MaxCellVoltage'] = self._bat.maxCellVoltage
        if (self._bat.maxCellVoltage >
                self._dbusservice['/History/MaxCellVoltage']):
            self._dbusservice[
                '/History/MaxCellVoltage'] = self._bat.maxCellVoltage
            logging.info("New maximum cell voltage: %f",
                         self._bat.maxCellVoltage)
        self._dbusservice['/System/MinCellVoltage'] = self._bat.minCellVoltage
        if (0 < self._bat.minCellVoltage <
                self._dbusservice['/History/MinCellVoltage']):
            self._dbusservice[
                '/History/MinCellVoltage'] = self._bat.minCellVoltage
            logging.info("New minimum cell voltage: %f",
                         self._bat.minCellVoltage)
        self._dbusservice[
            '/System/MinCellTemperature'] = self._bat.minCellTemperature
        self._dbusservice[
            '/System/MaxCellTemperature'] = self._bat.maxCellTemperature
        self._dbusservice[
            '/System/MaxPcbTemperature'] = self._bat.maxPcbTemperature
        self._dbusservice[
            '/Info/MaxChargeCurrent'] = self._bat.maxChargeCurrent
        self._dbusservice[
            '/Info/MaxDischargeCurrent'] = self._bat.maxDischargeCurrent
        self._dbusservice[
            '/Info/MaxChargeVoltage'] = self._bat.maxChargeVoltage
        self._dbusservice[
            '/System/NrOfModulesOnline'] = self._bat.numberOfModulesCommunicating
        self._dbusservice[
            '/System/NrOfModulesOffline'] = self._bat.numberOfModules - self._bat.numberOfModulesCommunicating
        self._dbusservice[
            '/System/NrOfBatteriesBalancing'] = self._bat.numberOfModulesBalancing

        #update energy statistics daily at 6:00,
        if datetime.now().hour == 6 and datetime.now(
        ).minute == 0 and datetime.now().day != self.dailyResetDone:
            self._daily_stats()

        now = datetime.now().time()
        if now.minute != self.minUpdateDone:
            self.minUpdateDone = now.minute
            if self._bat.current > 0:
                #charging
                self._dbusservice[
                    '/History/ChargedEnergy'] += power * 1.666667e-5  #kWh
                #calculate time to full, in the absense of a mutex accessing bat.current value might have changed
                #to 0 after check above, so try to catch a div0
                try:
                    self._dbusservice['/TimeToGo'] = (
                        100 - self._bat.soc
                    ) * self._bat.capacity * 36 / self._bat.current
                except:
                    self._dbusservice[
                        '/TimeToGo'] = self._bat.soc * self._bat.capacity * 36
            else:
                #discharging
                self._dbusservice[
                    '/ConsumedAmphours'] += self._bat.current * 0.016667  #Ah
                self._dbusservice[
                    '/History/TotalAhDrawn'] += self._bat.current * 0.016667  #Ah
                self._dbusservice[
                    '/History/DischargedEnergy'] += power * 1.666667e-5  #kWh

                #calculate time to empty
                try:
                    self._dbusservice[
                        '/TimeToGo'] = self._bat.soc * self._bat.capacity * 36 / (
                            -self._bat.current)
                except:
                    self._dbusservice[
                        '/TimeToGo'] = self._bat.soc * self._bat.capacity * 36

            self._safe_history()

        self._bat.updated = -1
        return True
class VBus():
    def __init__(self):
        self.dbusservice = None  #dbus service variable
        self.args = ""  #extract parse argument
        self.init_on = 0  #variable to init the vebus service

    #-----------------------------------------------------------------------------
    # Initializes the different arguments to add.
    # ENTRIES:
    #   Nothing
    # RETURNS:
    #   Nothing
    #-----------------------------------------------------------------------------
    def parser_arguments(self):
        # Argument parsing
        parser = ArgumentParser(description='Wind+ with CCGX monitoring',
                                add_help=True)
        parser.add_argument("-n",
                            "--name",
                            help="the D-Bus service you want me to claim",
                            type=str,
                            default="com.windcharger.bornay_ttyUSB0")
        parser.add_argument("-i",
                            "--deviceinstance",
                            help="the device instance you want me to be",
                            type=str,
                            default="0")
        parser.add_argument("-d",
                            "--debug",
                            help="set logging level to debug",
                            action="store_true")
        parser.add_argument('-s', '--serial', default='/dev/ttyUSB0')

        self.args = parser.parse_args()
        log.info(self.args)
        # Init logging
        logging.basicConfig(
            level=(logging.DEBUG if self.args.debug else logging.INFO))
        log.info(__file__ + " is starting up")
        logLevel = {
            0: 'NOTSET',
            10: 'DEBUG',
            20: 'INFO',
            30: 'WARNING',
            40: 'ERROR'
        }
        log.info('Loglevel set to ' + logLevel[log.getEffectiveLevel()])

    #-----------------------------------------------------------------------------
    # Initializes the vbus protocol.
    # ENTRIES:
    #   Nothing
    # RETURNS:
    #   Nothing
    #-----------------------------------------------------------------------------
    def Init(self):
        try:
            DBusGMainLoop(set_as_default=True)
            serial = os.path.basename(self.args.serial)
            self.dbusservice = VeDbusService(
                'com.victronenergy.windcharger.bornay_' + serial)
            self.__mandatory__()
            self.__objects_dbus__()
        except:
            log.warn("Bornay wind+ has been created before")
            self.__mandatory__()

    #-----------------------------------------------------------------------------
    # Registers the mandatory instances
    # ENTRIES:
    #   Nothing
    # RETURNS:
    #   Nothing
    #-----------------------------------------------------------------------------
    def __mandatory__(self):
        try:
            log.info("using device instance 0")

            # Create the management objects, as specified in the ccgx dbus-api document
            self.dbusservice.add_path('/Management/ProcessName', __file__)
            self.dbusservice.add_path(
                '/Management/ProcessVersion',
                'Version {} running on Python {}'.format(
                    __version__, sys.version))
            self.dbusservice.add_path('/Management/Connection', 'ModBus RTU')

            # Create the mandatory objects
            self.dbusservice.add_path('/DeviceInstance', 0)
            self.dbusservice.add_path('/ProductId', 0)
            self.dbusservice.add_path('/ProductName', 'Bornay Wind+ MPPT')
            self.dbusservice.add_path('/FirmwareVersion', __version__)
            self.dbusservice.add_path('/HardwareVersion', 1.01)
            self.dbusservice.add_path('/Connected', 1)
        except:
            log.warn("Mandatory Bornay wind+ has been created before")
            self.__objects_dbus__()

    #-----------------------------------------------------------------------------
    # Creates the different registers to save.
    # ENTRIES:
    #   Nothing
    # RETURNS:
    #   Nothing
    #-----------------------------------------------------------------------------
    def __objects_dbus__(self):
        try:
            # Create all the objects that we want to export to the dbus
            # All are initialized with the same value 0
            self.dbusservice.add_path('/Mppt/StatusMEF', 0, writeable=True)
            self.dbusservice.add_path('/Mppt/RefMEF', 0, writeable=True)
            self.dbusservice.add_path('/Turbine/BatPowerLastMin',
                                      0,
                                      writeable=True)
            self.dbusservice.add_path('/Turbine/BatPowerLastHour',
                                      0,
                                      writeable=True)
            self.dbusservice.add_path('/Turbine/BreakerPowerLastMin',
                                      0,
                                      writeable=True)
            self.dbusservice.add_path('/Turbine/WindSpeedLastMin',
                                      0,
                                      writeable=True)
            self.dbusservice.add_path('/Turbine/WindSpeedLastHour',
                                      0,
                                      writeable=True)
            self.dbusservice.add_path('/Mppt/Phase', 0, writeable=True)
            self.dbusservice.add_path('/Mppt/SinkTemp', 0, writeable=True)
            self.dbusservice.add_path('/Mppt/BoxTemp', 0, writeable=True)
            self.dbusservice.add_path('/Flags/ElevatedVoltage',
                                      0,
                                      writeable=True)
            self.dbusservice.add_path('/Flags/Extrem', 0, writeable=True)
            self.dbusservice.add_path('/Flags/ExternSupply', 0, writeable=True)
            self.dbusservice.add_path('/Flags/ElevatedWind', 0, writeable=True)
            self.dbusservice.add_path('/Flags/FanState', 0, writeable=True)
            self.dbusservice.add_path('/Flags/EmergencyButton',
                                      0,
                                      writeable=True)
            self.dbusservice.add_path('/Turbine/RPM', 0, writeable=True)
            self.dbusservice.add_path('/History/Overall/MaxRPM',
                                      0,
                                      writeable=True)
            self.dbusservice.add_path('/Mppt/DuttyCycle', 0, writeable=True)
            self.dbusservice.add_path('/Turbine/WindSpeed', 0, writeable=True)
            self.dbusservice.add_path('/Turbine/VDC', 0, writeable=True)
            self.dbusservice.add_path('/Dc/0/Current', 0, writeable=True)
            self.dbusservice.add_path('/Turbine/IBrk', 0, writeable=True)
            self.dbusservice.add_path('/Dc/0/Power', 0, writeable=True)
            self.dbusservice.add_path('/Turbine/AvailablePower',
                                      0,
                                      writeable=True)
            self.dbusservice.add_path('/Turbine/Stop', 0, writeable=True)
            self.dbusservice.add_path('/Dc/0/Voltage', 0, writeable=True)
            self.dbusservice.add_path('/Mppt/ChargerState', 0, writeable=True)
            self.dbusservice.add_path('/Turbine/EstimatedWind',
                                      0,
                                      writeable=True)
            self.dbusservice.add_path('/Flags/ChargedBattery',
                                      0,
                                      writeable=True)
            self.dbusservice.add_path('/Mppt/AbsortionTime', 0, writeable=True)
        except:
            log.warn("Bornay wind+ objects has been created before")

    #-----------------------------------------------------------------------------
    # Update the different registers to save in dbus.
    # ENTRIES:
    #   -modbus_values: Vector who contains the bornay modbus values
    # RETURNS:
    #   Nothing
    #-----------------------------------------------------------------------------
    def update_modbus_values(self, value_modbus):
        self.dbusservice['/Mppt/StatusMEF'] = value_modbus[0]
        self.dbusservice['/Mppt/RefMEF'] = value_modbus[1]
        self.dbusservice['/Turbine/BatPowerLastMin'] = value_modbus[2]
        self.dbusservice['/Turbine/BatPowerLastHour'] = value_modbus[3]
        self.dbusservice['/Turbine/BreakerPowerLastMin'] = value_modbus[4]
        self.dbusservice['/Turbine/WindSpeedLastMin'] = value_modbus[5]
        self.dbusservice['/Turbine/WindSpeedLastHour'] = value_modbus[6]
        self.dbusservice['/Mppt/Phase'] = value_modbus[7]
        self.dbusservice['/Mppt/SinkTemp'] = (value_modbus[8] / 10)
        self.dbusservice['/Mppt/BoxTemp'] = (value_modbus[9] / 10)
        self.dbusservice['/Flags/ElevatedVoltage'] = value_modbus[10]
        self.dbusservice['/Flags/Extrem'] = value_modbus[11]
        self.dbusservice['/Flags/ExternSupply'] = value_modbus[12]
        self.dbusservice['/Flags/ElevatedWind'] = value_modbus[13]
        self.dbusservice['/Flags/FanState'] = value_modbus[14]
        self.dbusservice['/Flags/EmergencyButton'] = value_modbus[15]
        self.dbusservice['/Turbine/RPM'] = value_modbus[16]
        self.dbusservice['/History/Overall/MaxRPM'] = value_modbus[17]
        self.dbusservice['/Mppt/DuttyCycle'] = value_modbus[18]
        self.dbusservice['/Turbine/WindSpeed'] = (value_modbus[19] / 100)
        self.dbusservice['/Turbine/VDC'] = (value_modbus[20] / 10)
        self.dbusservice['/Dc/0/Current'] = (value_modbus[21] / 10)
        self.dbusservice['/Turbine/IBrk'] = (value_modbus[22] / 10)
        self.dbusservice['/Dc/0/Power'] = value_modbus[23]
        self.dbusservice['/Turbine/AvailablePower'] = value_modbus[24]
        self.dbusservice['/Turbine/Stop'] = value_modbus[25]
        self.dbusservice['/Dc/0/Voltage'] = (value_modbus[26] / 10)
        self.dbusservice['/Mppt/ChargerState'] = value_modbus[27]
        self.dbusservice['/Turbine/EstimatedWind'] = (value_modbus[28] / 10)
        self.dbusservice['/Flags/ChargedBattery'] = value_modbus[29]
        self.dbusservice['/Mppt/AbsortionTime'] = value_modbus[30]
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 setup_vedbus(self, instance):

        self._dbusservice = VeDbusService("com.victronenergy.battery." +
                                          self.port[self.port.rfind('/') + 1:])
        logger.debug("%s /DeviceInstance = %d" %
                     ("com.victronenergy.battery." +
                      self.port[self.port.rfind('/') + 1:], instance))

        # Create the management objects, as specified in the ccgx dbus-api document
        self._dbusservice.add_path('/Mgmt/ProcessName', __file__)
        self._dbusservice.add_path('/Mgmt/ProcessVersion',
                                   'Python ' + platform.python_version())
        self._dbusservice.add_path('/Mgmt/Connection', 'Serial ' + self.port)

        # Create the mandatory objects
        self._dbusservice.add_path('/DeviceInstance', instance)
        self._dbusservice.add_path('/ProductId', 0x0)
        self._dbusservice.add_path('/ProductName', 'SerialBattery (LTT)')
        self._dbusservice.add_path('/FirmwareVersion', self.version)
        self._dbusservice.add_path('/HardwareVersion', self.hardware_version)
        self._dbusservice.add_path('/Connected', 1)
        # Create static battery info
        self._dbusservice.add_path('/Info/BatteryLowVoltage',
                                   self.min_battery_voltage,
                                   writeable=True)
        self._dbusservice.add_path('/Info/MaxChargeVoltage',
                                   self.max_battery_voltage,
                                   writeable=True)
        self._dbusservice.add_path('/Info/MaxChargeCurrent',
                                   MAX_BATTERY_CURRENT,
                                   writeable=True)
        self._dbusservice.add_path('/Info/MaxDischargeCurrent',
                                   MAX_BATTERY_DISCHARGE_CURRENT,
                                   writeable=True)
        self._dbusservice.add_path('/System/NrOfCellsPerBattery',
                                   self.cell_count,
                                   writeable=True)
        self._dbusservice.add_path('/System/NrOfModulesOnline',
                                   1,
                                   writeable=True)
        self._dbusservice.add_path('/System/NrOfModulesOffline',
                                   None,
                                   writeable=True)
        self._dbusservice.add_path('/System/NrOfModulesBlockingCharge',
                                   None,
                                   writeable=True)
        self._dbusservice.add_path('/System/NrOfModulesBlockingDischarge',
                                   None,
                                   writeable=True)
        # Not used at this stage
        # self._dbusservice.add_path('/System/MinTemperatureCellId', None, writeable=True)
        # self._dbusservice.add_path('/System/MaxTemperatureCellId', None, writeable=True)
        self._dbusservice.add_path('/Capacity', self.capacity, writeable=True)
        # Create SOC, DC and System items
        self._dbusservice.add_path('/Soc', None, writeable=True)
        self._dbusservice.add_path('/Dc/0/Voltage', None, writeable=True)
        self._dbusservice.add_path('/Dc/0/Current', None, writeable=True)
        self._dbusservice.add_path('/Dc/0/Power', None, writeable=True)
        self._dbusservice.add_path('/Dc/0/Temperature', 21.0, writeable=True)
        # Create battery extras
        self._dbusservice.add_path('/System/MinCellTemperature',
                                   None,
                                   writeable=True)
        self._dbusservice.add_path('/System/MaxCellTemperature',
                                   None,
                                   writeable=True)
        self._dbusservice.add_path('/System/MaxCellVoltage',
                                   0.0,
                                   writeable=True)
        self._dbusservice.add_path('/System/MaxVoltageCellId',
                                   '',
                                   writeable=True)
        self._dbusservice.add_path('/System/MinCellVoltage',
                                   0.0,
                                   writeable=True)
        self._dbusservice.add_path('/System/MinVoltageCellId',
                                   '',
                                   writeable=True)
        self._dbusservice.add_path('/History/ChargeCycles', 0, writeable=True)
        self._dbusservice.add_path('/Balancing', 0, writeable=True)
        self._dbusservice.add_path('/Io/AllowToCharge', 0, writeable=True)
        self._dbusservice.add_path('/Io/AllowToDischarge', 0, writeable=True)
        # Create the alarms
        self._dbusservice.add_path('/Alarms/LowVoltage', 0, writeable=True)
        self._dbusservice.add_path('/Alarms/HighVoltage', 0, writeable=True)
        self._dbusservice.add_path('/Alarms/LowSoc', 0, writeable=True)
        self._dbusservice.add_path('/Alarms/HighChargeCurrent',
                                   0,
                                   writeable=True)
        self._dbusservice.add_path('/Alarms/HighDischargeCurrent',
                                   0,
                                   writeable=True)
        self._dbusservice.add_path('/Alarms/CellImbalance', 0, writeable=True)
        self._dbusservice.add_path('/Alarms/InternalFailure',
                                   0,
                                   writeable=True)
        self._dbusservice.add_path('/Alarms/HighChargeTemperature',
                                   0,
                                   writeable=True)
        self._dbusservice.add_path('/Alarms/LowChargeTemperature',
                                   0,
                                   writeable=True)
        self._dbusservice.add_path('/Alarms/HighTemperature',
                                   0,
                                   writeable=True)
        self._dbusservice.add_path('/Alarms/LowTemperature', 0, writeable=True)
	def __init__(self, dbusmonitor_gen=None, dbusservice_gen=None, settings_device_gen=None):
		self.STATE_IDLE = 0
		self.STATE_CHARGING = 1
		self.STATE_DISCHARGING = 2

		self.BATSERVICE_DEFAULT = 'default'
		self.BATSERVICE_NOBATTERY = 'nobattery'

		# Why this dummy? Because DbusMonitor expects these values to be there, even though we don't
		# need them. So just add some dummy data. This can go away when DbusMonitor is more generic.
		dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None}
		dbus_tree = {
			'com.victronenergy.solarcharger': {
				'/Connected': dummy,
				'/ProductName': dummy,
				'/Mgmt/Connection': dummy,
				'/Dc/0/Voltage': dummy,
				'/Dc/0/Current': dummy},
			'com.victronenergy.pvinverter': {
				'/Connected': dummy,
				'/ProductName': dummy,
				'/Mgmt/Connection': dummy,
				'/Ac/L1/Power': dummy,
				'/Ac/L2/Power': dummy,
				'/Ac/L3/Power': dummy,
				'/Position': dummy,
				'/ProductId': dummy},
			'com.victronenergy.battery': {
				'/Connected': dummy,
				'/ProductName': dummy,
				'/Mgmt/Connection': dummy,
				'/Dc/0/Voltage': dummy,
				'/Dc/0/Current': dummy,
				'/Dc/0/Power': dummy,
				'/Soc': dummy,
				'/TimeToGo': dummy,
				'/ConsumedAmphours': dummy},
			'com.victronenergy.vebus' : {
				'/Ac/ActiveIn/ActiveInput': dummy,
				'/Ac/ActiveIn/L1/P': dummy,
				'/Ac/ActiveIn/L2/P': dummy,
				'/Ac/ActiveIn/L3/P': dummy,
				'/Ac/Out/L1/P': dummy,
				'/Ac/Out/L2/P': dummy,
				'/Ac/Out/L3/P': dummy,
				'/Hub4/AcPowerSetpoint': dummy,
				'/ProductName': dummy,
				'/Mgmt/Connection': dummy,
				'/Dc/0/Voltage': dummy,
				'/Dc/0/Current': dummy,
				'/Dc/0/Power': dummy,
				'/Soc': dummy},
			'com.victronenergy.charger': {
				'/ProductName': dummy,
				'/Mgmt/Connection': dummy,
				'/Dc/0/Voltage': dummy,
				'/Dc/0/Current': dummy},
			'com.victronenergy.grid' : {
				'/ProductName': dummy,
				'/Mgmt/Connection': dummy,
				'/ProductId' : dummy,
				'/DeviceType' : dummy,
				'/Ac/L1/Power': dummy,
				'/Ac/L2/Power': dummy,
				'/Ac/L3/Power': dummy},
			'com.victronenergy.genset' : {
				'/ProductName': dummy,
				'/Mgmt/Connection': dummy,
				'/ProductId' : dummy,
				'/DeviceType' : dummy,
				'/Ac/L1/Power': dummy,
				'/Ac/L2/Power': dummy,
				'/Ac/L3/Power': dummy},
			'com.victronenergy.settings' : {
				'/Settings/SystemSetup/AcInput1' : dummy,
				'/Settings/SystemSetup/AcInput2' : dummy}
		}

		if dbusmonitor_gen is None:
			self._dbusmonitor = DbusMonitor(dbus_tree, self._dbus_value_changed, self._device_added, self._device_removed)
		else:
			self._dbusmonitor = dbusmonitor_gen(dbus_tree)

		# Connect to localsettings
		supported_settings = {
			'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0],
			'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1],
			'writevebussoc': ['/Settings/SystemSetup/WriteVebusSoc', 0, 0, 1]}

		if settings_device_gen is None:
			self._settings = SettingsDevice(
				bus=dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus(),
				supportedSettings=supported_settings,
				eventCallback=self._handlechangedsetting)
		else:
			self._settings = settings_device_gen(supported_settings, self._handlechangedsetting)

		# put ourselves on the dbus
		if dbusservice_gen is None:
			self._dbusservice = VeDbusService('com.victronenergy.system')
		else:
			self._dbusservice = dbusservice_gen('com.victronenergy.system')

		self._dbusservice.add_mandatory_paths(
			processname=__file__,
			processversion=softwareVersion,
			connection='data from other dbus processes',
			deviceinstance=0,
			productid=None,
			productname=None,
			firmwareversion=None,
			hardwareversion=None,
			connected=1)

		# At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely
		# identifying the CCGX.
		self._dbusservice.add_path('/Serial', value=get_vrm_portal_id())

		self._dbusservice.add_path(
			'/AvailableBatteryServices', value=None, gettextcallback=self._gettext)
		self._dbusservice.add_path(
			'/AvailableBatteryMeasurements', value=None, gettextcallback=self._gettext)
		self._dbusservice.add_path(
			'/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext)
		self._dbusservice.add_path(
			'/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext)
		self._dbusservice.add_path(
			'/ActiveBatteryService', value=None, gettextcallback=self._gettext)
		self._dbusservice.add_path(
			'/PvInvertersProductIds', value=None)
		self._summeditems = {
			'/Ac/Grid/L1/Power': {'gettext': '%.0F W'},
			'/Ac/Grid/L2/Power': {'gettext': '%.0F W'},
			'/Ac/Grid/L3/Power': {'gettext': '%.0F W'},
			'/Ac/Grid/Total/Power': {'gettext': '%.0F W'},
			'/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'},
			'/Ac/Grid/ProductId': {'gettext': '%s'},
			'/Ac/Grid/DeviceType': {'gettext': '%s'},
			'/Ac/Genset/L1/Power': {'gettext': '%.0F W'},
			'/Ac/Genset/L2/Power': {'gettext': '%.0F W'},
			'/Ac/Genset/L3/Power': {'gettext': '%.0F W'},
			'/Ac/Genset/Total/Power': {'gettext': '%.0F W'},
			'/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'},
			'/Ac/Genset/ProductId': {'gettext': '%s'},
			'/Ac/Genset/DeviceType': {'gettext': '%s'},
			'/Ac/Consumption/L1/Power': {'gettext': '%.0F W'},
			'/Ac/Consumption/L2/Power': {'gettext': '%.0F W'},
			'/Ac/Consumption/L3/Power': {'gettext': '%.0F W'},
			'/Ac/Consumption/Total/Power': {'gettext': '%.0F W'},
			'/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'},
			'/Ac/PvOnOutput/L1/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnOutput/L2/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnOutput/L3/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnOutput/Total/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnOutput/NumberOfPhases': {'gettext': '%.0F W'},
			'/Ac/PvOnGrid/L1/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnGrid/L2/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnGrid/L3/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnGrid/Total/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnGrid/NumberOfPhases': {'gettext': '%.0F W'},
			'/Ac/PvOnGenset/L1/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnGenset/L2/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnGenset/L3/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnGenset/NumberOfPhases': {'gettext': '%d'},
			'/Ac/PvOnGenset/Total/Power': {'gettext': '%.0F W'},
			'/Dc/Pv/Power': {'gettext': '%.0F W'},
			'/Dc/Pv/Current': {'gettext': '%.1F A'},
			'/Dc/Battery/Voltage': {'gettext': '%.2F V'},
			'/Dc/Battery/Current': {'gettext': '%.1F A'},
			'/Dc/Battery/Power': {'gettext': '%.0F W'},
			'/Dc/Battery/Soc': {'gettext': '%.0F %%'},
			'/Dc/Battery/State': {'gettext': '%s'},
			'/Dc/Battery/TimeToGo': {'gettext': '%.0F s'},
			'/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'},
			'/Dc/Charger/Power': {'gettext': '%.0F %%'},
			'/Dc/Vebus/Current': {'gettext': '%.1F A'},
			'/Dc/Vebus/Power': {'gettext': '%.0F W'},
			'/Dc/System/Power': {'gettext': '%.0F W'},
			'/Hub': {'gettext': '%s'},
			'/Ac/ActiveIn/Source': {'gettext': '%s'},
			'/VebusService': {'gettext': '%s'}
		}

		for path in self._summeditems.keys():
			self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext)

		self._batteryservice = None
		self._determinebatteryservice()

		if self._batteryservice is None:
			logger.info("Battery service initialized to None (setting == %s)" %
				self._settings['batteryservice'])

		self._changed = True
		for service, instance in self._dbusmonitor.get_service_list().items():
			self._device_added(service, instance, do_service_change=False)

		self._handleservicechange()
		self._updatevalues()

		self._writeVebusSocCounter = 9
		gobject.timeout_add(1000, exit_on_error, self._handletimertick)
def main(argv):
    global dbusObjects

    print __file__ + " starting up"

    # Have a mainloop, so we can send/receive asynchronous calls to and from dbus
    DBusGMainLoop(set_as_default=True)

    # Put ourselves on to the dbus
    dbusservice = VeDbusService('com.victronenergy.example')

    # Most simple and short way to add an object with an initial value of 5.
    dbusservice.add_path('/Position', value=5)

    # Most advanced wayt to add a path
    dbusservice.add_path('/RPM',
                         value=100,
                         description='RPM setpoint',
                         writeable=True,
                         onchangecallback=validate_new_value,
                         gettextcallback=get_text_for_rpm)

    # You can access the paths as if the dbusservice is a dictionary
    print('/Position value is %s' % dbusservice['/Position'])

    # Same for changing it
    dbusservice['/Position'] = 10

    print('/Position value is now %s' % dbusservice['/Position'])

    # To invalidate a value (see com.victronenergy.BusItem specs for definition of invalid), set to None
    dbusservice['/Position'] = None

    dbusservice.add_path('/String', 'this is a string')
    dbusservice.add_path('/Int', 0)
    dbusservice.add_path('/NegativeInt', -10)
    dbusservice.add_path('/Float', 1.5)

    print(
        'try changing our RPM by executing the following command from a terminal\n'
    )
    print(
        'dbus-send --print-reply --dest=com.victronenergy.example /RPM com.victronenergy.BusItem.SetValue int32:1200'
    )
    print(
        'Reply will be <> 0 for values > 1000: not accepted. And reply will be 0 for values < 1000: accepted.'
    )
    mainloop = gobject.MainLoop()
    mainloop.run()
Example #28
0
def main():
    global dbusservice
    global kwhdeltas

    # Argument parsing
    parser = argparse.ArgumentParser(
        description= 'kwhcounters aggregrates information from vebus system, solar chargers, etc. and' +
            'calculates system kWh\n'
    )

    parser.add_argument("-d", "--debug", help="set logging level to debug",
                    action="store_true")

    args = parser.parse_args()

    # Init logging
    logging.basicConfig(level=(logging.DEBUG if args.debug else logging.INFO))
    logging.info("%s v%s is starting up" % (__file__, softwareversion))
    logLevel = {0: 'NOTSET', 10: 'DEBUG', 20: 'INFO', 30: 'WARNING', 40: 'ERROR'}
    logging.info('Loglevel set to ' + logLevel[logging.getLogger().getEffectiveLevel()])

    # Have a mainloop, so we can send/receive asynchronous calls to and from dbus
    DBusGMainLoop(set_as_default=True)

    # Publish ourselves on the dbus
    dbusservice = VeDbusService("com.victronenergy.kwhcounters.s0")
    dbusservice.add_path('/GridToBattery', value=None)
    dbusservice.add_path('/GridToConsumers', value=None)
    dbusservice.add_path('/GensetToConsumers', value=None)
    dbusservice.add_path('/GensetToBattery', value=None)
    dbusservice.add_path('/PvToBattery', value=None)
    dbusservice.add_path('/PvToGrid', value=None)
    dbusservice.add_path('/PvToConsumers', value=None)
    dbusservice.add_path('/BatteryToConsumers', value=None)
    dbusservice.add_path('/BatteryToGrid', value=None)

    kwhdeltas = KwhDeltas()

    gobject.timeout_add(1000, kwhdeltas.getdeltas)

    # Start and run the mainloop
    logging.info("Starting mainloop, responding on only events from now on. Press ctrl-Z to see the deltas")
    mainloop = gobject.MainLoop()
    mainloop.run()
parser.add_argument("-d", "--debug", help="set logging level to debug",
				action="store_true")

args = parser.parse_args()

# Init logging
logging.basicConfig(level=(logging.DEBUG if args.debug else logging.INFO))
logging.info(__file__ + " is starting up")
logLevel = {0: 'NOTSET', 10: 'DEBUG', 20: 'INFO', 30: 'WARNING', 40: 'ERROR'}
logging.info('Loglevel set to ' + logLevel[logging.getLogger().getEffectiveLevel()])

# Have a mainloop, so we can send/receive asynchronous calls to and from dbus
DBusGMainLoop(set_as_default=True)

dbusservice = VeDbusService(args.name)

logging.info("using device instance %s" % args.deviceinstance)

# Create the management objects, as specified in the ccgx dbus-api document
dbusservice.add_path('/Management/ProcessName', __file__)
dbusservice.add_path('/Management/ProcessVersion', 'Unkown version, and running on Python ' + platform.python_version())
dbusservice.add_path('/Management/Connection', 'Data taken from mk2dbus')

# Create the mandatory objects
dbusservice.add_path('/DeviceInstance', args.deviceinstance)
dbusservice.add_path('/ProductId', 0)
dbusservice.add_path('/ProductName', 'PV Inverter on Output')
dbusservice.add_path('/FirmwareVersion', 0)
dbusservice.add_path('/HardwareVersion', 0)
dbusservice.add_path('/Connected', 1)
Example #30
0
def main():
    global mainloop
    global start

    start = datetime.now()

    parser = ArgumentParser(description=NAME, add_help=True)
    parser.add_argument('-d',
                        '--debug',
                        help='enable debug logging',
                        action='store_true')
    parser.add_argument('-s', '--serial', help='tty')

    args = parser.parse_args()

    logging.basicConfig(format='%(levelname)-8s %(message)s',
                        level=(logging.DEBUG if args.debug else logging.INFO))

    logLevel = {
        0: 'NOTSET',
        10: 'DEBUG',
        20: 'INFO',
        30: 'WARNING',
        40: 'ERROR',
    }
    log.info('Loglevel set to ' + logLevel[log.getEffectiveLevel()])

    if not args.serial:
        log.error('No serial port specified, see -h')
        exit(1)

    rate = 19200

    gpio_base = find_gpio_base(os.path.basename(args.serial))
    if gpio_base == None:
        log.error('GPIO not found')
        exit(1)

    log.info('Starting %s %s on %s at %d bps, GPIO base %d' %
             (NAME, VERSION, args.serial, rate, gpio_base))

    gobject.threads_init()
    dbus.mainloop.glib.threads_init()
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)

    mainloop = gobject.MainLoop()

    svc = VeDbusService('com.victronenergy.hiber')

    svc.add_path('/Model', None)
    svc.add_path('/ModemNumber', None)
    svc.add_path('/Firmware', None)
    svc.add_path('/NextAlarm', None)
    svc.add_path('/NextPass', None)

    hiber = Hiber(svc, args.serial, rate, gpio_base)
    hiber.start()

    gobject.timeout_add(WAKEUP_INTERVAL * 1000, hiber.update_modem)
    gobject.timeout_add(5000, hiber.update_watchdog)
    mainloop.run()

    quit(1)
Example #31
0
class DbusGenerator:

    def __init__(self):
        self.RELAY_GPIO_FILE = '/sys/class/gpio/gpio182/value'
        self.SERVICE_NOBATTERY = 'nobattery'
        self.SERVICE_NOVEBUS = 'novebus'
        self.HISTORY_DAYS = 30
        self._last_counters_check = 0
        self._dbusservice = None
        self._batteryservice = None
        self._vebusservice = None
        self._starttime = 0
        self._manualstarttimer = 0
        self._last_runtime_update = 0
        self.timer_runnning = 0

        self._condition_stack = {
            'batteryvoltage': {
                'name': 'batteryvoltage',
                'reached': False,
                'timed': True,
                'start_timer': 0,
                'stop_timer': 0,
                'valid': True,
                'enabled': False
            },
            'batterycurrent': {
                'name': 'batterycurrent',
                'reached': False,
                'timed': True,
                'start_timer': 0,
                'stop_timer': 0,
                'valid': True,
                'enabled': False
            },
            'acload': {
                'name': 'acload',
                'reached': False,
                'timed': True,
                'start_timer': 0,
                'stop_timer': 0,
                'valid': True,
                'enabled': False
            },
            'soc': {
                'name': 'soc',
                'reached': False,
                'timed': False,
                'valid': True,
                'enabled': False
            }
        }

        # 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}

        self._dbusmonitor = DbusMonitor({
            'com.victronenergy.vebus': {
                '/Connected': dummy,
                '/ProductName': dummy,
                '/Mgmt/Connection': dummy,
                '/State': dummy,
                '/Ac/Out/P': dummy,
                '/Dc/I': dummy,
                '/Dc/V': dummy,
                '/Soc': dummy
            },
            'com.victronenergy.battery': {
                '/Connected': dummy,
                '/ProductName': dummy,
                '/Mgmt/Connection': dummy,
                '/Dc/0/V': dummy,
                '/Dc/0/I': dummy,
                '/Dc/0/P': dummy,
                '/Soc': dummy
            },
            '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}
        }, 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=dbus.SystemBus() if (platform.machine() == 'armv7l') else dbus.SessionBus(),
            supportedSettings={
                'autostart': ['/Settings/Generator/AutoStart', 0, 0, 1],
                'accumulateddaily': ['/Settings/Generator/AccumulatedDaily', '', 0, 0],
                'accumulatedtotal': ['/Settings/Generator/AccumulatedTotal', 0, 0, 0],
                'batteryservice': ['/Settings/Generator/BatteryService', self.SERVICE_NOBATTERY, 0, 0],
                'vebusservice': ['/Settings/Generator/VebusService', self.SERVICE_NOVEBUS, 0, 0],
                # Silent mode
                'silentmodeenabled': ['/Settings/Generator/SilentMode/Enabled', 0, 0, 1],
                'silentmodestarttimer': ['/Settings/Generator/SilentMode/StartTime', 0, 0, 86400],
                'silentmodeendtime': ['/Settings/Generator/SilentMode/EndTime', 0, 0, 86400],
                # SOC
                'socenabled': ['/Settings/Generator/Soc/Enabled', 0, 0, 1],
                'socstart': ['/Settings/Generator/Soc/StartValue', 90, 0, 100],
                'socstop': ['/Settings/Generator/Soc/StopValue', 90, 0, 100],
                'em_socstart': ['/Settings/Generator/Soc/EmergencyStartValue', 90, 0, 100],
                'em_socstop': ['/Settings/Generator/Soc/EmergencyStopValue', 90, 0, 100],
                # Voltage
                'batteryvoltageenabled': ['/Settings/Generator/BatteryVoltage/Enabled', 0, 0, 1],
                'batteryvoltagestart': ['/Settings/Generator/BatteryVoltage/StartValue', 11.5, 0, 150],
                'batteryvoltagestop': ['/Settings/Generator/BatteryVoltage/StopValue', 12.4, 0, 150],
                'batteryvoltagestarttimer': ['/Settings/Generator/BatteryVoltage/StartTimer', 20, 0, 10000],
                'batteryvoltagestoptimer': ['/Settings/Generator/BatteryVoltage/StopTimer', 20, 0, 10000],
                'em_batteryvoltagestart': ['/Settings/Generator/BatteryVoltage/EmergencyStartValue', 11.9, 0, 100],
                'em_batteryvoltagestop': ['/Settings/Generator/BatteryVoltage/EmergencyStopValue', 12.4, 0, 100],
                # Current
                'batterycurrentenabled': ['/Settings/Generator/BatteryCurrent/Enabled', 0, 0, 1],
                'batterycurrentstart': ['/Settings/Generator/BatteryCurrent/StartValue', 10.5, 0.5, 1000],
                'batterycurrentstop': ['/Settings/Generator/BatteryCurrent/StopValue', 5.5, 0, 1000],
                'batterycurrentstarttimer': ['/Settings/Generator/BatteryCurrent/StartTimer', 20, 0, 10000],
                'batterycurrentstoptimer': ['/Settings/Generator/BatteryCurrent/StopTimer', 20, 0, 10000],
                'em_batterycurrentstart': ['/Settings/Generator/BatteryCurrent/EmergencyStartValue', 20.5, 0, 1000],
                'em_batterycurrentstop': ['/Settings/Generator/BatteryCurrent/EmergencyStopValue', 15.5, 0, 1000],
                # AC load
                'acloadenabled': ['/Settings/Generator/AcLoad/Enabled', 0, 0, 1],
                'acloadstart': ['/Settings/Generator/AcLoad/StartValue', 1600, 5, 100000],
                'acloadstop': ['/Settings/Generator/AcLoad/StopValue', 800, 0, 100000],
                'acloadstarttimer': ['/Settings/Generator/AcLoad/StartTimer', 20, 0, 10000],
                'acloadstoptimer': ['/Settings/Generator/AcLoad/StopTimer', 20, 0, 10000],
                'em_acloadstart': ['/Settings/Generator/AcLoad/EmergencyStartValue', 1900, 0, 100000],
                'em_acloadstop': ['/Settings/Generator/AcLoad/EmergencyStopValue', 1200, 0, 100000],
                # Maintenance
                'maintenanceenabled': ['/Settings/Generator/Maintenance/Enabled', 0, 0, 1],
                'maintenancestartdate': ['/Settings/Generator/Maintenance/StartDate', time.time(), 0, 10000000000.1],
                'maintenancestarttimer': ['/Settings/Generator/Maintenance/StartTime', 54000, 0, 86400],
                'maintenanceinterval': ['/Settings/Generator/Maintenance/Interval', 28, 1, 365],
                'maintenanceruntime': ['/Settings/Generator/Maintenance/Duration', 7200, 1, 86400],
                'maintenanceskipruntime': ['/Settings/Generator/Maintenance/SkipRuntime', 0, 0, 100000]
            },
            eventCallback=self._handle_changed_setting)

        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') == 1:
            if self._dbusservice is None:
                logger.info('Action! Going on dbus and taking control of the relay.')
                # 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)
                # Maintenance runtime
                self._dbusservice.add_path('/MaintenanceIntervalRuntime',
                                           value=self._interval_runtime(self._settings['maintenanceinterval']),
                                           gettextcallback=self._gettext)
                # Next maintenance date, values is 0 for maintenande disabled
                self._dbusservice.add_path('/NextMaintenance', value=None, gettextcallback=self._gettext)
                # Next maintenance is needed 1, not needed 0
                self._dbusservice.add_path('/SkipMaintenance', 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('/SilentMode', value=0)
                # Battery services
                self._dbusservice.add_path('/AvailableBatteryServices', value=None)
                # Vebus services
                self._dbusservice.add_path('/AvailableVebusServices', value=None)
                # As the user can select the vebus service and is not yet possible to get the servie name from the gui
                # we need to provide it
                self._dbusservice.add_path('/VebusServiceName', value=None)

                self._determineservices()

                self._batteryservice = None
                self._vebusservice = None
                self._populate_services_list()
                self._determineservices()

                if self._batteryservice is not None:
                    logger.info('Battery service we need (%s) found! Using it for generator start/stop'
                                % self._get_service_path(self._settings['batteryservice']))

                elif self._vebusservice is not None:
                    logger.info('VE.Bus service we need (%s) found! Using it for generator start/stop'
                                % self._get_service_path(self._settings['vebusservice']))
            else:
                self._populate_services_list()
                self._determineservices()
        else:
            if self._dbusservice is not None:
                self._stop_generator()
                self._batteryservice = None
                self._vebusservice = None
                self._dbusservice.__del__()
                self._dbusservice = None
                # Reset conditions
                for condition in self._condition_stack:
                    self._reset_condition(self._condition_stack[condition])
                logger.info('Relay function is no longer set to generator start/stop: made sure generator is off ' +
                            'and now going off dbus')

    def _device_added(self, dbusservicename, instance):
        self._evaluate_if_we_are_needed()

    def _device_removed(self, dbusservicename, instance):
        self._evaluate_if_we_are_needed()

    def _dbus_value_changed(self, dbusServiceName, dbusPath, options, changes, deviceInstance):
        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 == 'Polarity':
            self._update_relay()
        if self._dbusservice is not None and setting == 'maintenanceinterval':
            self._dbusservice['/MaintenanceIntervalRuntime'] = self._interval_runtime(
                                                               self._settings['maintenanceinterval'])

    def _gettext(self, path, value):
        if path == '/NextMaintenance':
            # Locale format date
            d = datetime.datetime.fromtimestamp(value)
            return d.strftime('%c')
        elif path in ['/Runtime', '/MaintenanceIntervalRuntime', '/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!
        # To keep accuracy, conditions will forced to be evaluated each second when the generator or a timer is running
        try:
            if self._dbusservice is not None and (self._changed or self._dbusservice['/State'] == 1
                                                  or self._dbusservice['/ManualStart'] == 1 or self.timer_runnning):
                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']
        start = False
        runningbycondition = None
        today = calendar.timegm(datetime.date.today().timetuple())
        self.timer_runnning = False
        values = self._get_updated_values()

        self._check_silent_mode()

        # 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 onle 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

        # 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

        if self._evaluate_maintenance_condition() and not start:
            runningbycondition = 'maintenance'
            start = True

        if start:
            self._start_generator(runningbycondition)
        else:
            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)
                self._reset_condition(condition)
            return False

        elif not condition['enabled']:
            condition['enabled'] = True
            logger.info('Enabling (%s) condition' % name)

        if value is None and condition['valid']:
            logger.info('Error getting (%s) value, skipping evaluation till get a valid value' % name)
            self._reset_condition(condition)
            condition['valid'] = False
            return False

        elif value is not None and not condition['valid']:
            logger.info('Success getting (%s) value, resuming evaluation' % name)
            condition['valid'] = True

        return condition['valid']

    def _evaluate_condition(self, condition, value):
        name = condition['name']
        setting = ('em_' if self._dbusservice['/SilentMode'] == 1 else '') + name
        startvalue = self._settings[setting + 'start']
        stopvalue = self._settings[setting + 'stop']

        # Check if the have to be evaluated
        if not self._check_condition(condition, value):
            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:
            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_maintenance_condition(self):
        if self._settings['maintenanceenabled'] == 0:
            self._dbusservice['/SkipMaintenance'] = None
            self._dbusservice['/NextMaintenance'] = None
            return False

        today = datetime.date.today()
        try:
            startdate = datetime.date.fromtimestamp(self._settings['maintenancestartdate'])
            starttime = time.mktime(today.timetuple()) + self._settings['maintenancestarttimer']
        except ValueError:
            logger.debug('Invalid dates, skipping maintenance')
            return False

        # If start date is in the future set as NextMaintenance and stop evaluating
        if startdate > today:
            self._dbusservice['/NextMaintenance'] = time.mktime(startdate.timetuple())
            return False

        start = False
        # If the accumulated runtime during the maintenance interval is greater than '/MaintenanceIntervalRuntime'
        # the maintenance must be skipped
        needed = (self._settings['maintenanceskipruntime'] > self._dbusservice['/MaintenanceIntervalRuntime']
                  or self._settings['maintenanceskipruntime'] == 0)
        self._dbusservice['/SkipMaintenance'] = int(not needed)

        interval = self._settings['maintenanceinterval']
        stoptime = starttime + self._settings['maintenanceruntime']
        elapseddays = (today - startdate).days
        mod = elapseddays % interval
        start = (not bool(mod) and (time.time() >= starttime) and (time.time() <= stoptime))

        if not bool(mod) and (time.time() <= stoptime):
            self._dbusservice['/NextMaintenance'] = starttime
        else:
            self._dbusservice['/NextMaintenance'] = (time.mktime((today +
                                                     datetime.timedelta(days=interval - mod)).timetuple()) +
                                                     self._settings['maintenancestarttimer'])
        return start and needed

    def _check_silent_mode(self):
        active = False
        if self._settings['silentmodeenabled'] == 1:
            # Seconds after today 00:00
            timeinseconds = time.time() - time.mktime(datetime.date.today().timetuple())
            silentmodestart = self._settings['silentmodestarttimer']
            silentmodeend = self._settings['silentmodeendtime']

            # Check if the current time is between the start time and end time
            if silentmodestart < silentmodeend:
                active = silentmodestart <= timeinseconds and timeinseconds < silentmodeend
            else:  # End time is lower than start time, example Start: 21:00, end: 08:00
                active = not (silentmodeend < timeinseconds and timeinseconds < silentmodestart)

        if self._dbusservice['/SilentMode'] == 0 and active:
            logger.info('Entering silent mode, only emergency values will be evaluated')

        elif self._dbusservice['/SilentMode'] == 1 and not active:
            logger.info('Leaving silent mode')

        self._dbusservice['/SilentMode'] = 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['/MaintenanceIntervalRuntime'] = self._interval_runtime(self._settings['maintenanceinterval'])

    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': None,
            'batterycurrent': None,
            'soc': None,
            'acload': None
        }
        # Update values from battery monitor
        if self._batteryservice is not None:
            batteryservicetype = self._batteryservice.split('.')[2]
            values['soc'] = self._dbusmonitor.get_value(self._batteryservice, '/Soc')
            if batteryservicetype == 'battery':
                values['batteryvoltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/V')
                values['batterycurrent'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/I') * -1
            elif batteryservicetype == 'vebus':
                values['batteryvoltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/V')
                values['batterycurrent'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/I') * -1

        if self._vebusservice is not None:
            values['acload'] = self._dbusmonitor.get_value(self._vebusservice, '/Ac/Out/P')

        return values

    def _populate_services_list(self):
        vebusservices = self._dbusmonitor.get_service_list('com.victronenergy.vebus')
        batteryservices = self._dbusmonitor.get_service_list('com.victronenergy.battery')
        self._remove_unconnected_services(vebusservices)
        # User can set a vebus as battery monitor, add the option
        batteryservices.update(vebusservices)

        vebus = {self.SERVICE_NOVEBUS: 'None'}
        battery = {self.SERVICE_NOBATTERY: 'None'}

        for servicename, instance in vebusservices.items():
            key = '%s/%s' % ('.'.join(servicename.split('.')[0:3]), instance)
            vebus[key] = self._get_readable_service_name(servicename)

        for servicename, instance in batteryservices.items():
            key = '%s/%s' % ('.'.join(servicename.split('.')[0:3]), instance)
            battery[key] = self._get_readable_service_name(servicename)

        self._dbusservice['/AvailableBatteryServices'] = json.dumps(battery)
        self._dbusservice['/AvailableVebusServices'] = json.dumps(vebus)

    def _determineservices(self):
        vebusservice = self._settings['vebusservice']
        batteryservice = self._settings['batteryservice']

        if batteryservice != self.SERVICE_NOBATTERY and batteryservice != '':
            self._batteryservice = self._get_service_path(batteryservice)
        else:
            self._batteryservice = None

        if vebusservice != self.SERVICE_NOVEBUS and vebusservice != '':
            self._vebusservice = self._get_service_path(vebusservice)
            self._dbusservice['/VebusServiceName'] = self._vebusservice
        else:
            self._vebusservice = None

        self._changed = True

    def _get_readable_service_name(self, servicename):
        return (self._dbusmonitor.get_value(servicename, '/ProductName') + ' on ' +
                self._dbusmonitor.get_value(servicename, '/Mgmt/Connection'))

    def _remove_unconnected_services(self, services):
        # Workaround: because com.victronenergy.vebus is available even when there is no vebus product
        # connected. Remove any that is not connected. For this, we use /State since mandatory path
        # /Connected is not implemented in mk2dbus.
        for servicename in services.keys():
            if ((servicename.split('.')[2] == 'vebus' and self._dbusmonitor.get_value(servicename, '/State') is None)
                    or self._dbusmonitor.get_value(servicename, '/Connected') != 1
                    or self._dbusmonitor.get_value(servicename, '/ProductName') is None
                    or self._dbusmonitor.get_value(servicename, '/Mgmt/Connection') is None):
                del services[servicename]

    def _get_service_path(self, service):
        s = service.split('/')
        assert len(s) == 2, 'The setting (%s) is invalid!' % service
        serviceclass = s[0]
        instance = int(s[1])
        services = self._dbusmonitor.get_service_list(classfilter=serviceclass)
        if instance not in services.values():
            # Once chosen battery monitor does not exist. Don't auto change the setting (it might come
            # back). And also don't autoselect another.
            servicepath = None
        else:
            # According to https://www.python.org/dev/peps/pep-3106/, dict.keys() and dict.values()
            # always have the same order.
            servicepath = services.keys()[services.values().index(instance)]
        return servicepath

    def _start_generator(self, condition):
        # This function will start the generator in the case generator not
        # already running. When differs, the RunningByCondition is updated
        if self._dbusservice['/State'] == 0:
            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 self._dbusservice['/State'] == 1:
            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):
        # 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:
            f = open(self.RELAY_GPIO_FILE, 'w')
            f.write(str(w))
            f.close()
        except IOError:
            logger.info('Error writting to the relay GPIO file!: %s' % self.RELAY_GPIO_FILE)
def main(argv):
    global dbusObjects

    print __file__ + " starting up"

    # Have a mainloop, so we can send/receive asynchronous calls to and from dbus
    DBusGMainLoop(set_as_default=True)

    # Put ourselves on to the dbus
    dbusservice = VeDbusService("com.victronenergy.example")

    # Most simple and short way to add an object with an initial value of 5.
    dbusservice.add_path("/Position", value=5)

    # Most advanced wayt to add a path
    dbusservice.add_path(
        "/RPM",
        value=100,
        description="RPM setpoint",
        writeable=True,
        onchangecallback=validate_new_value,
        gettextcallback=get_text_for_rpm,
    )

    # You can access the paths as if the dbusservice is a dictionary
    print ("/Position value is %s" % dbusservice["/Position"])

    # Same for changing it
    dbusservice["/Position"] = 10

    print ("/Position value is now %s" % dbusservice["/Position"])

    # To invalidate a value (see com.victronenergy.BusItem specs for definition of invalid), set to None
    dbusservice["/Position"] = None

    dbusservice.add_path("/String", "this is a string")
    dbusservice.add_path("/Int", 0)
    dbusservice.add_path("/NegativeInt", -10)
    dbusservice.add_path("/Float", 1.5)

    print ("try changing our RPM by executing the following command from a terminal\n")
    print (
        "dbus-send --print-reply --dest=com.victronenergy.example /RPM com.victronenergy.BusItem.SetValue int32:1200"
    )
    print ("Reply will be <> 0 for values > 1000: not accepted. And reply will be 0 for values < 1000: accepted.")
    mainloop = gobject.MainLoop()
    mainloop.run()
Example #33
0
    def __init__(self,
                 servicename,
                 deviceinstance,
                 voltage,
                 capacity,
                 productname='Valence U-BMS',
                 connection='can0'):
        self.minUpdateDone = 0
        self.dailyResetDone = 0

        self._bat = UbmsBattery(capacity=capacity,
                                voltage=voltage,
                                connection=connection)

        self._dbusservice = VeDbusService(servicename + '.socketcan_' +
                                          connection + '_di' +
                                          str(deviceinstance))

        logging.debug("%s /DeviceInstance = %d" %
                      (servicename + '.socketcan_' + connection + '_di' +
                       str(deviceinstance), deviceinstance))

        # Create the management objects, as specified in the ccgx dbus-api document
        self._dbusservice.add_path('/Mgmt/ProcessName', __file__)
        self._dbusservice.add_path(
            '/Mgmt/ProcessVersion',
            VERSION + ' running on Python ' + platform.python_version())
        self._dbusservice.add_path('/Mgmt/Connection', connection)

        # Create the mandatory objects
        self._dbusservice.add_path('/DeviceInstance', deviceinstance)
        self._dbusservice.add_path('/ProductId', 0)
        self._dbusservice.add_path('/ProductName', productname)
        self._dbusservice.add_path('/FirmwareVersion', 'unknown')
        self._dbusservice.add_path('/HardwareVersion', 'unknown')
        self._dbusservice.add_path('/Connected', 0)
        # Create battery specific objects
        self._dbusservice.add_path('/Status', 0)
        self._dbusservice.add_path('/Mode',
                                   1,
                                   writeable=True,
                                   onchangecallback=self._transmit_mode)
        self._dbusservice.add_path('/Soh', 100)
        self._dbusservice.add_path('/Capacity', int(capacity))
        self._dbusservice.add_path('/InstalledCapacity', int(capacity))
        self._dbusservice.add_path('/Dc/0/Temperature', 25)
        self._dbusservice.add_path('/Info/MaxChargeCurrent', 70)
        self._dbusservice.add_path('/Info/MaxDischargeCurrent', 150)
        self._dbusservice.add_path('/Info/MaxChargeVoltage', float(voltage))
        self._dbusservice.add_path('/Info/BatteryLowVoltage', 24.0)
        self._dbusservice.add_path('/Alarms/CellImbalance', 0)
        self._dbusservice.add_path('/Alarms/LowVoltage', 0)
        self._dbusservice.add_path('/Alarms/HighVoltage', 0)
        self._dbusservice.add_path('/Alarms/HighDischargeCurrent', 0)
        self._dbusservice.add_path('/Alarms/HighChargeCurrent', 0)
        self._dbusservice.add_path('/Alarms/LowSoc', 0)
        self._dbusservice.add_path('/Alarms/LowTemperature', 0)
        self._dbusservice.add_path('/Alarms/HighTemperature', 0)
        self._dbusservice.add_path('/Balancing', 0)
        self._dbusservice.add_path('/System/HasTemperature', 1)
        self._dbusservice.add_path('/System/NrOfBatteries', 10)
        self._dbusservice.add_path('/System/NrOfModulesOnline', 10)
        self._dbusservice.add_path('/System/NrOfModulesOffline', 0)
        self._dbusservice.add_path('/System/NrOfModulesBlockingDischarge', 0)
        self._dbusservice.add_path('/System/NrOfModulesBlockingCharge', 0)
        self._dbusservice.add_path('/System/NrOfBatteriesBalancing', 0)
        self._dbusservice.add_path('/System/BatteriesParallel', 5)
        self._dbusservice.add_path('/System/BatteriesSeries', 2)
        self._dbusservice.add_path('/System/NrOfCellsPerBattery', 4)
        self._dbusservice.add_path('/System/MinVoltageCellId', 'M_C_')
        self._dbusservice.add_path('/System/MaxVoltageCellId', 'M_C_')
        self._dbusservice.add_path('/System/MinCellTemperature', 10.0)
        self._dbusservice.add_path('/System/MaxCellTemperature', 10.0)
        self._dbusservice.add_path('/System/MaxPcbTemperature', 10.0)

        self._settings = SettingsDevice(
            bus=dbus.SystemBus() if
            (platform.machine() == 'armv7l') else dbus.SessionBus(),
            supportedSettings={
                'AvgDischarge':
                ['/Settings/Ubms/AvgerageDischarge', 0.0, 0, 0],
                'TotalAhDrawn': ['/Settings/Ubms/TotalAhDrawn', 0.0, 0, 0],
                'TimeLastFull': ['/Settings/Ubms/TimeLastFull', 0.0, 0, 0],
                'MinCellVoltage':
                ['/Settings/Ubms/MinCellVoltage', 4.0, 2.0, 4.0],
                'MaxCellVoltage':
                ['/Settings/Ubms/MaxCellVoltage', 2.0, 2.0, 4.0],
                'interval': ['/Settings/Ubms/Interval', 50, 50, 200]
            },
            eventCallback=handle_changed_setting)

        self._summeditems = {
            '/System/MaxCellVoltage': {
                'gettext': '%.2F V'
            },
            '/System/MinCellVoltage': {
                'gettext': '%.2F V'
            },
            '/Dc/0/Voltage': {
                'gettext': '%.2F V'
            },
            '/Dc/0/Current': {
                'gettext': '%.1F A'
            },
            '/Dc/0/Power': {
                'gettext': '%.0F W'
            },
            '/Soc': {
                'gettext': '%.0F %%'
            },
            '/History/TotalAhDrawn': {
                'gettext': '%.0F Ah'
            },
            '/History/DischargedEnergy': {
                'gettext': '%.2F kWh'
            },
            '/History/ChargedEnergy': {
                'gettext': '%.2F kWh'
            },
            '/History/AverageDischarge': {
                'gettext': '%.2F kWh'
            },
            '/TimeToGo': {
                'gettext': '%.0F s'
            },
            '/ConsumedAmphours': {
                'gettext': '%.1F Ah'
            }
        }
        for path in self._summeditems.keys():
            self._dbusservice.add_path(path,
                                       value=None,
                                       gettextcallback=self._gettext)

        self._dbusservice['/History/AverageDischarge'] = self._settings[
            'AvgDischarge']
        self._dbusservice['/History/TotalAhDrawn'] = self._settings[
            'TotalAhDrawn']
        self._dbusservice.add_path('/History/TimeSinceLastFullCharge', 0)
        self._dbusservice.add_path('/History/MinCellVoltage',
                                   self._settings['MinCellVoltage'])
        self._dbusservice.add_path('/History/MaxCellVoltage',
                                   self._settings['MaxCellVoltage'])
        self._dbusservice['/ConsumedAmphours'] = 0

        logging.info(
            "History cell voltage min: %.3f, max: %.3f, totalAhDrawn: %d",
            self._settings['MinCellVoltage'], self._settings['MaxCellVoltage'],
            self._settings['TotalAhDrawn'])

        self._dbusservice['/History/ChargedEnergy'] = 0
        self._dbusservice['/History/DischargedEnergy'] = 0

        gobject.timeout_add(self._settings['interval'], exit_on_error,
                            self._update)
Example #34
0
class DbusGenerator:

	def __init__(self):
		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 = 300
		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._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,
				'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 self._dbusservice is None:
				logger.info('Action! Going on dbus and taking control of the relay.')

				relay_polarity_import = VeDbusItemImport(
														 bus=self._bus, serviceName='com.victronenergy.settings',
														 path='/Settings/Relay/Polarity',
														 eventCallback=None, createsignal=True)

				# As is not possible to keep the relay state during the CCGX power cycles,
				# set the relay polarity to normally open.
				if relay_polarity_import.get_value() == 1:
					relay_polarity_import.set_value(0)
					logger.info('Setting relay polarity to normally open.')

				# put ourselves on the dbus
				self._dbusservice = VeDbusService('com.victronenergy.generator.startstop0')
				self._dbusservice.add_mandatory_paths(
					processname=__file__,
					processversion=softwareversion,
					connection='generator',
					deviceinstance=0,
					productid=None,
					productname=None,
					firmwareversion=None,
					hardwareversion=None,
					connected=1)
				# State: None = invalid, 0 = stopped, 1 = running
				self._dbusservice.add_path('/State', value=0)
				# Condition that made the generator start
				self._dbusservice.add_path('/RunningByCondition', value='')
				# Runtime
				self._dbusservice.add_path('/Runtime', value=0, gettextcallback=self._gettext)
				# Today runtime
				self._dbusservice.add_path('/TodayRuntime', value=0, gettextcallback=self._gettext)
				# Test run runtime
				self._dbusservice.add_path('/TestRunIntervalRuntime',
										   value=self._interval_runtime(self._settings['testruninterval']),
										   gettextcallback=self._gettext)
				# Next tes trun date, values is 0 for test run disabled
				self._dbusservice.add_path('/NextTestRun', value=None, gettextcallback=self._gettext)
				# Next tes trun is needed 1, not needed 0
				self._dbusservice.add_path('/SkipTestRun', value=None)
				# Manual start
				self._dbusservice.add_path('/ManualStart', value=0, writeable=True)
				# Manual start timer
				self._dbusservice.add_path('/ManualStartTimer', value=0, writeable=True)
				# Silent mode active
				self._dbusservice.add_path('/QuietHours', value=0)
				self._determineservices()

		else:
			if self._dbusservice is not None:
				self._stop_generator()
				self._dbusservice.__del__()
				self._dbusservice = None
				# Reset conditions
				for condition in self._condition_stack:
					self._reset_condition(self._condition_stack[condition])
				logger.info('Relay function is no longer set to generator start/stop: made sure generator is off ' +
							'and now going off dbus')

	def _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()
		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:
			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

	def _start_generator(self, condition):
		# This function will start the generator in the case generator not
		# already running. When differs, the RunningByCondition is updated
		if self._dbusservice['/State'] == 0:
			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 self._dbusservice['/State'] == 1:
			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):
		# 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:
			f = open(self.RELAY_GPIO_FILE, 'w')
			f.write(str(w))
			f.close()
		except IOError:
			logger.info('Error writting to the relay GPIO file!: %s' % self.RELAY_GPIO_FILE)
class DbusGpsService:
    def __init__(self,
                 servicename,
                 deviceinstance,
                 paths,
                 productname='SIM868',
                 connection='GPS'):
        self._dbusservice = VeDbusService(servicename)
        self._paths = paths

        logging.debug("%s /DeviceInstance = %d" %
                      (servicename, deviceinstance))

        # Create the management objects, as specified in the ccgx dbus-api document
        self._dbusservice.add_path('/Mgmt/ProcessName', __file__)
        self._dbusservice.add_path(
            '/Mgmt/ProcessVersion', 'Unkown version, and running on Python ' +
            platform.python_version())
        self._dbusservice.add_path('/Mgmt/Connection', connection)

        # Create the mandatory objects
        self._dbusservice.add_path('/DeviceInstance', deviceinstance)
        self._dbusservice.add_path('/ProductId', 0)
        self._dbusservice.add_path('/ProductName', productname)
        self._dbusservice.add_path('/FirmwareVersion', 0)
        self._dbusservice.add_path('/HardwareVersion', 0)
        self._dbusservice.add_path('/Connected', 1)

        for path, settings in self._paths.iteritems():
            self._dbusservice.add_path(
                path,
                settings['initial'],
                writeable=True,
                onchangecallback=self._handlechangedvalue)
        serial.Serial()
        self.ser = serial.Serial("/dev/ttyUSB0", 115200)
        self.ser.timeout = 0
        self.ser.flushInput()

        init_gps(self.ser)
        gobject.timeout_add(1000, self.update_position)

    def update_position(self):
        num_waiting = self.ser.inWaiting()
        while num_waiting > 0:
            try:
                line = self.ser.readline()
                msg = pynmea2.parse(line)
                if type(msg) is pynmea2.GGA:
                    logging.info("lat: {}; lon: {}".format(
                        msg.latitude, msg.longitude))
                    self._dbusservice["/Position/Latitude"] = msg.latitude
                    self._dbusservice["/Position/Longtitude"] = msg.longitude
            except serial.SerialException as e:
                logging.error('Device error: {}'.format(e))
                break
            except pynmea2.ParseError as e:
                logging.warning('Parse error: {}'.format(e))
                continue
            num_waiting = self.ser.inWaiting()
        return True

    def _update(self):
        for path, settings in self._paths.iteritems():
            if 'update' in settings:
                self._dbusservice[
                    path] = self._dbusservice[path] + settings['update']
                logging.debug("%s: %s" % (path, self._dbusservice[path]))
        return True

    def _handlechangedvalue(self, path, value):
        logging.debug("someone else updated %s to %s" % (path, value))
        return True  # accept the change
class PinHandler(object, metaclass=HandlerMaker):
    product_id = 0xFFFF
    _product_name = 'Generic GPIO'
    dbus_name = "digital"

    def __init__(self, bus, base, path, gpio, settings):
        self.gpio = gpio
        self.path = path
        self.bus = bus
        self.settings = settings
        self._level = 0  # Remember last state

        self.service = VeDbusService("{}.{}.input{:02d}".format(
            base, self.dbus_name, gpio),
                                     bus=bus)

        # Add objects required by ve-api
        self.service.add_path('/Mgmt/ProcessName', __file__)
        self.service.add_path('/Mgmt/ProcessVersion', VERSION)
        self.service.add_path('/Mgmt/Connection', path)
        self.service.add_path('/DeviceInstance', gpio)
        self.service.add_path('/ProductId', self.product_id)
        self.service.add_path('/ProductName', self.product_name)
        self.service.add_path('/Connected', 1)

        # Custom name setting
        def _change_name(p, v):
            # This should fire a change event that will update product_name
            # below.
            settings['name'] = v
            return True

        self.service.add_path('/CustomName',
                              settings['name'],
                              writeable=True,
                              onchangecallback=_change_name)

        # We'll count the pulses for all types of services
        self.service.add_path('/Count', value=settings['count'])

    @property
    def product_name(self):
        return self.settings['name'] or self._product_name

    @product_name.setter
    def product_name(self, v):
        # Some pin types don't have an associated service (Disabled pins for
        # example)
        if self.service is not None:
            self.service['/ProductName'] = v or self._product_name

    def deactivate(self):
        self.save_count()
        self.service.__del__()
        del self.service
        self.service = None

    @property
    def level(self):
        return self._level

    @level.setter
    def level(self, l):
        self._level = int(bool(l))

    def toggle(self, level):
        # Only increment Count on rising edge.
        if level and level != self._level:
            self.service['/Count'] = (self.service['/Count'] + 1) % MAXCOUNT
        self._level = level

    def refresh(self):
        """ Toggle state to last remembered state. This is called if settings
            are changed so the Service can recalculate paths. """
        self.toggle(self._level)

    def save_count(self):
        if self.service is not None:
            self.settings['count'] = self.count

    @property
    def active(self):
        return self.service is not None

    @property
    def count(self):
        return self.service['/Count']

    @count.setter
    def count(self, v):
        self.service['/Count'] = v

    @classmethod
    def createHandler(cls, _type, *args, **kwargs):
        if _type in cls.handlers:
            return cls.handlers[_type](*args, **kwargs)
        return None
Example #37
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.')
                # 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)
                # Maintenance runtime
                self._dbusservice.add_path('/MaintenanceIntervalRuntime',
                                           value=self._interval_runtime(self._settings['maintenanceinterval']),
                                           gettextcallback=self._gettext)
                # Next maintenance date, values is 0 for maintenande disabled
                self._dbusservice.add_path('/NextMaintenance', value=None, gettextcallback=self._gettext)
                # Next maintenance is needed 1, not needed 0
                self._dbusservice.add_path('/SkipMaintenance', 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('/SilentMode', value=0)
                # Battery services
                self._dbusservice.add_path('/AvailableBatteryServices', value=None)
                # Vebus services
                self._dbusservice.add_path('/AvailableVebusServices', value=None)
                # As the user can select the vebus service and is not yet possible to get the servie name from the gui
                # we need to provide it
                self._dbusservice.add_path('/VebusServiceName', value=None)

                self._determineservices()

                self._batteryservice = None
                self._vebusservice = None
                self._populate_services_list()
                self._determineservices()

                if self._batteryservice is not None:
                    logger.info('Battery service we need (%s) found! Using it for generator start/stop'
                                % self._get_service_path(self._settings['batteryservice']))

                elif self._vebusservice is not None:
                    logger.info('VE.Bus service we need (%s) found! Using it for generator start/stop'
                                % self._get_service_path(self._settings['vebusservice']))
            else:
                self._populate_services_list()
                self._determineservices()
        else:
            if self._dbusservice is not None:
                self._stop_generator()
                self._batteryservice = None
                self._vebusservice = None
                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')
class SystemCalc:
	def __init__(self, dbusmonitor_gen=None, dbusservice_gen=None, settings_device_gen=None):
		self.STATE_IDLE = 0
		self.STATE_CHARGING = 1
		self.STATE_DISCHARGING = 2

		self.BATSERVICE_DEFAULT = 'default'
		self.BATSERVICE_NOBATTERY = 'nobattery'

		# Why this dummy? Because DbusMonitor expects these values to be there, even though we don't
		# need them. So just add some dummy data. This can go away when DbusMonitor is more generic.
		dummy = {'code': None, 'whenToLog': 'configChange', 'accessLevel': None}
		dbus_tree = {
			'com.victronenergy.solarcharger': {
				'/Connected': dummy,
				'/ProductName': dummy,
				'/Mgmt/Connection': dummy,
				'/Dc/0/Voltage': dummy,
				'/Dc/0/Current': dummy},
			'com.victronenergy.pvinverter': {
				'/Connected': dummy,
				'/ProductName': dummy,
				'/Mgmt/Connection': dummy,
				'/Ac/L1/Power': dummy,
				'/Ac/L2/Power': dummy,
				'/Ac/L3/Power': dummy,
				'/Position': dummy,
				'/ProductId': dummy},
			'com.victronenergy.battery': {
				'/Connected': dummy,
				'/ProductName': dummy,
				'/Mgmt/Connection': dummy,
				'/Dc/0/Voltage': dummy,
				'/Dc/0/Current': dummy,
				'/Dc/0/Power': dummy,
				'/Soc': dummy,
				'/TimeToGo': dummy,
				'/ConsumedAmphours': dummy},
			'com.victronenergy.vebus' : {
				'/Ac/ActiveIn/ActiveInput': dummy,
				'/Ac/ActiveIn/L1/P': dummy,
				'/Ac/ActiveIn/L2/P': dummy,
				'/Ac/ActiveIn/L3/P': dummy,
				'/Ac/Out/L1/P': dummy,
				'/Ac/Out/L2/P': dummy,
				'/Ac/Out/L3/P': dummy,
				'/Hub4/AcPowerSetpoint': dummy,
				'/ProductName': dummy,
				'/Mgmt/Connection': dummy,
				'/Dc/0/Voltage': dummy,
				'/Dc/0/Current': dummy,
				'/Dc/0/Power': dummy,
				'/Soc': dummy},
			'com.victronenergy.charger': {
				'/ProductName': dummy,
				'/Mgmt/Connection': dummy,
				'/Dc/0/Voltage': dummy,
				'/Dc/0/Current': dummy},
			'com.victronenergy.grid' : {
				'/ProductName': dummy,
				'/Mgmt/Connection': dummy,
				'/ProductId' : dummy,
				'/DeviceType' : dummy,
				'/Ac/L1/Power': dummy,
				'/Ac/L2/Power': dummy,
				'/Ac/L3/Power': dummy},
			'com.victronenergy.genset' : {
				'/ProductName': dummy,
				'/Mgmt/Connection': dummy,
				'/ProductId' : dummy,
				'/DeviceType' : dummy,
				'/Ac/L1/Power': dummy,
				'/Ac/L2/Power': dummy,
				'/Ac/L3/Power': dummy},
			'com.victronenergy.settings' : {
				'/Settings/SystemSetup/AcInput1' : dummy,
				'/Settings/SystemSetup/AcInput2' : dummy}
		}

		if dbusmonitor_gen is None:
			self._dbusmonitor = DbusMonitor(dbus_tree, self._dbus_value_changed, self._device_added, self._device_removed)
		else:
			self._dbusmonitor = dbusmonitor_gen(dbus_tree)

		# Connect to localsettings
		supported_settings = {
			'batteryservice': ['/Settings/SystemSetup/BatteryService', self.BATSERVICE_DEFAULT, 0, 0],
			'hasdcsystem': ['/Settings/SystemSetup/HasDcSystem', 0, 0, 1],
			'writevebussoc': ['/Settings/SystemSetup/WriteVebusSoc', 0, 0, 1]}

		if settings_device_gen is None:
			self._settings = SettingsDevice(
				bus=dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus(),
				supportedSettings=supported_settings,
				eventCallback=self._handlechangedsetting)
		else:
			self._settings = settings_device_gen(supported_settings, self._handlechangedsetting)

		# put ourselves on the dbus
		if dbusservice_gen is None:
			self._dbusservice = VeDbusService('com.victronenergy.system')
		else:
			self._dbusservice = dbusservice_gen('com.victronenergy.system')

		self._dbusservice.add_mandatory_paths(
			processname=__file__,
			processversion=softwareVersion,
			connection='data from other dbus processes',
			deviceinstance=0,
			productid=None,
			productname=None,
			firmwareversion=None,
			hardwareversion=None,
			connected=1)

		# At this moment, VRM portal ID is the MAC address of the CCGX. Anyhow, it should be string uniquely
		# identifying the CCGX.
		self._dbusservice.add_path('/Serial', value=get_vrm_portal_id())

		self._dbusservice.add_path(
			'/AvailableBatteryServices', value=None, gettextcallback=self._gettext)
		self._dbusservice.add_path(
			'/AvailableBatteryMeasurements', value=None, gettextcallback=self._gettext)
		self._dbusservice.add_path(
			'/AutoSelectedBatteryService', value=None, gettextcallback=self._gettext)
		self._dbusservice.add_path(
			'/AutoSelectedBatteryMeasurement', value=None, gettextcallback=self._gettext)
		self._dbusservice.add_path(
			'/ActiveBatteryService', value=None, gettextcallback=self._gettext)
		self._dbusservice.add_path(
			'/PvInvertersProductIds', value=None)
		self._summeditems = {
			'/Ac/Grid/L1/Power': {'gettext': '%.0F W'},
			'/Ac/Grid/L2/Power': {'gettext': '%.0F W'},
			'/Ac/Grid/L3/Power': {'gettext': '%.0F W'},
			'/Ac/Grid/Total/Power': {'gettext': '%.0F W'},
			'/Ac/Grid/NumberOfPhases': {'gettext': '%.0F W'},
			'/Ac/Grid/ProductId': {'gettext': '%s'},
			'/Ac/Grid/DeviceType': {'gettext': '%s'},
			'/Ac/Genset/L1/Power': {'gettext': '%.0F W'},
			'/Ac/Genset/L2/Power': {'gettext': '%.0F W'},
			'/Ac/Genset/L3/Power': {'gettext': '%.0F W'},
			'/Ac/Genset/Total/Power': {'gettext': '%.0F W'},
			'/Ac/Genset/NumberOfPhases': {'gettext': '%.0F W'},
			'/Ac/Genset/ProductId': {'gettext': '%s'},
			'/Ac/Genset/DeviceType': {'gettext': '%s'},
			'/Ac/Consumption/L1/Power': {'gettext': '%.0F W'},
			'/Ac/Consumption/L2/Power': {'gettext': '%.0F W'},
			'/Ac/Consumption/L3/Power': {'gettext': '%.0F W'},
			'/Ac/Consumption/Total/Power': {'gettext': '%.0F W'},
			'/Ac/Consumption/NumberOfPhases': {'gettext': '%.0F W'},
			'/Ac/PvOnOutput/L1/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnOutput/L2/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnOutput/L3/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnOutput/Total/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnOutput/NumberOfPhases': {'gettext': '%.0F W'},
			'/Ac/PvOnGrid/L1/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnGrid/L2/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnGrid/L3/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnGrid/Total/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnGrid/NumberOfPhases': {'gettext': '%.0F W'},
			'/Ac/PvOnGenset/L1/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnGenset/L2/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnGenset/L3/Power': {'gettext': '%.0F W'},
			'/Ac/PvOnGenset/NumberOfPhases': {'gettext': '%d'},
			'/Ac/PvOnGenset/Total/Power': {'gettext': '%.0F W'},
			'/Dc/Pv/Power': {'gettext': '%.0F W'},
			'/Dc/Pv/Current': {'gettext': '%.1F A'},
			'/Dc/Battery/Voltage': {'gettext': '%.2F V'},
			'/Dc/Battery/Current': {'gettext': '%.1F A'},
			'/Dc/Battery/Power': {'gettext': '%.0F W'},
			'/Dc/Battery/Soc': {'gettext': '%.0F %%'},
			'/Dc/Battery/State': {'gettext': '%s'},
			'/Dc/Battery/TimeToGo': {'gettext': '%.0F s'},
			'/Dc/Battery/ConsumedAmphours': {'gettext': '%.1F Ah'},
			'/Dc/Charger/Power': {'gettext': '%.0F %%'},
			'/Dc/Vebus/Current': {'gettext': '%.1F A'},
			'/Dc/Vebus/Power': {'gettext': '%.0F W'},
			'/Dc/System/Power': {'gettext': '%.0F W'},
			'/Hub': {'gettext': '%s'},
			'/Ac/ActiveIn/Source': {'gettext': '%s'},
			'/VebusService': {'gettext': '%s'}
		}

		for path in self._summeditems.keys():
			self._dbusservice.add_path(path, value=None, gettextcallback=self._gettext)

		self._batteryservice = None
		self._determinebatteryservice()

		if self._batteryservice is None:
			logger.info("Battery service initialized to None (setting == %s)" %
				self._settings['batteryservice'])

		self._changed = True
		for service, instance in self._dbusmonitor.get_service_list().items():
			self._device_added(service, instance, do_service_change=False)

		self._handleservicechange()
		self._updatevalues()

		self._writeVebusSocCounter = 9
		gobject.timeout_add(1000, exit_on_error, self._handletimertick)

	def _handlechangedsetting(self, setting, oldvalue, newvalue):
		self._determinebatteryservice()
		self._changed = True

	def _determinebatteryservice(self):
		auto_battery_service = self._autoselect_battery_service()
		auto_battery_measurement = None
		if auto_battery_service is not None:
			services = self._dbusmonitor.get_service_list()
			if auto_battery_service in services:
				auto_battery_measurement = \
					self._get_instance_service_name(auto_battery_service, services[auto_battery_service])
				auto_battery_measurement = auto_battery_measurement.replace('.', '_').replace('/', '_') + '/Dc/0'
		self._dbusservice['/AutoSelectedBatteryMeasurement'] = auto_battery_measurement

		if self._settings['batteryservice'] == self.BATSERVICE_DEFAULT:
			newbatteryservice = auto_battery_service
			self._dbusservice['/AutoSelectedBatteryService'] = (
				'No battery monitor found' if newbatteryservice is None else
				self._get_readable_service_name(newbatteryservice))

		elif self._settings['batteryservice'] == self.BATSERVICE_NOBATTERY:
			self._dbusservice['/AutoSelectedBatteryService'] = None
			newbatteryservice = None

		else:
			self._dbusservice['/AutoSelectedBatteryService'] = None

			s = self._settings['batteryservice'].split('/')
			if len(s) != 2:
				logger.error("The battery setting (%s) is invalid!" % self._settings['batteryservice'])
			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 battery monitor does not exist. Don't auto change the setting (it might come
				# back). And also don't autoselect another.
				newbatteryservice = None
			else:
				# According to https://www.python.org/dev/peps/pep-3106/, dict.keys() and dict.values()
				# always have the same order.
				newbatteryservice = services.keys()[services.values().index(instance)]

		if newbatteryservice != self._batteryservice:
			services = self._dbusmonitor.get_service_list()
			instance = services.get(newbatteryservice, None)
			if instance is None:
				battery_service = None
			else:
				battery_service = self._get_instance_service_name(newbatteryservice, instance)
			self._dbusservice['/ActiveBatteryService'] = battery_service
			logger.info("Battery service, setting == %s, changed from %s to %s (%s)" %
				(self._settings['batteryservice'], self._batteryservice, newbatteryservice, instance))
			self._batteryservice = newbatteryservice

	def _autoselect_battery_service(self):
		# Default setting business logic:
		# first try to use a battery service (BMV or Lynx Shunt VE.Can). If there is more than one battery
		# service, use the first one (sort alphabetical). If no battery service is available, check if there
		# are not Solar chargers and no normal chargers. If they are not there, assume this is a hub-2,
		# hub-3 or hub-4 system and use VE.Bus SOC.
		battery_service = self._get_first_service('com.victronenergy.battery')
		if battery_service is not None:
			return battery_service

		if len(self._dbusmonitor.get_service_list('com.victronenergy.solarcharger')) > 0:
			return None

		if len(self._dbusmonitor.get_service_list('com.victronenergy.charger')) > 0:
			return None

		vebus_service = self._get_first_service('com.victronenergy.vebus')

		return vebus_service  # will be None when no vebus service found

	# Called on a one second timer
	def _handletimertick(self):
		if self._changed:
			self._updatevalues()
		self._changed = False

		self._writeVebusSocCounter += 1
		if self._writeVebusSocCounter >= 10:
			self._writeVebusSoc()
			self._writeVebusSocCounter = 0

		return True  # keep timer running

	def _writeVebusSoc(self):
		# ==== COPY BATTERY SOC TO VEBUS ====
		if self._settings['writevebussoc'] and self._dbusservice['/VebusService'] and self._dbusservice['/Dc/Battery/Soc'] and \
			self._batteryservice.split('.')[2] != 'vebus':

			logger.debug("writing this soc to vebus: %d", self._dbusservice['/Dc/Battery/Soc'])
			self._dbusmonitor.get_item(self._dbusservice['/VebusService'], '/Soc').set_value(self._dbusservice['/Dc/Battery/Soc'])

	def _updatepvinverterspidlist(self):
		# Create list of connected pv inverters id's
		pvinverters = self._dbusmonitor.get_service_list('com.victronenergy.pvinverter')
		productids = []

		for pvinverter in pvinverters:
			pid = self._dbusmonitor.get_value(pvinverter, '/ProductId')
			if pid not in productids:
				productids.append(pid)
		self._dbusservice['/PvInvertersProductIds'] = productids if productids else None

	def _updatevalues(self):
		# ==== PREPARATIONS ====
		# Determine values used in logic below
		vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus')
		vebuspower = 0
		for vebus in vebusses:
			v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage')
			i = self._dbusmonitor.get_value(vebus, '/Dc/0/Current')
			if v is not None and i is not None:
				vebuspower += v * i

		# ==== PVINVERTERS ====
		pvinverters = self._dbusmonitor.get_service_list('com.victronenergy.pvinverter')
		newvalues = {}
		pos = {0: '/Ac/PvOnGrid', 1: '/Ac/PvOnOutput', 2: '/Ac/PvOnGenset'}
		total = {0: None, 1: None, 2: None}
		for pvinverter in pvinverters:
			# Position will be None if PV inverter service has just been removed (after retrieving the
			# service list).
			position = pos.get(self._dbusmonitor.get_value(pvinverter, '/Position'))
			if position is not None:
				for phase in range(1, 4):
					power = self._dbusmonitor.get_value(pvinverter, '/Ac/L%s/Power' % phase)
					if power is not None:
						path = '%s/L%s/Power' % (position, phase)
						newvalues[path] = _safeadd(newvalues.get(path), power)

		for path in pos.values():
			self._compute_phase_totals(path, newvalues)

		# ==== SOLARCHARGERS ====
		solarchargers = self._dbusmonitor.get_service_list('com.victronenergy.solarcharger')
		solarcharger_batteryvoltage = None
		for solarcharger in solarchargers:
			v = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Voltage')
			if v is None:
				continue
			i = self._dbusmonitor.get_value(solarcharger, '/Dc/0/Current')
			if i is None:
				continue

			if '/Dc/Pv/Power' not in newvalues:
				newvalues['/Dc/Pv/Power'] = v * i
				newvalues['/Dc/Pv/Current'] = i
				solarcharger_batteryvoltage = v
			else:
				newvalues['/Dc/Pv/Power'] += v * i
				newvalues['/Dc/Pv/Current'] += i

		# ==== CHARGERS ====
		chargers = self._dbusmonitor.get_service_list('com.victronenergy.charger')
		charger_batteryvoltage = None
		for charger in chargers:
			# Assume the battery connected to output 0 is the main battery
			v = self._dbusmonitor.get_value(charger, '/Dc/0/Voltage')
			if v is None:
				continue

			charger_batteryvoltage = v

			i = self._dbusmonitor.get_value(charger, '/Dc/0/Current')
			if i is None:
				continue

			if '/Dc/Charger/Power' not in newvalues:
				newvalues['/Dc/Charger/Power'] = v * i
			else:
				newvalues['/Dc/Charger/Power'] += v * i

		# ==== BATTERY ====
		if self._batteryservice is not None:
			batteryservicetype = self._batteryservice.split('.')[2]  # either 'battery' or 'vebus'
			newvalues['/Dc/Battery/Soc'] = self._dbusmonitor.get_value(self._batteryservice,'/Soc')
			newvalues['/Dc/Battery/TimeToGo'] = self._dbusmonitor.get_value(self._batteryservice,'/TimeToGo')
			newvalues['/Dc/Battery/ConsumedAmphours'] = self._dbusmonitor.get_value(self._batteryservice,'/ConsumedAmphours')

			if batteryservicetype == 'battery':
				newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage')
				newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current')
				newvalues['/Dc/Battery/Power'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Power')

			elif batteryservicetype == 'vebus':
				newvalues['/Dc/Battery/Voltage'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Voltage')
				newvalues['/Dc/Battery/Current'] = self._dbusmonitor.get_value(self._batteryservice, '/Dc/0/Current')
				if newvalues['/Dc/Battery/Voltage'] is not None and newvalues['/Dc/Battery/Current'] is not None:
					newvalues['/Dc/Battery/Power'] = (
						newvalues['/Dc/Battery/Voltage'] * newvalues['/Dc/Battery/Current'])

			p = newvalues.get('/Dc/Battery/Power', None)
			if p is not None:
				if p > 30:
					newvalues['/Dc/Battery/State'] = self.STATE_CHARGING
				elif p < -30:
					newvalues['/Dc/Battery/State'] = self.STATE_DISCHARGING
				else:
					newvalues['/Dc/Battery/State'] = self.STATE_IDLE

		else:
			batteryservicetype = None
			if solarcharger_batteryvoltage is not None:
				newvalues['/Dc/Battery/Voltage'] = solarcharger_batteryvoltage
			elif charger_batteryvoltage is not None:
				newvalues['/Dc/Battery/Voltage'] = charger_batteryvoltage
			else:
				# CCGX-connected system consists of only a Multi, but it is not user-selected, nor
				# auto-selected as the battery-monitor, probably because there are other loads or chargers.
				# In that case, at least use its reported battery voltage.
				vebusses = self._dbusmonitor.get_service_list('com.victronenergy.vebus')
				for vebus in vebusses:
					v = self._dbusmonitor.get_value(vebus, '/Dc/0/Voltage')
					if v is not None:
						newvalues['/Dc/Battery/Voltage'] = v

			if self._settings['hasdcsystem'] == 0 and '/Dc/Battery/Voltage' in newvalues:
				# No unmonitored DC loads or chargers, and also no battery monitor: derive battery watts
				# and amps from vebus, solarchargers and chargers.
				assert '/Dc/Battery/Power' not in newvalues
				assert '/Dc/Battery/Current' not in newvalues
				p = newvalues.get('/Dc/Pv/Power', 0) + newvalues.get('/Dc/Charger/Power', 0) + vebuspower
				voltage = newvalues['/Dc/Battery/Voltage']
				newvalues['/Dc/Battery/Current'] = p / voltage if voltage > 0 else None
				newvalues['/Dc/Battery/Power'] = p

		# ==== SYSTEM ====
		if self._settings['hasdcsystem'] == 1 and batteryservicetype == 'battery':
			# Calculate power being generated/consumed by not measured devices in the network.
			# /Dc/System: positive: consuming power
			# VE.Bus: Positive: current flowing from the Multi to the dc system or battery
			# Solarcharger & other chargers: positive: charging
			# battery: Positive: charging battery.
			# battery = solarcharger + charger + ve.bus - system

			battery_power = newvalues.get('/Dc/Battery/Power')
			if battery_power is not None:
				dc_pv_power = newvalues.get('/Dc/Pv/Power', 0)
				charger_power = newvalues.get('/Dc/Charger/Power', 0)
				newvalues['/Dc/System/Power'] = dc_pv_power + charger_power + vebuspower - battery_power

		# ==== Vebus ====
		# Assume there's only 1 multi service present on the D-Bus
		multi_path = self._get_first_service('com.victronenergy.vebus')
		if multi_path is not None:
			dc_current = self._dbusmonitor.get_value(multi_path, '/Dc/0/Current')
			newvalues['/Dc/Vebus/Current'] = dc_current
			dc_power = self._dbusmonitor.get_value(multi_path, '/Dc/0/Power')
			# Just in case /Dc/0/Power is not available
			if dc_power == None and dc_current is not None:
				dc_voltage = self._dbusmonitor.get_value(multi_path, '/Dc/0/Voltage')
				if dc_voltage is not None:
					dc_power = dc_voltage * dc_current
			# Note that there is also vebuspower, which is the total DC power summed over all multis.
			# However, this value cannot be combined with /Dc/Multi/Current, because it does not make sense
			# to add the Dc currents of all multis if they do not share the same DC voltage.
			newvalues['/Dc/Vebus/Power'] = dc_power

		newvalues['/VebusService'] = multi_path

		# ===== AC IN SOURCE =====
		ac_in_source = None
		active_input = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/ActiveInput')
		if active_input is not None:
			settings_path = '/Settings/SystemSetup/AcInput%s' % (active_input + 1)
			ac_in_source = self._dbusmonitor.get_value('com.victronenergy.settings', settings_path)
		newvalues['/Ac/ActiveIn/Source'] = ac_in_source

		# ===== HUB MODE =====
		# The code below should be executed after PV inverter data has been updated, because we need the
		# PV inverter total power to update the consumption.
		hub = None
		if self._dbusmonitor.get_value(multi_path, '/Hub4/AcPowerSetpoint') is not None:
			hub = 4
		elif newvalues.get('/Dc/Pv/Power', None) is not None:
			hub = 1
		elif newvalues.get('/Ac/PvOnOutput/Total/Power', None) is not None:
			hub = 2
		elif newvalues.get('/Ac/PvOnGrid/Total/Power', None) is not None or \
			newvalues.get('/Ac/PvOnGenset/Total/Power', None) is not None:
			hub = 3
		newvalues['/Hub'] = hub

		# ===== GRID METERS & CONSUMPTION ====
		consumption = { "L1" : None, "L2" : None, "L3" : None }
		for device_type in ['Grid', 'Genset']:
			servicename = 'com.victronenergy.%s' % device_type.lower()
			em_service = self._get_first_service(servicename)
			uses_active_input = False
			if multi_path is not None:
				# If a grid meter is present we use values from it. If not, we look at the multi. If it has
				# AcIn1 or AcIn2 connected to the grid, we use those values.
				# com.victronenergy.grid.??? indicates presence of an energy meter used as grid meter.
				# com.victronenergy.vebus.???/Ac/ActiveIn/ActiveInput: decides which whether we look at AcIn1
				# or AcIn2 as possible grid connection.
				if ac_in_source is not None:
					uses_active_input = ac_in_source > 0 and (ac_in_source == 2) == (device_type == 'Genset')
			for phase in consumption:
				p = None
				pvpower = newvalues.get('/Ac/PvOn%s/%s/Power' % (device_type, phase))
				if em_service is not None:
					p = self._dbusmonitor.get_value(em_service, '/Ac/%s/Power' % phase)
					# Compute consumption between energy meter and multi (meter power - multi AC in) and
					# add an optional PV inverter on input to the mix.
					c = consumption[phase]
					if uses_active_input:
						ac_in = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)
						if ac_in is not None:
							c = _safeadd(c, -ac_in)
					# If there's any power coming from a PV inverter in the inactive AC in (which is unlikely),
					# it will still be used, because there may also be a load in the same ACIn consuming
					# power, or the power could be fed back to the net.
					c = _safeadd(c, p, pvpower)
					consumption[phase] = None if c is None else max(0, c)
				else:
					if uses_active_input:
						p = self._dbusmonitor.get_value(multi_path, '/Ac/ActiveIn/%s/P' % phase)
					# No relevant energy meter present. Assume there is no load between the grid and the multi.
					# There may be a PV inverter present though (Hub-3 setup).
					if pvpower != None:
						p = _safeadd(p, -pvpower)
				newvalues['/Ac/%s/%s/Power' % (device_type, phase)] = p
			self._compute_phase_totals('/Ac/%s' % device_type, newvalues)
			if em_service is not None:
				newvalues['/Ac/%s/ProductId' % device_type] = self._dbusmonitor.get_value(em_service, '/ProductId')
				newvalues['/Ac/%s/DeviceType' % device_type] = self._dbusmonitor.get_value(em_service, '/DeviceType')
		for phase in consumption:
			c = consumption[phase]
			pvpower = newvalues.get('/Ac/PvOnOutput/%s/Power' % phase)
			c = _safeadd(c, pvpower)
			if multi_path is not None:
				ac_out = self._dbusmonitor.get_value(multi_path, '/Ac/Out/%s/P' % phase)
				c = _safeadd(c, ac_out)
			newvalues['/Ac/Consumption/%s/Power' % phase] = None if c is None else max(0, c)
		self._compute_phase_totals('/Ac/Consumption', newvalues)
		# TODO EV Add Multi DeviceType & ProductID. Unfortunately, the com.victronenergy.vebus.??? tree does
		# not contain a /ProductId entry.

		# ==== UPDATE DBUS ITEMS ====
		for path in self._summeditems.keys():
			# Why the None? Because we want to invalidate things we don't have anymore.
			self._dbusservice[path] = newvalues.get(path, None)

	def _handleservicechange(self):
		# Update the available battery monitor services, used to populate the dropdown in the settings.
		# Below code makes a dictionary. The key is [dbuserviceclass]/[deviceinstance]. For example
		# "battery/245". The value is the name to show to the user in the dropdown. The full dbus-
		# servicename, ie 'com.victronenergy.vebus.ttyO1' is not used, since the last part of that is not
		# fixed. dbus-serviceclass name and the device instance are already fixed, so best to use those.

		services = self._dbusmonitor.get_service_list('com.victronenergy.vebus')
		services.update(self._dbusmonitor.get_service_list('com.victronenergy.battery'))

		ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'}
		for servicename, instance in services.items():
			key = self._get_instance_service_name(servicename, instance)
			ul[key] = self._get_readable_service_name(servicename)
		self._dbusservice['/AvailableBatteryServices'] = json.dumps(ul)

		ul = {self.BATSERVICE_DEFAULT: 'Automatic', self.BATSERVICE_NOBATTERY: 'No battery monitor'}
		# For later: for device supporting multiple Dc measurement we should add entries for /Dc/1 etc as
		# well.
		for servicename, instance in services.items():
			key = self._get_instance_service_name(servicename, instance).replace('.', '_').replace('/', '_') + '/Dc/0'
			ul[key] = self._get_readable_service_name(servicename)
		self._dbusservice['/AvailableBatteryMeasurements'] = dbus.Dictionary(ul, signature='sv')

		self._determinebatteryservice()
		self._updatepvinverterspidlist()

		self._changed = True

	def _get_readable_service_name(self, servicename):
		return (self._dbusmonitor.get_value(servicename, '/ProductName') + ' 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_service_mapping_path(self, service, instance):
		sn = self._get_instance_service_name(service, instance).replace('.', '_').replace('/', '_')
		return '/ServiceMapping/%s' % sn

	def _dbus_value_changed(self, dbusServiceName, dbusPath, dict, changes, deviceInstance):
		self._changed = True

		# Workaround because com.victronenergy.vebus is available even when there is no vebus product
		# connected.
		if dbusPath in ['/ProductName', '/Mgmt/Connection']:
			self._handleservicechange()

	def _device_added(self, service, instance, do_service_change=True):
		path = self._get_service_mapping_path(service, instance)
		if path in self._dbusservice:
			self._dbusservice[path] = service
		else:
			self._dbusservice.add_path(path, service)

		if do_service_change:
			self._handleservicechange()

	def _device_removed(self, service, instance):
		path = self._get_service_mapping_path(service, instance)
		if path in self._dbusservice:
			del self._dbusservice[path]
		self._handleservicechange()

	def _gettext(self, path, value):
		if path == '/Dc/Battery/State':
			state = {self.STATE_IDLE: 'Idle', self.STATE_CHARGING: 'Charging',
				self.STATE_DISCHARGING: 'Discharging'}
			return state[value]
		item = self._summeditems.get(path)
		if item is not None:
			return item['gettext'] % value
		return value

	def _compute_phase_totals(self, path, newvalues):
		total_power = None
		number_of_phases = None
		for phase in range(1, 4):
			p = newvalues.get('%s/L%s/Power' % (path, phase))
			total_power = _safeadd(total_power, p)
			if p is not None:
				number_of_phases = phase
		newvalues[path + '/Total/Power'] = total_power
		newvalues[path + '/NumberOfPhases'] = number_of_phases

	def _get_first_service(self, classfilter=None):
		services = self._dbusmonitor.get_service_list(classfilter=classfilter)
		if len(services) == 0:
			return None
		return sorted(services.keys())[0]
parser.add_argument("-d", "--debug", help="set logging level to debug",
				action="store_true")

args = parser.parse_args()

# Init logging
logging.basicConfig(level=(logging.DEBUG if args.debug else logging.INFO))
logging.info(__file__ + " is starting up")
logLevel = {0: 'NOTSET', 10: 'DEBUG', 20: 'INFO', 30: 'WARNING', 40: 'ERROR'}
logging.info('Loglevel set to ' + logLevel[logging.getLogger().getEffectiveLevel()])

# Have a mainloop, so we can send/receive asynchronous calls to and from dbus
DBusGMainLoop(set_as_default=True)

dbusservice = VeDbusService(args.name)

logging.info("using device instance %s" % args.deviceinstance)

# Create the management objects, as specified in the ccgx dbus-api document
dbusservice.add_path('/Management/ProcessName', __file__)
dbusservice.add_path('/Management/ProcessVersion', 'Unkown version, and running on Python ' + platform.python_version())
dbusservice.add_path('/Management/Connection', 'Data taken from mk2dbus')

# Create the mandatory objects
dbusservice.add_path('/DeviceInstance', args.deviceinstance)
dbusservice.add_path('/ProductId', 0)
dbusservice.add_path('/ProductName', 'vebus device with ac sensors')
dbusservice.add_path('/FirmwareVersion', 0)
dbusservice.add_path('/HardwareVersion', 0)
dbusservice.add_path('/Connected', 1)
class Battery:
    hardware_version = ""
    voltage = 0
    current = 0
    capacity_remain = 0
    capacity = 0
    cycles = 0
    production = ""
    protection = Protection()
    version = 0
    soc = 0
    charge_fet = True
    discharge_fet = True
    cell_count = 0
    temp_censors = 0
    temp1 = 0
    temp2 = 0
    cells = []
    control_charging = False
    control_voltage = 0
    control_current = 0
    control_previous_total = 0
    control_previous_max = 0
    control_discharge_current = 0
    control_charge_current = 0
    control_allow_charge = True
    max_battery_voltage = 0
    min_battery_voltage = 0

    def __init__(self, port):
        self.port = port

    # degree_sign = u'\N{DEGREE SIGN}'
    command_general = b"\xDD\xA5\x03\x00\xFF\xFD\x77"
    command_cell = b"\xDD\xA5\x04\x00\xFF\xFC\x77"
    command_hardware = b"\xDD\xA5\x05\x00\xFF\xFB\x77"
    zero_char = chr(48)

    def to_protection_bits(self, byte_data):
        tmp = bin(byte_data)[2:].rjust(13, self.zero_char)
        self.protection.voltage_high_cell = self.is_bit_set(tmp[12])
        self.protection.voltage_low_cell = self.is_bit_set(tmp[11])
        self.protection.voltage_high = self.is_bit_set(tmp[10])
        self.protection.voltage_low = self.is_bit_set(tmp[9])
        self.protection.temp_high_charge = self.is_bit_set(tmp[8])
        self.protection.temp_low_charge = self.is_bit_set(tmp[7])
        self.protection.temp_high_discharge = self.is_bit_set(tmp[6])
        self.protection.temp_low_discharge = self.is_bit_set(tmp[5])
        self.protection.current_over = self.is_bit_set(tmp[4])
        self.protection.current_under = self.is_bit_set(tmp[3])
        self.protection.short = self.is_bit_set(tmp[2])
        self.protection.IC_inspection = self.is_bit_set(tmp[1])
        self.protection.software_lock = self.is_bit_set(tmp[0])

    def to_temp(self, sensor, value):
        # Keep the temp value between -20 and 100 to handle sensor issues or no data.
        # The BMS should have already protected before those limits have been reached.
        if sensor == 1:
            self.temp1 = min(max(value, -20), 100)
        if sensor == 2:
            self.temp2 = min(max(value, -20), 100)

    def is_bit_set(self, tmp):
        return False if tmp == self.zero_char else True

    def to_cell_bits(self, byte_data, byte_data_high):
        # clear the list
        for c in self.cells:
            self.cells.remove(c)
        # get up to the first 16 cells
        tmp = bin(byte_data)[2:].rjust(min(self.cell_count, 16),
                                       self.zero_char)
        for bit in reversed(tmp):
            self.cells.append(Cell(self.is_bit_set(bit)))
        # get any cells above 16
        if self.cell_count > 16:
            tmp = bin(byte_data_high)[2:].rjust(self.cell_count - 16,
                                                self.zero_char)
            for bit in reversed(tmp):
                self.cells.append(Cell(self.is_bit_set(bit)))

    def to_fet_bits(self, byte_data):
        tmp = bin(byte_data)[2:].rjust(2, self.zero_char)
        self.charge_fet = self.is_bit_set(tmp[1])
        self.discharge_fet = self.is_bit_set(tmp[0])

    def log_battery_data(self):
        logger.debug("voltage    {0}V   current  {1}A".format(
            self.voltage, self.current))
        logger.debug("   capacity   {0}Ah of {1}Ah   SOC {2}%".format(
            self.capacity_remain, self.capacity, self.soc))

        for c in range(self.cell_count):
            cell = str(c + 1)
            balance = "B" if self.cells[c].balance else " "
            cell_volt = str(self.cells[c].voltage)
            logger.debug("C[" + cell.rjust(2, self.zero_char) + "]  " +
                         balance + cell_volt + "V")

    def publish_battery_dbus(self):
        # Update SOC, DC and System items
        self._dbusservice['/System/NrOfCellsPerBattery'] = self.cell_count
        self._dbusservice['/Soc'] = round(self.soc, 2)
        self._dbusservice['/Dc/0/Voltage'] = round(self.voltage, 2)
        self._dbusservice['/Dc/0/Current'] = round(self.current, 2)
        self._dbusservice['/Dc/0/Power'] = round(self.voltage * self.current,
                                                 2)
        self._dbusservice['/Dc/0/Temperature'] = round(
            (float(self.temp1) + float(self.temp2)) / 2, 2)

        # Update battery extras
        self._dbusservice['/History/ChargeCycles'] = self.cycles
        self._dbusservice['/Io/AllowToCharge'] = 1 if self.charge_fet else 0
        self._dbusservice[
            '/Io/AllowToDischarge'] = 1 if self.discharge_fet else 0
        self._dbusservice['/System/MinCellTemperature'] = min(
            self.temp1, self.temp2)
        self._dbusservice['/System/MaxCellTemperature'] = max(
            self.temp1, self.temp2)

        # Updates from cells
        max_voltage = 0
        max_cell = ''
        min_voltage = 99
        min_cell = ''
        balance = False
        total_voltage = 0
        for c in range(self.cell_count):
            total_voltage += self.cells[c].voltage
            if max_voltage < self.cells[c].voltage:
                max_voltage = self.cells[c].voltage
                max_cell = c
            if min_voltage > self.cells[c].voltage:
                min_voltage = self.cells[c].voltage
                min_cell = c
            if self.cells[c].balance:
                balance = True
        self._dbusservice['/System/MaxCellVoltage'] = max_voltage
        self._dbusservice['/System/MaxVoltageCellId'] = 'C' + str(max_cell + 1)
        self._dbusservice['/System/MinCellVoltage'] = min_voltage
        self._dbusservice['/System/MinVoltageCellId'] = 'C' + str(min_cell + 1)
        self._dbusservice['/Balancing'] = 1 if balance else 0
        self.manage_charge_current()
        # self.manage_control_charging(max_voltage, min_voltage, total_voltage, balance)

        # Update the alarms
        self._dbusservice[
            '/Alarms/LowVoltage'] = 2 if self.protection.voltage_low else 0
        self._dbusservice[
            '/Alarms/HighVoltage'] = 2 if self.protection.voltage_high else 0
        self._dbusservice[
            '/Alarms/LowSoc'] = 2 if self.soc < 10 else 1 if self.soc < 20 else 0
        self._dbusservice[
            '/Alarms/HighChargeCurrent'] = 1 if self.protection.current_over else 0
        self._dbusservice[
            '/Alarms/HighDischargeCurrent'] = 1 if self.protection.current_under else 0
        self._dbusservice['/Alarms/CellImbalance'] = 2 if self.protection.voltage_low_cell \
            or self.protection.voltage_high_cell else 0
        self._dbusservice['/Alarms/InternalFailure'] = 2 if self.protection.short \
            or self.protection.IC_inspection \
            or self.protection.software_lock else 0
        self._dbusservice[
            '/Alarms/HighChargeTemperature'] = 1 if self.protection.temp_high_charge else 0
        self._dbusservice[
            '/Alarms/LowChargeTemperature'] = 1 if self.protection.temp_low_charge else 0
        self._dbusservice[
            '/Alarms/HighTemperature'] = 1 if self.protection.temp_high_discharge else 0
        self._dbusservice[
            '/Alarms/LowTemperature'] = 1 if self.protection.temp_low_discharge else 0

        logging.debug("logged to dbus ", round(self.voltage / 100, 2),
                      round(self.current / 100, 2), round(self.soc, 2))

    def manage_charge_current(self):
        # Start with the current values
        charge_current = self.control_charge_current
        discharge_current = self.control_discharge_current
        allow_charge = self.control_allow_charge

        # Change depending on the SOC values
        if self.soc > 99:
            allow_charge = False
        else:
            allow_charge = True
        # Change depending on the SOC values
        if self.soc >= 100:
            charge_current = 0
        elif 98 < self.soc < 100:
            charge_current = 1
        elif 95 < self.soc <= 97:
            charge_current = 4
        elif 91 < self.soc <= 95:
            charge_current = MAX_BATTERY_CURRENT / 2
        else:
            charge_current = MAX_BATTERY_CURRENT
        # Change depending on the SOC values
        if self.soc <= 20:
            discharge_current = 5
        elif 20 < self.soc <= 30:
            discharge_current = MAX_BATTERY_DISCHARGE_CURRENT / 4
        elif 30 < self.soc <= 35:
            discharge_current = MAX_BATTERY_DISCHARGE_CURRENT / 2
        else:
            discharge_current = MAX_BATTERY_DISCHARGE_CURRENT

        # Update the dbus values if they changed
        if charge_current != self.control_charge_current:
            self.control_charge_current = charge_current
            self._dbusservice[
                '/Info/MaxChargeCurrent'] = self.control_charge_current
        if discharge_current != self.control_discharge_current:
            self.control_discharge_current = discharge_current
            self._dbusservice[
                '/Info/MaxDischargeCurrent'] = self.control_discharge_current
        if allow_charge != self.control_allow_charge:
            if allow_charge and self.charge_fet:
                self._dbusservice['/Io/AllowToCharge'] = 1
                self._dbusservice['/System/NrOfModulesBlockingCharge'] = 0
            else:
                self._dbusservice['/Io/AllowToCharge'] = 0
                self._dbusservice['/System/NrOfModulesBlockingCharge'] = 1

    def manage_control_charging(self, max_voltage, min_voltage, total_voltage,
                                balance):
        # Nothing to do if we cannot charge
        if not self.charge_fet or self.current < 0:
            if self.control_charging:
                self.control_charging = False
                self._dbusservice[
                    '/Info/MaxChargeVoltage'] = self.max_battery_voltage
                self._dbusservice[
                    '/Info/MaxChargeCurrent'] = MAX_BATTERY_CURRENT
                logger.info(">STOP< control charging")
            return
        if max_voltage > 3.50:
            logger.info(
                ">CHECK< control charging min {0} max {1} tot {2} Bal {3}".
                format(min_voltage, max_voltage, total_voltage,
                       1 if balance else 0))
        if not self.control_charging and max_voltage > 3.50 and min_voltage < 3.45:
            # should we start
            self.control_charging = True
            self.control_previous_total = total_voltage
            self.control_current = min(MAX_BATTERY_CURRENT, 0.5)
            self.control_voltage = min(self.max_battery_voltage,
                                       total_voltage - 1)
            self.control_previous_max = max_voltage
            self._dbusservice['/Info/MaxChargeVoltage'] = self.control_voltage
            self._dbusservice['/Info/MaxChargeCurrent'] = self.control_current
            logger.info(">START< control charging {0}A {1}V".format(
                self.control_current, self.control_voltage))
        else:
            # If all cells are low then we can stop control
            if self.control_charging and max_voltage < 3.45:
                self.control_charging = False
                self._dbusservice[
                    '/Info/MaxChargeVoltage'] = self.max_battery_voltage
                self._dbusservice[
                    '/Info/MaxChargeCurrent'] = MAX_BATTERY_CURRENT
                logger.info(">STOP< control charging")
                return

            if self.control_charging and max_voltage > 3.64:
                self._dbusservice['/Info/MaxChargeVoltage'] = min(
                    self.max_battery_voltage, 48)
                self._dbusservice['/Info/MaxChargeCurrent'] = min(
                    MAX_BATTERY_CURRENT, 0.001)
                logger.info(">STOP< No Balancing!")
                return

            # Limit the charge voltage if a few cells get too high
            if max_voltage > (self.control_previous_max + 0.01):
                # Still to high
                self.control_current -= 0.005
                self.control_voltage -= 0.01
                self.control_current = max(0.2, self.control_current)
                self.control_voltage = max(self.max_battery_voltage - 4,
                                           self.control_voltage)
                self.control_previous_total = total_voltage
                self._dbusservice[
                    '/Info/MaxChargeVoltage'] = self.control_voltage
                self._dbusservice[
                    '/Info/MaxChargeCurrent'] = self.control_current
                logger.info(">DOWN< control charging {0}A {1}V".format(
                    self.control_current, self.control_voltage))
                return
            else:
                if total_voltage < (self.control_previous_total - 0.03):
                    # To low
                    self.control_current += 0.005
                    self.control_voltage += 0.01
                    self.control_current = min(MAX_BATTERY_CURRENT,
                                               self.control_current)
                    self.control_voltage = min(self.max_battery_voltage,
                                               self.control_voltage)
                    self.control_previous_total = total_voltage
                    self._dbusservice[
                        '/Info/MaxChargeVoltage'] = self.control_voltage
                    self._dbusservice[
                        '/Info/MaxChargeCurrent'] = self.control_current
                    logger.info(">UP< control charging {0}A {1}V".format(
                        self.control_current, self.control_voltage))
                    return

    def read_gen_data(self):
        gen_data = read_serial_data(self.command_general, self.port)
        # check if connect success
        if gen_data is False:
            return gen_data

        voltage, current, capacity_remain, capacity, self.cycles, self.production, balance, \
            balance2, protection, version, self.soc, fet, self.cell_count, self.temp_censors, temp1, temp2 \
            = unpack_from('>HhHHHHhHHBBBBBHH', gen_data)
        self.voltage = voltage / 100
        self.current = current / 100
        self.capacity_remain = capacity_remain / 100
        self.capacity = capacity / 100
        self.to_temp(1, (temp1 - 2731) / 10)
        self.to_temp(2, (temp2 - 2731) / 10)
        self.to_cell_bits(balance, balance2)
        self.version = float(
            str(version >> 4 & 0x0F) + "." + str(version & 0x0F))
        self.to_fet_bits(fet)
        self.to_protection_bits(protection)
        self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count
        self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count

    def read_cell_data(self):
        cell_data = read_serial_data(self.command_cell, self.port)
        # check if connect success
        if cell_data is False or len(cell_data) < self.cell_count * 2:
            return cell_data

        for c in range(self.cell_count):
            self.cells[c].voltage = unpack_from('>H', cell_data,
                                                c * 2)[0] / 1000

    def read_hardware_data(self):
        hardware_data = read_serial_data(self.command_hardware, self.port)
        # check if connection success
        if hardware_data is False:
            return hardware_data

        self.hardware_version = unpack_from(
            '>' + str(len(hardware_data)) + 's', hardware_data)[0]
        logger.info(self.hardware_version)
        return True

    def publish_battery(self, loop):
        try:
            self.read_gen_data()
            self.read_cell_data()
            # self.log_battery_data()
            self.publish_battery_dbus()
        except:
            traceback.print_exc()
            loop.quit()

    def setup_vedbus(self, instance):

        self._dbusservice = VeDbusService("com.victronenergy.battery." +
                                          self.port[self.port.rfind('/') + 1:])
        logger.debug("%s /DeviceInstance = %d" %
                     ("com.victronenergy.battery." +
                      self.port[self.port.rfind('/') + 1:], instance))

        # Create the management objects, as specified in the ccgx dbus-api document
        self._dbusservice.add_path('/Mgmt/ProcessName', __file__)
        self._dbusservice.add_path('/Mgmt/ProcessVersion',
                                   'Python ' + platform.python_version())
        self._dbusservice.add_path('/Mgmt/Connection', 'Serial ' + self.port)

        # Create the mandatory objects
        self._dbusservice.add_path('/DeviceInstance', instance)
        self._dbusservice.add_path('/ProductId', 0x0)
        self._dbusservice.add_path('/ProductName', 'SerialBattery (LTT)')
        self._dbusservice.add_path('/FirmwareVersion', self.version)
        self._dbusservice.add_path('/HardwareVersion', self.hardware_version)
        self._dbusservice.add_path('/Connected', 1)
        # Create static battery info
        self._dbusservice.add_path('/Info/BatteryLowVoltage',
                                   self.min_battery_voltage,
                                   writeable=True)
        self._dbusservice.add_path('/Info/MaxChargeVoltage',
                                   self.max_battery_voltage,
                                   writeable=True)
        self._dbusservice.add_path('/Info/MaxChargeCurrent',
                                   MAX_BATTERY_CURRENT,
                                   writeable=True)
        self._dbusservice.add_path('/Info/MaxDischargeCurrent',
                                   MAX_BATTERY_DISCHARGE_CURRENT,
                                   writeable=True)
        self._dbusservice.add_path('/System/NrOfCellsPerBattery',
                                   self.cell_count,
                                   writeable=True)
        self._dbusservice.add_path('/System/NrOfModulesOnline',
                                   1,
                                   writeable=True)
        self._dbusservice.add_path('/System/NrOfModulesOffline',
                                   None,
                                   writeable=True)
        self._dbusservice.add_path('/System/NrOfModulesBlockingCharge',
                                   None,
                                   writeable=True)
        self._dbusservice.add_path('/System/NrOfModulesBlockingDischarge',
                                   None,
                                   writeable=True)
        # Not used at this stage
        # self._dbusservice.add_path('/System/MinTemperatureCellId', None, writeable=True)
        # self._dbusservice.add_path('/System/MaxTemperatureCellId', None, writeable=True)
        self._dbusservice.add_path('/Capacity', self.capacity, writeable=True)
        # Create SOC, DC and System items
        self._dbusservice.add_path('/Soc', None, writeable=True)
        self._dbusservice.add_path('/Dc/0/Voltage', None, writeable=True)
        self._dbusservice.add_path('/Dc/0/Current', None, writeable=True)
        self._dbusservice.add_path('/Dc/0/Power', None, writeable=True)
        self._dbusservice.add_path('/Dc/0/Temperature', 21.0, writeable=True)
        # Create battery extras
        self._dbusservice.add_path('/System/MinCellTemperature',
                                   None,
                                   writeable=True)
        self._dbusservice.add_path('/System/MaxCellTemperature',
                                   None,
                                   writeable=True)
        self._dbusservice.add_path('/System/MaxCellVoltage',
                                   0.0,
                                   writeable=True)
        self._dbusservice.add_path('/System/MaxVoltageCellId',
                                   '',
                                   writeable=True)
        self._dbusservice.add_path('/System/MinCellVoltage',
                                   0.0,
                                   writeable=True)
        self._dbusservice.add_path('/System/MinVoltageCellId',
                                   '',
                                   writeable=True)
        self._dbusservice.add_path('/History/ChargeCycles', 0, writeable=True)
        self._dbusservice.add_path('/Balancing', 0, writeable=True)
        self._dbusservice.add_path('/Io/AllowToCharge', 0, writeable=True)
        self._dbusservice.add_path('/Io/AllowToDischarge', 0, writeable=True)
        # Create the alarms
        self._dbusservice.add_path('/Alarms/LowVoltage', 0, writeable=True)
        self._dbusservice.add_path('/Alarms/HighVoltage', 0, writeable=True)
        self._dbusservice.add_path('/Alarms/LowSoc', 0, writeable=True)
        self._dbusservice.add_path('/Alarms/HighChargeCurrent',
                                   0,
                                   writeable=True)
        self._dbusservice.add_path('/Alarms/HighDischargeCurrent',
                                   0,
                                   writeable=True)
        self._dbusservice.add_path('/Alarms/CellImbalance', 0, writeable=True)
        self._dbusservice.add_path('/Alarms/InternalFailure',
                                   0,
                                   writeable=True)
        self._dbusservice.add_path('/Alarms/HighChargeTemperature',
                                   0,
                                   writeable=True)
        self._dbusservice.add_path('/Alarms/LowChargeTemperature',
                                   0,
                                   writeable=True)
        self._dbusservice.add_path('/Alarms/HighTemperature',
                                   0,
                                   writeable=True)
        self._dbusservice.add_path('/Alarms/LowTemperature', 0, writeable=True)
class AcDevice(object):
	def __init__(self, position):
		# Dictionary containing the AC Sensors per phase. This is the source of the data
		self._acSensors = {'L1': [], 'L2': [], 'L3': []}

		# Type and position (numbering is equal to numbering in VE.Bus Assistant):
		self._names = {0: 'PV Inverter on input 1', 1: 'PV Inverter on output', 2: 'PV Inverter on input 2'}
		self._name = position
		self._dbusService = None

	def __str__(self):
		return self._names[self._name] + ' containing ' + \
			str(len(self._acSensors['L1'])) + ' AC-sensors on L1, ' + \
			str(len(self._acSensors['L2'])) + ' AC-sensors on L2, ' + \
			str(len(self._acSensors['L3'])) + ' AC-sensors on L3'

	# add_ac_sensor function is called to add dbusitems that represent power for a certain phase
	def add_ac_sensor(self, acsensor, phase):
		acsensor.set_eventcallback(self.value_has_changed)
		self._acSensors[phase].append(acsensor)

	def value_has_changed(self, dbusName, dbusObjectPath, changes):
		# decouple, and process update in the mainloop
		idle_add(self.update_values)

	# iterates through all sensor dbusItems, and recalculates our values. Adds objects to exported
	# dbus values if necessary.
	def update_values(self):

		if not self._dbusService:
			return

		totals = {'I': 0, 'P': 0, 'E': 0}

		for phase in ['L1', 'L2', 'L3']:
			pre = '/Ac/' + phase

			if len(self._acSensors[phase]) == 0:
				if (pre + '/Power') in self._dbusService:
					self._dbusService[pre + '/Power'] = None
					self._dbusService[pre + '/Energy/Forward'] = None
					self._dbusService[pre + '/Voltage'] = None
					self._dbusService[pre + '/Current'] = None
			else:
				phaseTotals = {'I': 0, 'P': 0, 'E': 0}
				for o in self._acSensors[phase]:
					phaseTotals['I'] += float(o['current'].get_value() or 0)
					phaseTotals['P'] += float(o['power'].get_value() or 0)
					phaseTotals['E'] += float(o['energycounter'].get_value() or 0)
					voltage = float(o['voltage'].get_value() or 0) # just take the last voltage

				if (pre + '/Power') not in self._dbusService:
					# This phase hasn't been added yet, adding it now

					self._dbusService.add_path(pre + '/Voltage', voltage, gettextcallback=self.gettextforV)
					self._dbusService.add_path(pre + '/Current', phaseTotals['I'], gettextcallback=self.gettextforA)
					self._dbusService.add_path(pre + '/Power', phaseTotals['P'], gettextcallback=self.gettextforW)
					self._dbusService.add_path(pre + '/Energy/Forward', phaseTotals['E'], gettextcallback=self.gettextforkWh)
				else:
					self._dbusService[pre + '/Voltage'] = voltage
					self._dbusService[pre + '/Current'] = phaseTotals['I']
					self._dbusService[pre + '/Power'] = phaseTotals['P']
					self._dbusService[pre + '/Energy/Forward'] = phaseTotals['E']

				totals['I'] += phaseTotals['I']
				totals['P'] += phaseTotals['P']
				totals['E'] += phaseTotals['E']

				#logging.debug(
				#	self._names[self._name] + '. Phase ' + phase + ' recalculated: %0.2fV,  %0.2fA, %0.4fW and %0.4f kWh' %
				#	(voltage,  phaseTotals['I'],  phaseTotals['P'],  phaseTotals['E']))

			# TODO, why doesn't the application crash on an exception? I want it to crash, also on exceptions
			# in threads.
			#raise Exception ("exit Exception!")

		if '/Ac/Current' not in self._dbusService:
			self._dbusService.add_path('/Ac/Current', totals['I'], gettextcallback=self.gettextforA)
			self._dbusService.add_path('/Ac/Power', totals['P'], gettextcallback=self.gettextforW)
			self._dbusService.add_path('/Ac/Energy/Forward', totals['E'], gettextcallback=self.gettextforkWh)
		else:
			self._dbusService['/Ac/Current'] = totals['I']
			self._dbusService['/Ac/Power'] =  totals['P']
			self._dbusService['/Ac/Energy/Forward'] = totals['E']

	# Call this function after you have added AC sensors to this class. Code will check if we have any,
	# and if yes, add ourselves to the dbus.
	def update_dbus_service(self):
		if (len(self._acSensors['L1']) > 0 or len(self._acSensors['L2']) > 0 or
			len(self._acSensors['L3']) > 0):

			if self._dbusService is None:

				pf = {0: 'input1', 1: 'output', 2: 'input2'}
				self._dbusService = VeDbusService('com.victronenergy.pvinverter.vebusacsensor_' + pf[self._name])
				#, self._dbusConn)

				self._dbusService.add_path('/Position', self._name, description=None, gettextcallback=self.gettextforposition)

				# Create the mandatory objects, as per victron dbus api document
				self._dbusService.add_path('/Mgmt/ProcessName', __file__)
				self._dbusService.add_path('/Mgmt/ProcessVersion', softwareVersion)
				self._dbusService.add_path('/Mgmt/Connection', 'AC Sensor on VE.Bus device')
				self._dbusService.add_path('/DeviceInstance', int(self._name) + 10)
				self._dbusService.add_path('/ProductId', 0xA141)
				self._dbusService.add_path('/ProductName', self._names[self._name])
				self._dbusService.add_path('/Connected', 1)

				logging.info('Added to D-Bus: ' + self.__str__())

			self.update_values()

	# Apparantly some service from which we imported AC Sensors has gone offline. Remove those sensors
	# from our repo.
	def remove_ac_sensors_imported_from(self, serviceBeingRemoved):
		logging.debug(
			'%s: Checking if we have sensors from %s, and removing them' %
			(self._names[self._name], serviceBeingRemoved))

		for phase in ['L1', 'L2', 'L3']:
			self._acSensors[phase][:] = [x for x in self._acSensors[phase] if not x['power'].serviceName == serviceBeingRemoved]

		if self._dbusService is None:
			return

		if (not self._acSensors['L1'] and not self._acSensors['L2'] and
			not self._acSensors['L3']):
			# No sensors left for us, clean up

			self._dbusService.__del__()  # explicitly call __del__(), instead of waiting for gc
			self._dbusService = None

			logging.info("Removed from D-Bus: %s" % self.__str__())
		else:
			# Still some sensors left for us, update values
			self.update_values()

	def gettextforkWh(self, path, value):
		return ("%.3FkWh" % (float(value) / 1000.0))

	def gettextforW(self, path, value):
		return ("%.0FW" % (float(value)))

	def gettextforV(self, path, value):
		return ("%.0FV" % (float(value)))

	def gettextforA(self, path, value):
		return ("%.0FA" % (float(value)))

	def gettextforposition(self, path, value):
		return self._names[value]
    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
Example #43
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')
Example #44
0
def new_service(base, type, physical, logical, id, instance, settingId=False):
    self = VeDbusService("{}.{}.{}_id{:02d}".format(base, type, physical, id),
                         dbusconnection())
    # physical is the physical connection
    # logical is the logical connection to allign with the numbering of the console display
    # Create the management objects, as specified in the ccgx dbus-api document
    self.add_path('/Mgmt/ProcessName', __file__)
    self.add_path(
        '/Mgmt/ProcessVersion',
        'Unkown version, and running on Python ' + platform.python_version())
    self.add_path('/Mgmt/Connection', logical)

    # Create the mandatory objects, note these may need to be customised after object creation
    self.add_path('/DeviceInstance', instance)
    self.add_path('/ProductId', 0)
    self.add_path('/ProductName', '')
    self.add_path('/FirmwareVersion', 0)
    self.add_path('/HardwareVersion', 0)
    self.add_path('/Connected',
                  0)  # Mark devices as disconnected until they are confirmed

    # Create device type specific objects set values to empty until connected
    if settingId:
        setting = "/Settings/" + type.capitalize() + "/" + str(settingId)
    else:
        print("no setting required")
        setting = ""
    if type == 'temperature':
        self.add_path('/Temperature', [])
        self.add_path('/Status', 0)
        if settingId:
            addSetting(setting, '/TemperatureType', self)
            addSetting(setting, '/CustomName', self)
        self.add_path(
            '/TemperatureType',
            0,
            writeable=True,
            onchangecallback=lambda x, y: handle_changed_value(setting, x, y))
        self.add_path(
            '/CustomName',
            '',
            writeable=True,
            onchangecallback=lambda x, y: handle_changed_value(setting, x, y))
        self.add_path('/Function', 1, writeable=True)
        if 'adc' in physical:
            if settingId:
                addSetting(setting, '/Scale', self)
                addSetting(setting, '/Offset', self)
            self.add_path('/Scale',
                          1.0,
                          writeable=True,
                          onchangecallback=lambda x, y: handle_changed_value(
                              setting, x, y))
            self.add_path('/Offset',
                          0,
                          writeable=True,
                          onchangecallback=lambda x, y: handle_changed_value(
                              setting, x, y))
    if type == 'humidity':
        self.add_path('/Humidity', [])
        self.add_path('/Status', 0)

    return self
class DbusDummyService:
  def __init__(self, servicename, deviceinstance, paths, productname='Fronius Smart Meter', connection='Fronius Smart Meter service'):
    self._dbusservice = VeDbusService(servicename)
    self._paths = paths

    logging.debug("%s /DeviceInstance = %d" % (servicename, deviceinstance))

    # Create the management objects, as specified in the ccgx dbus-api document
    self._dbusservice.add_path('/Mgmt/ProcessName', __file__)
    self._dbusservice.add_path('/Mgmt/ProcessVersion', 'Unkown version, and running on Python ' + platform.python_version())
    self._dbusservice.add_path('/Mgmt/Connection', connection)

    # Create the mandatory objects
    self._dbusservice.add_path('/DeviceInstance', deviceinstance)
    self._dbusservice.add_path('/ProductId', 16) # value used in ac_sensor_bridge.cpp of dbus-cgwacs
    self._dbusservice.add_path('/ProductName', productname)
    self._dbusservice.add_path('/FirmwareVersion', 0.1)
    self._dbusservice.add_path('/HardwareVersion', 0)
    self._dbusservice.add_path('/Connected', 1)

    for path, settings in self._paths.iteritems():
      self._dbusservice.add_path(
        path, settings['initial'], writeable=True, onchangecallback=self._handlechangedvalue)

    gobject.timeout_add(200, self._update) # pause 200ms before the next request

  def _update(self):
    URL = "http://10.194.65.143/solar_api/v1/GetMeterRealtimeData.cgi?Scope=Device&DeviceId=0&DataCollection=MeterRealtimeData"
    meter_r = requests.get(url = URL)
    meter_data = meter_r.json() 
    MeterConsumption = meter_data['Body']['Data']['PowerReal_P_Sum']
    self._dbusservice['/Ac/Power'] = MeterConsumption # positive: consumption, negative: feed into grid
    self._dbusservice['/Ac/L1/Voltage'] = meter_data['Body']['Data']['Voltage_AC_Phase_1']
    self._dbusservice['/Ac/L2/Voltage'] = meter_data['Body']['Data']['Voltage_AC_Phase_2']
    self._dbusservice['/Ac/L3/Voltage'] = meter_data['Body']['Data']['Voltage_AC_Phase_3']
    self._dbusservice['/Ac/L1/Current'] = meter_data['Body']['Data']['Current_AC_Phase_1']
    self._dbusservice['/Ac/L2/Current'] = meter_data['Body']['Data']['Current_AC_Phase_2']
    self._dbusservice['/Ac/L3/Current'] = meter_data['Body']['Data']['Current_AC_Phase_3']
    self._dbusservice['/Ac/L1/Power'] = meter_data['Body']['Data']['PowerReal_P_Phase_1']
    self._dbusservice['/Ac/L2/Power'] = meter_data['Body']['Data']['PowerReal_P_Phase_2']
    self._dbusservice['/Ac/L3/Power'] = meter_data['Body']['Data']['PowerReal_P_Phase_3']
    self._dbusservice['/Ac/Energy/Forward'] = meter_data['Body']['Data']['EnergyReal_WAC_Sum_Consumed']
    self._dbusservice['/Ac/Energy/Reverse'] = meter_data['Body']['Data']['EnergyReal_WAC_Sum_Produced']
    logging.info("House Consumption: %s" % (MeterConsumption))
    return True

  def _handlechangedvalue(self, path, value):
    logging.debug("someone else updated %s to %s" % (path, value))
    return True # accept the change
parser.add_argument("-d", "--debug", help="set logging level to debug",
				action="store_true")

args = parser.parse_args()

# Init logging
logging.basicConfig(level=(logging.DEBUG if args.debug else logging.INFO))
logging.info(__file__ + " is starting up")
logLevel = {0: 'NOTSET', 10: 'DEBUG', 20: 'INFO', 30: 'WARNING', 40: 'ERROR'}
logging.info('Loglevel set to ' + logLevel[logging.getLogger().getEffectiveLevel()])

# Have a mainloop, so we can send/receive asynchronous calls to and from dbus
DBusGMainLoop(set_as_default=True)

dbusservice = VeDbusService(args.name)

logging.info("using device instance %s" % args.deviceinstance)

# Create the management objects, as specified in the ccgx dbus-api document
dbusservice.add_path('/Management/ProcessName', __file__)
dbusservice.add_path('/Management/ProcessVersion', 'Unkown version, and running on Python ' + platform.python_version())
dbusservice.add_path('/Management/Connection', 'dummy data')

# Create the mandatory objects
dbusservice.add_path('/DeviceInstance', args.deviceinstance)
dbusservice.add_path('/ProductId', 0)
dbusservice.add_path('/ProductName', 'Dummy battery')
dbusservice.add_path('/FirmwareVersion', 0)
dbusservice.add_path('/HardwareVersion', 0)
dbusservice.add_path('/Connected', 1)
    def __init__(self, dev, connection, instance, serial, product, firmwarev,
                 pversion):
        #VERSION = '0.1'

        print(__file__ + " starting up")
        #	instance = 50 + 0

        # Have a mainloop, so we can send/receive asynchronous calls to and from dbus
        #DBusGMainLoop(set_as_default=True)

        #Put ourselves on to the dbus
        self.dbusservice = VeDbusService('com.victronenergy.pvinverter.' + dev)

        # Most simple and short way to add an object with an initial value of 5.
        #	dbusservice.add_path('/Ac/Power', value=1000, description='Total power', writeable=False)
        #	dbusservice.add_path('/DeviceType', value=1000, description='Total power', writeable=False)
        # Add objects required by ve-api
        self.dbusservice.add_path('/Mgmt/ProcessName', __file__)
        self.dbusservice.add_path('/Mgmt/ProcessVersion', pversion)
        self.dbusservice.add_path('/Mgmt/Connection', connection)  # todo
        self.dbusservice.add_path('/DeviceInstance', instance)
        self.dbusservice.add_path('/ProductId', 0xFFFF)  # 0xB012 ?
        self.dbusservice.add_path('/ProductName', product)
        #self.dbusservice.add_path('/CustomName', "PLC Mec meter")
        self.dbusservice.add_path('/FirmwareVersion', firmwarev)
        self.dbusservice.add_path('/Serial', serial)
        self.dbusservice.add_path('/Connected', 1, writeable=True)
        self.dbusservice.add_path('/ErrorCode', '(0) No Error')
        self.dbusservice.add_path('/Position', 0)

        _kwh = lambda p, v: (str(v) + 'KWh')
        _a = lambda p, v: (str(v) + 'A')
        _w = lambda p, v: (str(v) + 'W')
        _v = lambda p, v: (str(v) + 'V')
        _s = lambda p, v: (str(v) + 's')
        _x = lambda p, v: (str(v))

        self.dbusservice.add_path('/Ac/Energy/Forward',
                                  None,
                                  gettextcallback=_kwh)
        self.dbusservice.add_path('/Ac/L1/Current', None, gettextcallback=_a)
        self.dbusservice.add_path('/Ac/L1/Energy/Forward',
                                  None,
                                  gettextcallback=_kwh)
        self.dbusservice.add_path('/Ac/L1/Power', None, gettextcallback=_w)
        self.dbusservice.add_path('/Ac/L1/Voltage', None, gettextcallback=_v)
        self.dbusservice.add_path('/Ac/L2/Current', None, gettextcallback=_a)
        self.dbusservice.add_path('/Ac/L2/Energy/Forward',
                                  None,
                                  gettextcallback=_kwh)
        self.dbusservice.add_path('/Ac/L2/Power', None, gettextcallback=_w)
        self.dbusservice.add_path('/Ac/L2/Voltage', None, gettextcallback=_v)
        self.dbusservice.add_path('/Ac/L3/Current', None, gettextcallback=_a)
        self.dbusservice.add_path('/Ac/L3/Energy/Forward',
                                  None,
                                  gettextcallback=_kwh)
        self.dbusservice.add_path('/Ac/L3/Power', None, gettextcallback=_w)
        self.dbusservice.add_path('/Ac/L3/Voltage', None, gettextcallback=_v)
        self.dbusservice.add_path('/Ac/Power', None, gettextcallback=_w)
        self.dbusservice.add_path('/Ac/Current', None, gettextcallback=_a)
        self.dbusservice.add_path('/Ac/Voltage', None, gettextcallback=_v)

        self.dbusservice.add_path('/stats/connection_ok',
                                  0,
                                  gettextcallback=_x,
                                  writeable=True)
        self.dbusservice.add_path('/stats/connection_error',
                                  0,
                                  gettextcallback=_x,
                                  writeable=True)
        self.dbusservice.add_path('/stats/parse_error',
                                  0,
                                  gettextcallback=_x,
                                  writeable=True)
        self.dbusservice.add_path('/stats/repeated_values',
                                  0,
                                  gettextcallback=_x,
                                  writeable=True)
        self.dbusservice.add_path('/stats/last_connection_errors',
                                  0,
                                  gettextcallback=_x,
                                  writeable=True)
        self.dbusservice.add_path('/stats/last_repeated_values',
                                  0,
                                  gettextcallback=_x,
                                  writeable=True)
        self.dbusservice.add_path('/stats/reconnect', 0, gettextcallback=_x)
        self.dbusservice.add_path('/Mgmt/intervall',
                                  1,
                                  gettextcallback=_s,
                                  writeable=True)