├── .gitignore ├── dbus-i2c ├── service │ ├── supervise │ │ ├── lock │ │ └── status │ ├── log │ │ ├── supervise │ │ │ ├── lock │ │ │ └── status │ │ └── run │ └── run ├── i2c.pyc ├── c2.sh ├── check-i2c.sh ├── start-i2c.sh ├── i2c.py └── dbus-i2c.py ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /dbus-i2c/service/supervise/lock: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dbus-i2c/service/log/supervise/lock: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dbus-i2c/i2c.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LHardwick-git/Victron-Service/HEAD/dbus-i2c/i2c.pyc -------------------------------------------------------------------------------- /dbus-i2c/service/log/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec 2>&1 3 | exec multilog t s99999 n8 /var/log/dbus-i2c 4 | -------------------------------------------------------------------------------- /dbus-i2c/service/supervise/status: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LHardwick-git/Victron-Service/HEAD/dbus-i2c/service/supervise/status -------------------------------------------------------------------------------- /dbus-i2c/service/log/supervise/status: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LHardwick-git/Victron-Service/HEAD/dbus-i2c/service/log/supervise/status -------------------------------------------------------------------------------- /dbus-i2c/service/run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | exec 2>&1 3 | exec softlimit -d 100000000 -s 1000000 -a 100000000 /opt/victronenergy/dbus-i2c/dbus-i2c.py 4 | -------------------------------------------------------------------------------- /dbus-i2c/c2.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | if [ ! -e /dev/i2c-* ]; then 4 | svc -d /service/dbus-i2c 5 | exit 6 | else 7 | echo "device driver found" 8 | fi 9 | 10 | -------------------------------------------------------------------------------- /dbus-i2c/check-i2c.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | if ! pgrep -f "dbus-i2c.py" >/dev/null; then 4 | echo "process not found" 5 | else 6 | echo "process running" 7 | fi 8 | -------------------------------------------------------------------------------- /dbus-i2c/start-i2c.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | if [ ! -e /dev/i2c-* ]; then 4 | svc -d /service/dbus-i2c 5 | exit 6 | fi 7 | 8 | exec $(dirname $0)/dbus-i2c.py 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, LHardwick-git 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /dbus-i2c/i2c.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Thie file can be extended to add class drivers for additional devices as they are implemented and tested 4 | # https://github.com/Gozem/am2320/blob/master/am2320.py 5 | # file has been updated to catch and report errors (rather than raising and exception 6 | # use i2cdetect -y 1 on venus Rpi to check the device is found at address 5c 7 | 8 | import posix 9 | from fcntl import ioctl 10 | import time 11 | 12 | class AM2320: 13 | I2C_ADDR = 0x5c 14 | I2C_SLAVE = 0x0703 15 | 16 | def __init__(self, i2cbus = 1): 17 | self._i2cbus = i2cbus 18 | 19 | @staticmethod 20 | def _calc_crc16(data): 21 | crc = 0xFFFF 22 | for x in data: 23 | crc = crc ^ x 24 | for bit in range(0, 8): 25 | if (crc & 0x0001) == 0x0001: 26 | crc >>= 1 27 | crc ^= 0xA001 28 | else: 29 | crc >>= 1 30 | return crc 31 | 32 | @staticmethod 33 | def _combine_bytes(msb, lsb): 34 | return msb << 8 | lsb 35 | 36 | 37 | def readSensor(self): 38 | fd = posix.open("/dev/i2c-%d" % self._i2cbus, posix.O_RDWR) 39 | 40 | ioctl(fd, self.I2C_SLAVE, self.I2C_ADDR) 41 | 42 | # wake AM2320 up, goes to sleep to not warm up and affect the humidity sensor 43 | # This write will fail as AM2320 won't ACK this write 44 | try: 45 | posix.write(fd, b'\0x00') 46 | except: 47 | pass 48 | time.sleep(0.001) #Wait at least 0.8ms, at most 3ms 49 | 50 | # write at addr 0x03, start reg = 0x00, num regs = 0x04 */ 51 | try: 52 | posix.write(fd, b'\x03\x00\x04') 53 | except: 54 | posix.close(fd) 55 | return (0,0,1,"Device did not acknowledge request") 56 | time.sleep(0.0016) #Wait at least 1.5ms for result 57 | 58 | # Read out 8 bytes of result data 59 | # Byte 0: Should be Modbus function code 0x03 60 | # Byte 1: Should be number of registers to read (0x04) 61 | # Byte 2: Humidity msb 62 | # Byte 3: Humidity lsb 63 | # Byte 4: Temperature msb 64 | # Byte 5: Temperature lsb 65 | # Byte 6: CRC lsb byte 66 | # Byte 7: CRC msb byte 67 | data = bytearray(posix.read(fd, 8)) 68 | posix.close(fd) 69 | 70 | # Check data[0] and data[1] 71 | if data[0] != 0x03 or data[1] != 0x04: 72 | return (0,0,4,"First two read bytes are a mismatch") 73 | # raise Exception("First two read bytes are a mismatch") 74 | 75 | # CRC check 76 | if self._calc_crc16(data[0:6]) != self._combine_bytes(data[7], data[6]): 77 | return (0,0,4,"CRC failed") 78 | # raise Exception("CRC failed") 79 | 80 | # Temperature resolution is 16Bit, 81 | # temperature highest bit (Bit15) is equal to 1 indicates a 82 | # negative temperature, the temperature highest bit (Bit15) 83 | # is equal to 0 indicates a positive temperature; 84 | # temperature in addition to the most significant bit (Bit14 ~ Bit0) 85 | # indicates the temperature sensor string value. 86 | # Temperature sensor value is a string of 10 times the 87 | # actual temperature value. 88 | temp = self._combine_bytes(data[4], data[5]) 89 | if temp & 0x8000: 90 | temp = -(temp & 0x7FFF) 91 | temp /= 10.0 92 | 93 | humi = self._combine_bytes(data[2], data[3]) / 10.0 94 | 95 | return (temp, humi, 0,'') 96 | 97 | #am2320 = AM2320(1) 98 | #(t,h) = am2320.readSensor() 99 | #print 'temperature', t 100 | #print 'humidiy', h, ' %' 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Victron-Service 2 | Temperature and Humidity d-bus services for Victron GX 3 | Oh! and also now reading the Raspberry Pi CPU temperature which is why I was asked to post this ! 4 | This has been updated for Venus 2.8x and Python 3. 5 | 6 | Now with added 1 wire support contributed by Albertbm however read the community 7 | Notes here regarding setting up 1-wire 8 | https://community.victronenergy.com/questions/58792/raspberry-pi-3b-heat-temperature.html 9 | (Look for post from olafd Feb 11 2022) 10 | 11 | This is a service to publish temperature type data onto the DBus of VenusOs running on a Victron GX device. 12 | Note: Currently this will not display the CPU temperature on Venus GX, only on RPi. 13 | 14 | I develop on a Raspberry Pi 3B+, this service was last tested on VenusOS 2.87 in July 2022. 15 | I use these service on my Narrowboat Lady's Smock, I am based in the UK. 16 | 17 | If all goes well with the install when youstart up your GX device the service will start. 18 | The data is published with a type of "Temperature" and will be available: 19 | 1) On the VenusOS DBus API 20 | 2) On the GX console 21 | 3) On the Victron VRM - if you have this configured for you installation 22 | 23 | Note, only services of path type "Temperature" will be displayed on the console and VRM 24 | If you modify the service to pubish data as a path that is of a different type 25 | it will only be available via the DBus and will not appear on the console or VRM. 26 | 27 | INSTRUCTIONS 28 | 1) Download the GitHub repo, probably by hitting the green (V Code) button on GITHub 29 | 30 | 2) Copy the dbus-i2c directory onto the VenusOS filessystem as /opt/victronenergy/dbus-i2c 31 | cp -r /dbus-i2c /opt/victronenergy/dbus-i2c 32 | 33 | 3) ## create a symlink from /service/dbus-i2c to /opt/victronenergy/dbus-i2c/service 34 | ## ln -s /opt/victronenergy/dbus-i2c/service /service/dbus-i2c 35 | For Venus OS 2.8 (using Python 3), this now needs to be. 36 | 37 | mkdir /opt/victronenergy/service/dbus-i2c 38 | cp -r /opt/victronenergy/dbus-i2c/service/* /opt/victronenergy/service/dbus-i2c/ 39 | 40 | 4) Set execute on the following files (Sadly storing things on github does not preserve execute bits) 41 | dbus-i2c/i2c.py 42 | dbus-i2c/start-i2c.sh 43 | dbus-i2c/check-i2c.sh - this is just a simple file you can use to check if the service is running 44 | dbus-i2c/service/run 45 | /service/dbus-i2c 46 | 47 | The command is 48 | chmod a+x 49 | 50 | So do this for each of the files listed above. 51 | 52 | There is a good chance the service will start, if not reboot your VenusOS device 53 | You can check in the file /var/log/dbus-i2c to see what is happening as the service starts up 54 | 55 | 5) Go through the file dbus-12c.py and comment out (or uncomment) the setting and creation of each service. 56 | (OK this could now be a single config section in a future release of the services to run as we have so many.) 57 | 58 | For now look here and only enable the services you want. 59 | 60 | # So the only service left running is the Raspberry pi CPU temperature. 61 | # 62 | # update_i2c() 63 | # update_adc() 64 | update_rpi() 65 | # update_W1() 66 | return True 67 | 68 | ALSO below this line: 69 | # I have commented out the bits that will make new services for i2C and ADC services here 70 | # If you want to re-enable these you need to uncomment the right lines 71 | 72 | Look for and comment out the 1-wire (unless you have one) 73 | # dbusservice['W1-temp'] = new_service(base, 'temperature', 'Wire', '1Wire', 0, 28, 5) 74 | and here 75 | # dbusservice['W1-temp'] ['/ProductName'] = '1Wire Sensor' 76 | 77 | 6) Currently this will not survive an OS upgrade and will need to be re-installed. 78 | There are frameworks from other contributors that csn be used to make sure this 79 | PAckage will remain (or be re-installed) after an upgrade. 80 | 81 | NOTES 82 | Why is it call dbus-i2c ? = Well it started as a service to add i2c devices 83 | - you will see I have left the code in just commented it out. 84 | 85 | What's the adc stuff? = Victron GX devices only use 5 of the 8 analogue interfaces available 86 | - again this is commented out, if you add an 8 channel adc device to a Raspberry pi you can use 87 | the extra 3 channels as additional temperature inputs (the VenusOS code will read it's usual 5 inputs) 88 | In fact there is no conflict as the Venus OS and the service would both quite happily read the 89 | same inputs - but you would end up with the same inputs twice in the User Interface. 90 | 91 | I added some code recently to enable all the setting of names and temperature offsets as this is supported in VenusOS 2.60. 92 | 93 | There is some other useful stuff (Which answers other questions I have seen on the Victron comunity) 94 | Such as how to have one service publish multiple types of data to the DBus from one file. 95 | 96 | Hope this all works for you 97 | 98 | -------------------------------------------------------------------------------- /dbus-i2c/dbus-i2c.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright (c) 2021 LHardwick-git 4 | # Licensed under the BSD 3-Clause license. See LICENSE file in the project root for full license information. 5 | # 6 | # takes data from the i2c and adc channels (which are not used by venus) and publishes the data on the bus. 7 | 8 | # If edditing then use 9 | # svc -d /service/dbus-i2c and 10 | # svc -u /service/dbus-i2c 11 | # to stop and restart the service 12 | 13 | from dbus.mainloop.glib import DBusGMainLoop 14 | import sys 15 | if sys.version_info.major == 2: 16 | import gobject 17 | from gobject import idle_add 18 | else: 19 | from gi.repository import GLib as gobject 20 | import dbus 21 | import dbus.service 22 | import inspect 23 | import platform 24 | from threading import Timer 25 | import argparse 26 | import logging 27 | import sys 28 | import os 29 | from pprint import pprint 30 | # from w1thermsensor import W1ThermSensor 31 | # Import i2c interface driver, this is a modified library stored in the same directory as this file 32 | from i2c import AM2320 as AM2320 33 | 34 | # our own packages 35 | sys.path.insert(1, os.path.join(os.path.dirname(__file__), '/opt/victronenergy/dbus-modem')) 36 | from vedbus import VeDbusService, VeDbusItemExport, VeDbusItemImport 37 | from settingsdevice import SettingsDevice # available in the velib_python repository 38 | 39 | 40 | dbusservice = None 41 | 42 | def update(): 43 | # Calls to update ADC and I2C interfaces have been commented out 44 | # The is in case someone runes this who does not know what they are doing 45 | # and does not have i2c devices and/or does not want or have the extra ADC channels. 46 | # I have left the code in in case you wanton enable them 47 | # 48 | # So the only service left running is the Raspberry pi CPU temperature. 49 | # 50 | # update_i2c() 51 | # update_adc() 52 | update_rpi() 53 | # update_W1() 54 | return True 55 | 56 | # update i2c interface values 57 | def update_i2c(): 58 | if not os.path.exists('/dev/i2c-1'): 59 | if dbusservice['i2c-humidity']['/Connected'] != 0: 60 | logging.info("i2c interface disconnected") 61 | dbusservice['i2c-humidity']['/Connected'] = 0 62 | dbusservice['i2c-temperature']['/Connected'] = 0 63 | logging.info("i2c bus not available") 64 | else: 65 | am2320 = AM2320(1) 66 | (t,h,e, report) = am2320.readSensor() 67 | # Returns temperature, humidity, error ststus, and text report 68 | if e != 0: 69 | logging.info("Error in i2c bus read, "+ report) 70 | dbusservice['i2c-humidity']['/Status'] = e 71 | dbusservice['i2c-temp']['/Status'] = e 72 | dbusservice['i2c-humidity']['/Humidity'] = [] 73 | dbusservice['i2c-temp']['/Temperature'] = [] 74 | else: 75 | if dbusservice['i2c-humidity']['/Connected'] != 1: 76 | logging.info("i2c bus device connected") 77 | dbusservice['i2c-humidity']['/Connected'] = 1 78 | dbusservice['i2c-temp']['/Connected'] = 1 79 | dbusservice['i2c-humidity']['/Status'] = 0 80 | dbusservice['i2c-temp']['/Status'] = 0 81 | logging.debug("values now are temperature %s, humidity %s" % (t, h)) 82 | dbusservice['i2c-humidity']['/Humidity'] = h 83 | dbusservice['i2c-temp']['/Temperature'] = t 84 | 85 | def update_adc(): 86 | # update adc interface values 87 | # scale is hard coded here but could be implemented as a /scale setting in the dbus object 88 | scale = 1 89 | 90 | # the device iio:device 0 is the device running all adc channels 91 | # there are repeated calls here to check the device exists to set the ststus for every channel 92 | # it is assumed there is little overhead in this repeated call to the system. 93 | 94 | for channel in [0, 1, 7]: 95 | if not os.path.exists('/sys/bus/iio/devices/iio:device0'): 96 | if dbusservice['adc-temp'+str(channel)]['/Connected'] != 0: 97 | logging.info("adc interface disconnected") 98 | dbusservice['adc-temp'+str(channel)]['/Connected'] = 0 99 | else: 100 | if dbusservice['adc-temp'+str(channel)]['/Connected'] != 1: 101 | logging.info("adc interface channel " + str(channel) + " connected") 102 | dbusservice['adc-temp'+str(channel)]['/Connected'] = 1 103 | fd = open('/sys/bus/iio/devices/iio:device0/in_voltage'+str(channel)+'_raw','r') 104 | 105 | value = 0 106 | for loop in range (0,10): 107 | value += int(fd.read()) 108 | fd.seek(0) 109 | fd.close 110 | value = value / 10 111 | value = dbusservice['adc-temp'+str(channel)]['/Offset']+round(2.1+(value-2015)*0.135*scale,1) 112 | # logging.info(" Temperature "+str(value)) 113 | # added stuff here for short circuit and disconnect status 114 | if value > 140: 115 | dbusservice['adc-temp'+str(channel)]['/Status'] = 1 116 | dbusservice['adc-temp'+str(channel)]['/Temperature'] = [] 117 | elif value < -100: 118 | dbusservice['adc-temp'+str(channel)]['/Status'] = 2 119 | dbusservice['adc-temp'+str(channel)]['/Temperature'] = [] 120 | else: 121 | dbusservice['adc-temp'+str(channel)]['/Status'] = 0 122 | dbusservice['adc-temp'+str(channel)]['/Temperature'] = value 123 | 124 | 125 | 126 | # update Pi CPU temperature 127 | def update_rpi(): 128 | if not os.path.exists('/sys/devices/virtual/thermal/thermal_zone0/temp'): 129 | if dbusservice['cpu-temp']['/Connected'] != 0: 130 | logging.info("cpu temperature interface disconnected") 131 | dbusservice['cpu-temp']['/Connected'] = 0 132 | else: 133 | if dbusservice['cpu-temp']['/Connected'] != 1: 134 | logging.info("cpu temperature interface connected") 135 | dbusservice['cpu-temp']['/Connected'] = 1 136 | fd = open('/sys/devices/virtual/thermal/thermal_zone0/temp','r') 137 | value = float(fd.read()) 138 | value = round(value / 1000.0, 1) 139 | dbusservice['cpu-temp']['/Temperature'] = value 140 | fd.close 141 | 142 | #update W1 temp 143 | def update_W1(): 144 | if not os.path.exists('/sys/bus/w1/devices'): 145 | if dbusservice['W1-temp']['/Connected'] != 0: 146 | logging.info("W1 temperature interface disconnected") 147 | dbusservice['W1-temp']['/Connected'] = 0 148 | else: 149 | if dbusservice['W1-temp']['/Connected'] != 1: 150 | logging.info("W1 temperature interface connected") 151 | dbusservice['W1-temp']['/Connected'] = 1 152 | sensor = W1ThermSensor() 153 | temperature_in_celsius = sensor.get_temperature() 154 | value = temperature_in_celsius 155 | dbusservice['W1-temp']['/Temperature'] = value 156 | 157 | # =========================== Start of settings interface ================ 158 | # The settings interface handles the persistent storage of changes to settings 159 | # This should probably be created as a new class extension to the settingDevice object 160 | # The complexity is because this python service handles temperature and humidity 161 | # Data for about 6 different service paths so we need different dBusObjects for each device 162 | # 163 | newSettings = {} # Used to gather new settings to create/check as each dBus object is created 164 | settingObjects = {} # Used to identify the dBus object and path for each setting 165 | # settingsObjects = {setting: [path,object],} 166 | # each setting is the complete string e.g. /Settings/Temperature/4/Scale 167 | 168 | settingDefaults = {'/Offset': [0, -10, 10], 169 | '/Scale' : [1.0, -5, 5], 170 | '/TemperatureType' : [0, 0, 3], 171 | '/CustomName' : ['', 0, 0]} 172 | 173 | # Values changed in the GUI need to be updated in the settings 174 | # Without this changes made through the GUI change the dBusObject but not the persistent setting 175 | # (as tested in venus OS 2.54 August 2020) 176 | def handle_changed_value(setting, path, value): 177 | global settings 178 | print("some value changed") 179 | # The callback to the handle value changes has been modified by using an anonymouse function (lambda) 180 | # the callback is declared each time a path is added see example here 181 | # self.add_path(path, 0, writeable=True, onchangecallback = lambda x,y: handle_changed_value(setting,x,y) ) 182 | logging.info(" ".join(("Storing change to setting", setting+path, str(value) )) ) 183 | settings[setting+path] = value 184 | return True 185 | 186 | # Changes made to settings need to be reflected in the GUI and in the running service 187 | def handle_changed_setting(setting, oldvalue, newvalue): 188 | logging.info('Setting changed, setting: %s, old: %s, new: %s' % (setting, oldvalue, newvalue)) 189 | [path, object] = settingObjects[setting] 190 | object[path] = newvalue 191 | return True 192 | 193 | # Add setting is called each time a new service path is created that needs a persistent setting 194 | # If the setting already exists the existing recored is unchanged 195 | # If the setting does not exist it is created when the serviceDevice object is created 196 | def addSetting(base, path, dBusObject): 197 | global settingObjects 198 | global newSettings 199 | global settingDefaults 200 | setting = base + path 201 | logging.info(" ".join(("Add setting", setting, str(settingDefaults[path]) )) ) 202 | settingObjects[setting] = [path, dBusObject] # Record the dBus Object and path for this setting 203 | newSettings[setting] = [setting] + settingDefaults[path] # Add the setting to the list to be created 204 | 205 | # initSettings is called when all the required settings have been added 206 | def initSettings(newSettings): 207 | global settings 208 | 209 | # settingsDevice is the library class that handles the reading and setting of persistent settings 210 | settings = SettingsDevice( 211 | bus=dbus.SystemBus() if (platform.machine() == 'armv7l') else dbus.SessionBus(), 212 | supportedSettings = newSettings, 213 | eventCallback = handle_changed_setting) 214 | 215 | # readSettings is called after init settings to read all the stored settings and 216 | # set the initial values of each of the service object paths 217 | # Note you can not read or set a setting if it has not be included in the newSettings 218 | # list passed to create the new settingsDevice class object 219 | 220 | def readSettings(list): 221 | global settings 222 | for setting in list: 223 | [path, object] = list[setting] 224 | logging.info(" ".join(("Retreived setting", setting, path, str(settings[setting])))) 225 | object[path] = settings[setting] 226 | 227 | 228 | # =========================== end of settings interface ====================== 229 | 230 | class SystemBus(dbus.bus.BusConnection): 231 | def __new__(cls): 232 | return dbus.bus.BusConnection.__new__(cls, dbus.bus.BusConnection.TYPE_SYSTEM) 233 | 234 | class SessionBus(dbus.bus.BusConnection): 235 | def __new__(cls): 236 | return dbus.bus.BusConnection.__new__(cls, dbus.bus.BusConnection.TYPE_SESSION) 237 | 238 | def dbusconnection(): 239 | return SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else SystemBus() 240 | 241 | 242 | # Argument parsing 243 | parser = argparse.ArgumentParser(description='dbusMonitor.py demo run') 244 | parser.add_argument("-n", "--name", help="the D-Bus service you want me to claim", type=str, default="com.victronenergy.i2c") 245 | parser.add_argument("-i", "--deviceinstance", help="the device instance you want me to be", type=str, default="0") 246 | parser.add_argument("-d", "--debug", help="set logging level to debug", action="store_true") 247 | args = parser.parse_args() 248 | 249 | #args.debug = True 250 | 251 | # Init logging 252 | logging.basicConfig(level=(logging.DEBUG if args.debug else logging.INFO)) 253 | logging.info(__file__ + " is starting up") 254 | logLevel = {0: 'NOTSET', 10: 'DEBUG', 20: 'INFO', 30: 'WARNING', 40: 'ERROR'} 255 | logging.info('Loglevel set to ' + logLevel[logging.getLogger().getEffectiveLevel()]) 256 | 257 | # Have a mainloop, so we can send/receive asynchronous calls to and from dbus 258 | DBusGMainLoop(set_as_default=True) 259 | 260 | def new_service(base, type, physical, logical, id, instance, settingId = False): 261 | self = VeDbusService("{}.{}.{}_id{:02d}".format(base, type, physical, id), dbusconnection()) 262 | # physical is the physical connection 263 | # logical is the logical connection to allign with the numbering of the console display 264 | # Create the management objects, as specified in the ccgx dbus-api document 265 | self.add_path('/Mgmt/ProcessName', __file__) 266 | self.add_path('/Mgmt/ProcessVersion', 'Unkown version, and running on Python ' + platform.python_version()) 267 | self.add_path('/Mgmt/Connection', logical) 268 | 269 | # Create the mandatory objects, note these may need to be customised after object creation 270 | self.add_path('/DeviceInstance', instance) 271 | self.add_path('/ProductId', 0) 272 | self.add_path('/ProductName', '') 273 | self.add_path('/FirmwareVersion', 0) 274 | self.add_path('/HardwareVersion', 0) 275 | self.add_path('/Connected', 0) # Mark devices as disconnected until they are confirmed 276 | 277 | # Create device type specific objects set values to empty until connected 278 | if settingId : 279 | setting = "/Settings/" + type.capitalize() + "/" + str(settingId) 280 | else: 281 | print("no setting required") 282 | setting = "" 283 | if type == 'temperature': 284 | self.add_path('/Temperature', []) 285 | self.add_path('/Status', 0) 286 | if settingId: 287 | addSetting(setting , '/TemperatureType', self) 288 | addSetting(setting , '/CustomName', self) 289 | self.add_path('/TemperatureType', 0, writeable=True, onchangecallback = lambda x,y: handle_changed_value(setting,x,y) ) 290 | self.add_path('/CustomName', '', writeable=True, onchangecallback = lambda x,y: handle_changed_value(setting,x,y) ) 291 | self.add_path('/Function', 1, writeable=True ) 292 | if 'adc' in physical: 293 | if settingId: 294 | addSetting(setting,'/Scale',self) 295 | addSetting(setting,'/Offset',self) 296 | self.add_path('/Scale', 1.0, writeable=True, onchangecallback = lambda x,y: handle_changed_value(setting,x,y) ) 297 | self.add_path('/Offset', 0, writeable=True, onchangecallback = lambda x,y: handle_changed_value(setting,x,y) ) 298 | if type == 'humidity': 299 | self.add_path('/Humidity', []) 300 | self.add_path('/Status', 0) 301 | 302 | return self 303 | 304 | dbusservice = {} # Dictionary to hold the multiple services 305 | 306 | base = 'com.victronenergy' 307 | 308 | # Init setting - create setting object to read any existing settings 309 | # Init is called again later to set anything that does not exist 310 | # this gets round the Chicken and Egg bootstrap problem, 311 | 312 | # service defined by (base*, type*, connection*, logial, id*, instance, settings ID): 313 | # The setting iD is used with settingsDevice library to create a persistent setting 314 | # Items marked with a (*) are included in the service name 315 | # 316 | # I have commented out the bits that will make new services for i2C and ADC services here 317 | # If you want to re-enable these you need to uncomment the right lines 318 | 319 | #dbusservice['i2c-temp'] = new_service(base, 'temperature', 'i2c', 'i2c Device 1', 0, 25, 7) 320 | #dbusservice['i2c-humidity'] = new_service(base, 'humidity', 'i2c', 'i2c Device 1', 0, 25) 321 | # Tidy up custom or missing items 322 | #dbusservice['i2c-temp'] ['/ProductName'] = 'Encased i2c AM2315' 323 | #dbusservice['i2c-humidity']['/ProductName'] = 'Encased i2c AM2315' 324 | 325 | 326 | #dbusservice['adc-temp0'] = new_service(base, 'temperature', 'RPi_adc0', 'Temperature sensor input 3', 0, 26, 3) 327 | #dbusservice['adc-temp1'] = new_service(base, 'temperature', 'RPi_adc1', 'Temperature sensor input 4', 1, 27, 4) 328 | #dbusservice['adc-temp7'] = new_service(base, 'temperature', 'RPi_adc7', 'Temperature sensor input 5', 2, 28, 5) 329 | # Tidy up custom or missing items 330 | #dbusservice['adc-temp0'] ['/ProductName'] = 'Custard Pi-3 8x12bit adc' 331 | #dbusservice['adc-temp1'] ['/ProductName'] = 'Custard Pi-3 8x12bit adc' 332 | #dbusservice['adc-temp7'] ['/ProductName'] = 'Custard Pi-3 8x12bit adc' 333 | 334 | dbusservice['cpu-temp'] = new_service(base, 'temperature', 'Rpi-cpu', 'Raspberry Pi OS', 6, 29, 6) 335 | #dbusservice['W1-temp'] = new_service(base, 'temperature', 'Wire', '1Wire', 0, 28, 5) 336 | 337 | # Tidy up custom or missing items 338 | dbusservice['cpu-temp'] ['/ProductName'] = 'Raspberry Pi' 339 | #dbusservice['W1-temp'] ['/ProductName'] = '1Wire Sensor' 340 | # Persistent settings obejects in settingsDevice will not exist before this is executed 341 | initSettings(newSettings) 342 | # Do something to read the saved settings and apply them to the objects 343 | readSettings(settingObjects) 344 | 345 | # Do a first update so that all the readings appear. 346 | update() 347 | # update every 10 seconds - temperature and humidity should move slowly so no need to demand 348 | # too much CPU time 349 | # 350 | gobject.timeout_add(10000, update) 351 | 352 | print('Connected to dbus, and switching over to gobject.MainLoop() (= event based)') 353 | mainloop = gobject.MainLoop() 354 | mainloop.run() 355 | 356 | 357 | --------------------------------------------------------------------------------