article

laurenceh avatar image
laurenceh posted

Simple json/REST/Ajax interface to venus d-bus written as a python script.

I wanted to create a simple json interface to d-bus that could be called using javascript as supported in almost all modern browsers. I tried signal-k but could not get that to work and being a package signal-k seemed prone to problems when new firmware versions are installed. I have written a lightweight python script to implement a REST/json type interface described here.

What does this let enable you to do?

You can write javascript in your html files to request and display d-bus data values in your web pages.

Here is the listing:

#!/usr/bin/env python
import dbus
import sys
import os
import json
from urlparse import urlparse, parse_qs

from dbus.mainloop.glib import DBusGMainLoop

# our own packages - add a location of vedbus module
sys.path.insert(1, os.path.join(os.path.dirname(__file__), '/opt/victronenergy/dbus-modem'))
from vedbus import VeDbusItemExport, VeDbusItemImport

#template URL format example
#url = "http://192.168.1.248/ui/dbus-get.py?com.victronenergy.gps=NrOfSatellites&com.victronenergy.gps/Position=Latitude,Longitude&com.victronenergy.temperature.builtin_adc3_di1=Temperature,Status"

#template response example
#response = {"vars": {"com.victronenergy.temperature.builtin_adc3_di1": {"Status": 0, "Temperature": 23}, "com.victronenergy.gps": {"NrOfSatellites": 8}, "com.victronenergy.gps/Position": {"Latitude": 52.219814300537109, "Longitude": 0.26365199685096741}}}

url = os.environ["REQUEST_URI"]

request = parse_qs(urlparse(url).query)
response = {"vars": {}}

DBusGMainLoop(set_as_default=True)
dbusConn = dbus.SessionBus() if 'DBUS_SESSION_BUS_ADDRESS' in os.environ else dbus.SystemBus()

for service in request:
    # split service address at first "/" into service and top_path
    address = service.partition("/")
    try:
        value = VeDbusItemImport(dbusConn, address[0], "/"+address[2]).get_value()
        trash = response['vars'].setdefault(service, {})
        for path in request[service][0].split(","):
            try:
                response['vars'][service][path]=value[path]
            except:
                response['vars'][service][path]="No Data"
        except:
        # this won't go anywhere perhaps it should go to error.log to aid debugging
        # or perhaps return a 404 not found - as it has not been found.
            print "Failed to fetch dbus object"

print "Content-Type: application/json"
print
print json.dumps(response)

Usage:

Examples of the url to request a set of d-bus values and the json formatted response are included in the listing.

As can be seen in the example URL it is possible to include parts of the d-bus path to the end of the d-bus service name. This increases efficiency as you don't need to issue a d-bus call to retrieve all of the data from services with large amounts of data such as "com.victronenergy.settings" or "com.victronenergy.system". In the example above you can see that there is a request for just the position values from the GPS service.

For testing purposes you can uncomment the url line or the example response line to mimic the request and response contents.

At the client end, simple javascript to make the request and parse the returned values looks like this:

<construct a url request matching the example template>
a = url;
var b = new XMLHttpRequest;
        b.onreadystatechange = function(){
            if (4 == b.readyState && 200 == b.status) {
                setTimeout(render(JSON.parse(b.responseText)), REST_CALL_DELAY_MS);
            }
        };
        b.open("GET", a, !0);
        b.send();

In my code I have REST_CALL_DELAY_MS set to 1 millisecond as I found this increases reliability depending on the client browser architecture and threading.

See article here if you need a primer on making Ajax calls https://dev.to/nikola/making-ajax-calls-in-pure-javascript-the-old-way-ed5

The JSON.pars function turns the returned json text into an object which can then be handled in javascript. The render function can parse the response object and extract the data values something like this:

function render(response) {
for(var service in response["vars"]) {
      for(var path in response["vars"][service]) {
          <locate node where returned value is to be displayed>
          node.innerHTML = response["vars"][service][path];
     }
}

To use the python script it should be placed in the directory /var/www/venus, i have placed it in a sub directory "ui", the script also need to have execute access. Python needs to be enabled in the /etc/hiawatha/hiawatha.conf file.

Using this interface I can create a custom UI using CSS, HTML and scripts which will allow me to move away from the Venus Moterhome/Boat overview screens as I have not been able to find any instructions on modifying those pages.


Venus GX - VGXRaspberry Pimodificationsd-busjavascript
2 |3000

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

Article

Contributors

LaurenceH contributed to this article