Source Files¶
Most of the code for the application is included in a single main.py
file which contains an Application
class to manage the running of the application and a Window
class to describe the user interface.
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 a custom sensor
module to enable us to access the sensors.
import sys
import gi
from .sensor import Proximity
gi.require_version('Gtk', '3.0')
from gi.repository import GLib, Gtk
The sensor
module is described in more detail below.
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.proximity')
GLib.set_application_name(_('Proximity'))
GLib.set_prgname('com.example.proximity')
In the do_activate
method, we create the application window and show it, but we also create a Proximity
object from the sensor
module:
def do_activate(self):
window = Window(application=self)
window.set_default_size(320, 512)
window.show_all()
self.proximity = Proximity()
self.proximity.connect('changed', window.update_info)
self.proximity.claim()
The Proximity
object has a changed
signal that we connect to the window’s update_info
method.
The do_shutdown
method of the application is called just before the application exits. In this method we call the release
method of the Proximity
object to release the sensor for other applications:
def do_shutdown(self):
self.proximity.release()
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 a Gtk.Label widget to show the status of the sensor:
class Window(Gtk.ApplicationWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.label = Gtk.Label(halign='center')
self.label.set_markup(
_('<span size="large">Waiting for changes</span>'))
self.add(self.label)
The label will be updated when the sensor’s state changes due to the signal connection in the application’s do_activate
method.
Responding to Updates¶
As described above, the application contains a Proximity
object that will emit a changed
signal when the sensor’s state changes. When this occurs the update_info
method is called:
def update_info(self, sensor, near):
if near:
self.label.set_markup(
_('<span size="large"><b>Near</b></span>'))
else:
self.label.set_markup(
_('<span size="large">Far</span>'))
The signal delivers two values in the sensor
and near
parameters of the method. Since there is only one proximity
object in use, we simply check the value of near
to determine which string to show in the label.
The information provided by the changed
signal is intended to be simple, so that the GUI part of the application only needs to include code to respond to proximity sensor changes. The Proximity
class hides any complexity from the main application.
The Proximity Class¶
The sensor
module provides the Proximity
class that is used to access sensor data, using the Gio
module to communicate with a D-Bus service:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gio, GObject
Since the Proximity
class needs to emit signals, the GObject
class from the GObject
module is also needed. The class is derived from GObject
and declares the changed
signal to inform the rest of the application about sensor changes:
class Proximity(GObject.GObject):
__gsignals__ = {
'changed': (GObject.SIGNAL_RUN_FIRST, None, (bool,))
}
In the __init__
method of the class, we call the base class’s __init__
method to properly initialize it, before creating a proxy object that communicates with the net.hadess.SensorProxy
service on the system D-Bus:
def __init__(self):
GObject.GObject.__init__(self)
# 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)
# Track D-Bus property changes.
self.proxy.connect('g-properties-changed',
self.properties_changed, 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 this class:
def properties_changed(self, proxy, changed, invalidated, user_data):
near = proxy.get_cached_property('ProximityNear').get_boolean()
self.emit('changed', near)
In the properties_changed
method, we respond to changes from the D-Bus service by reading the state of the proximity sensor and emitting the changed
signal. This provides only the basic information to any methods that receive it, keeping the details of the D-Bus communication separate from other components.
We also provide the claim
and release
methods to allow the application to start and stop using the sensor. Each of these methods check for the presence of the proximity sensor by reading the HasProximity property of the D-Bus service:
def claim(self):
if self.proxy.get_cached_property('HasProximity').get_boolean():
self.proxy.call_sync('ClaimProximity', None, Gio.DBusCallFlags.NONE,
-1, None)
In the claim
method, we call the ClaimProximity method if the sensor is available.
def release(self):
if self.proxy.get_cached_property('HasProximity').get_boolean():
self.proxy.call_sync('ReleaseProximity', None, Gio.DBusCallFlags.NONE,
-1, None)
In the release
method, we call the ReleaseProximity method to release it for other applications.
Summary¶
Handling sensor data involves requesting information from the net.hadess.SensorProxy
service on the system D-Bus. We create a module containing the Proximity
class to communicate with the service, allowing the main application to subscribe to sensor changes by connecting to a changed
signal.
The Proximity
class itself performs a few tasks to make this possible, but users of this class can simply
- connect its
changed
signal to a callback method,- call its
claim
method to start receiving updates, and- call its
release
method to stop receiving updates.
Applications do not need to provide an abstraction like this one, but it can make it easier to separate sensor-handling code from GUI code.