Source Files¶
Most of the code for the application is included in a single main.py
file which contains a single Application
class to manage the running of the application and a main
function to start it.
Much of the code is very similar to other examples and tutorials. We will focus on the parts that are specific to this example.
Relevant Modules¶
In addition to the usual modules for accessing command line arguments and GTK widgets, we use the Gio
module to enable us to access D-Bus services.
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gio, GLib, Gtk
The Gio
and GLib
modules are imported in the same way as the Gtk
module.
Setting up the Application¶
The application provides an Application
class with the usual methods to set up the application and perform tasks when it is run. In the __init__
method we set up the application title, ID and program name to ensure that it integrates into the environment:
class Application(Gtk.Application):
def __init__(self):
super().__init__(application_id='com.example.ambient_light')
GLib.set_application_name(_('Ambient Light'))
GLib.set_prgname('com.example.ambient_light')
In the do_activate
method, we create the application window and show it, but we also set up a DBusProxy object that we use to communicate with a D-Bus service providing sensor data:
def do_activate(self):
window = Window(application=self)
window.set_default_size(320, 512)
window.show_all()
# Request a proxy for accessing the sensor service.
self.proxy = Gio.DBusProxy.new_for_bus_sync(
Gio.BusType.SYSTEM, Gio.DBusProxyFlags.NONE, None,
'net.hadess.SensorProxy',
'/net/hadess/SensorProxy',
'net.hadess.SensorProxy',
None)
This proxy object emits the g-properties-changed
signal when any of the properties exposed by the service change. We connect this signal to the properties_changed
method in the window so that it can update the user interface with the latest sensor information:
self.proxy.connect('g-properties-changed',
window.properties_changed, None)
The last task the do_activate
does is to check for the presence of the ambient light sensor by reading the HasAmbientLight property of the D-Bus service. If this returns True
we call the ClaimLight method of the D-Bus API to start receiving updates to the ambient light sensor.
if self.proxy.get_cached_property('HasAmbientLight').get_boolean():
self.proxy.call_sync('ClaimLight', None, Gio.DBusCallFlags.NONE,
-1, None)
The do_shutdown
method of the application is called just before the application exits. In this method we call the ReleaseLight method of the D-Bus API to release the sensor for other applications:
def do_shutdown(self):
if self.proxy.get_cached_property('HasAmbientLight').get_boolean():
self.proxy.call_sync('ReleaseLight', None, Gio.DBusCallFlags.NONE,
-1, None)
Applications should usually only claim the sensor when required and release it as soon as possible.
Creating the User Interface¶
The Window
class is used to display a simple user interface that updates when new sensor data is available. We begin by creating some Gtk.Label widgets to show the information, using Gtk.Box boxes to organize them:
class Window(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.indicator_label = Gtk.Label()
self.level_label = Gtk.Label()
self.level_label.set_markup(
_('<span size="large">Waiting for changes</span>'))
inner_box = Gtk.Box(orientation='vertical')
inner_box.add(self.level_label)
inner_box.add(self.indicator_label)
box = Gtk.Box(orientation='vertical')
box.set_center_widget(inner_box)
self.add(box)
The indicator label will be used to display a simple bar graph; the level label will show the measured quantity provided by the D-Bus service.
Responding to Updates¶
As described above, the properties_changed
method of the Window
class will be called when the properties exposed by the D-Bus service change:
def properties_changed(self, proxy, changed, invalidated, user_data):
level = proxy.get_cached_property('LightLevel').get_double()
unit = proxy.get_cached_property('LightLevelUnit').get_string()
blocks = '▩' * int(min((level * 16)**0.5, 16))
self.indicator_label.set_markup(
_('<span size="large">' + blocks + '</span>'))
self.level_label.set_markup(
_('<span size="large">%1.1f %s</span>' % (level, unit)))
Since we are only interested in the ambient light level, we simply request the values of the LightLevel and LightLevelUnit properties, updating the two labels with the new information.
Another approach to handling updated sensor information would involve examining the values passed in the changed
and invalidated
method parameters, and only changing the user interface if the ambient light data has changed.
Summary¶
Handling sensor data involves requesting information from the net.hadess.SensorProxy
service on the system D-Bus. Applications can create a DBusProxy object to communicate with the service. When this is done, you can subscribe to property updates by
- connecting the
g-properties-changed
signal to a callback method,- checking the HasAmbientLight property for the presence of the sensor, and
- calling the service’s ClaimLight method to start receiving updates.
When the sensor is no longer needed, call the ReleaseLight method of the D-Bus API.