question

mfriedel avatar image
mfriedel asked

Multiple Dbus service instances in a single driver

I got my MPPT driver working for a single instance. However when I try to register multiple instances from the same process it fails with an error.


KeyError: "Can't register the object-path handler for '/': there is already a handler"


Is there an easy way to register multiple services ?



Venus OS
1 comment
2 |3000

Up to 8 attachments (including images) can be used with a maximum of 190.8 MiB each and 286.6 MiB total.

laurenceh avatar image laurenceh commented Ā·

Yes, easy to do, mvader wrote an article about it but I canā€™t find a link to it, you need to pass in a new dbus object to each new instance you create. Iā€™ll post my working code ( 3 temperature services and 2 i2c services all in one process ) over the weekend as a answer to this.

0 Likes 0 Ā·
3 Answers
laurenceh avatar image
laurenceh answered Ā·

Here you go.

Here is my code which implements multiple d-bus services in a single python programme:

#!/usr/bin/env python

# takes data from the i2c and adc channels (which are not used by venus) and publishes the data on the bus.

# If edditing then use
# svc -d /service/dbus-i2c and
# svc -u /service/dbus-i2c
# to stop and restart the service

# probably not all these required some are legacy and no longer used.
from dbus.mainloop.glib import DBusGMainLoop
import gobject
from gobject import idle_add
import dbus
import dbus.service
import inspect
import platform
from threading import Timer
import argparse
import logging
import sys
import os
# Import i2c interface driver, this is a modified library stored in the same directory as this file
from i2c import AM2320 as AM2320 

# our own packages
#sys.path.insert(1, os.path.join(os.path.dirname(__file__), '../ext/velib_python'))
sys.path.insert(1, os.path.join(os.path.dirname(__file__), '/opt/victronenergy/dbus-modem'))
from vedbus import VeDbusService

# Again not all of these needed this is just duplicating the Victron code.
class SystemBus(dbus.bus.BusConnection):
    def __new__(cls):
        return dbus.bus.BusConnection.__new__(cls, dbus.bus.BusConnection.TYPE_SYSTEM)

class SessionBus(dbus.bus.BusConnection):
    def __new__(cls):
        return dbus.bus.BusConnection.__new__(cls, dbus.bus.BusConnection.TYPE_SESSION)

def dbusconnection():
    return SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else SystemBus()

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

# Here is the bit you need to create multiple new services - try as much as possible timplement the Victron Dbus API requirements.
def new_service(base, type, physical, logical, id, instance):
    self =  VeDbusService("{}.{}.{}_id{:02d}".format(base, type, physical,  id), dbusconnection())
    # physical is the physical connection
    # logical is the logical connection to align 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
    if type == 'temperature':
        self.add_path('/Temperature', 0)
        self.add_path('/Status', 0)
        self.add_path('/TemperatureType', 0, writeable=True)
    if type == 'humidity':
        self.add_path('/Humidity', 0)
        self.add_path('/Status', 0)

    return self

dbusservice = {} # Dictonary to hold the multiple services

base = 'com.victronenergy'

# service defined by (base*, type*, connection*, id*, instance):
# * items are include in service name
# Create all the dbus-services we want
dbusservice['i2c-temp']     = new_service(base, 'temperature', 'i2c',       'Temperature sensor input 6',  0, 25)
dbusservice['i2c-humidity'] = new_service(base, 'humidity',    'i2c',       'Humidity sensor input 1'   ,  0, 25)
dbusservice['adc-temp0']    = new_service(base, 'temperature', 'RPi_adc0', 'Temperature sensor input 3',  0, 26)
dbusservice['adc-temp1']    = new_service(base, 'temperature', 'RPi_adc1', 'Temperature sensor input 4',  1, 27)
dbusservice['adc-temp7']    = new_service(base, 'temperature', 'RPi_adc7', 'Temperature sensor input 5',  2, 28)
# Tidy up custom or missing items
dbusservice['i2c-temp']    ['/ProductName']     = 'Encased i2c AM2315'
dbusservice['i2c-temp']    ['/Mgmt/Connection'] = 'i2c device 1'  # override connection description
dbusservice['i2c-humidity']['/ProductName']     = 'Encased i2c AM2315'
dbusservice['i2c-humidity']['/Mgmt/Connection'] = 'i2c device 1'  # override connection description
dbusservice['adc-temp0']   ['/ProductName']     = 'Custard Pi-3 8x12bit adc'
dbusservice['adc-temp1']   ['/ProductName']     = 'Custard Pi-3 8x12bit adc'
dbusservice['adc-temp7']   ['/ProductName']     = 'Custard Pi-3 8x12bit adc'

# Everything done so just set a time to run an update function to update the data values every 10 seconds.

gobject.timeout_add(10000, update)


print 'Connected to dbus, and switching over to gobject.MainLoop() (= event based)'
mainloop = gobject.MainLoop()
mainloop.run()

Note: there is an interface on the VeDbusService object that could be used to create the mandatory interface objects but it is easier to see what is going on in this code.

Once that is all done you just set the values you want in the update routine.

AS an example here is a snippet where I am setting the temperature values read from the adc.

            # Convert raw value to temperature
            value = round(2.1+(value-2015)*0.135*scale,1)
            if value > 140:
                dbusservice['adc-temp'+str(channel)]['/Status'] = 1
                dbusservice['adc-temp'+str(channel)]['/Temperature'] = ''
            elif value < -100:
                dbusservice['adc-temp'+str(channel)]['/Status'] = 2
                dbusservice['adc-temp'+str(channel)]['/Temperature'] = ''
            else:
                dbusservice['adc-temp'+str(channel)]['/Status'] = 0
                dbusservice['adc-temp'+str(channel)]['/Temperature'] = value


2 |3000

Up to 8 attachments (including images) can be used with a maximum of 190.8 MiB each and 286.6 MiB total.

mfriedel avatar image
mfriedel answered Ā·

Sorry, I was a little tired when I wrote this. I am using python to write the driver.

2 |3000

Up to 8 attachments (including images) can be used with a maximum of 190.8 MiB each and 286.6 MiB total.

mfriedel avatar image
mfriedel answered Ā·

So the answer is to use the private=True. Argument when requesting the bus connction

2 |3000

Up to 8 attachments (including images) can be used with a maximum of 190.8 MiB each and 286.6 MiB total.

Related Resources