Notice: This Wiki is now read only and edits are no longer possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.
ECF Remote Services for Raspberry Pi GPIO
Contents
Introduction
This article demonstrates how to use the Eclipse Communication Framework (ECF) Remote Services to interact with networked devices based upon Raspberry Pi GPIO. The use of Remote Services provides clear advantages:
- The Service Interface provides a simple, clear, and flexible abstraction for accessing individual GPIO pins. This enables system-wide loose coupling and high cohesion, two very desirable design characteristics for networked services.
- There are multiple technical advantages from using ECF Remote Services
- Dynamics - Hardware-based services come and go (especially when made available via a network), and applications can be notified and adjust appropriately
- Service Versioning - Hardware capabilities and their associated services may evolve over time, and this may require versioning the Service Interface, and multiple versions to coexisting in a single runtime.
- Support for Multiple Injection Frameworks - Declarative Services, Blueprint, Spring, and other standard (or proprietary) frameworks can be used to inject services and dependencies
- Support for Service-Level Security - Flexible policies to control, restrict, and/or manage services may be defined as appropriate for the application
- Transport-Independent Architecture - ECF Remote Services provides full interoperability through open standards, a transport-independent architecture, and open, extensible, community-developed-and-tested APIs and implementations.
In this article, we step through the declaration, implementation, and running of remote services for controlling individual GPIO Pins. After showing how these services can be used to control a simple light application for the Raspberry Pi, we then describe how to use ECF Remote Services to distribute these services across a network. We then demonstrate how these remote services can be easily used by an Eclipse-based user interface to remotely control the state of individual GPIO Pins.
Accessing GPIO Pin State
Each GPIO digital pins have two modes: output and input. For output, each pin can be in either the 'HIGH' state, or the 'LOW' state, and the control of the pin state is what we would like to manipulate to give our application desired behavior. After connecting a single LED to the Raspberry Pi GPIO, changing the output on a given pin to HIGH will turn the LED on, while setting to low will turn it off. See these instructions for how to physically connect a single LED to the Raspberry Pi GPIO.
Below is a picture of my Raspberry Pi with an LED connected to GPIO digital output pin 0, as per the these instructions.
We start by declaring a GPIO pin output service interface. This service interface declares methods for getting and setting the state of a single GPIO pin. Here is the IGPIOPinOutput service interface:
public interface IGPIOPinOutput extends IGPIOPin { public boolean getState(); public void setState(boolean value); ... }
For this article, we will be interested only in the getState and setState methods from this service interface. Note that at runtime we may create as many instances of this service interface as desired. Modern Raspberry Pi's have two rows of 10 pins for a total of 20 physical GPIO pins. To control our LED application, we only need to control a single pin, so will only need a single instance of the IGPIOPinOutput service that communicates with pin id=0.
To find active instances of the IGPIOPinOutput service there are several mechanisms available (e.g. Declarative Services, Blueprint, Spring, ServiceTracker, ServiceReferences, etc.). For this tutorial, we will use a very simple ServiceTracker. The complete code for this example can be located in the start method of this test code. The important fragments is the implementation of the addingService method, which is called by the OSGi framework when instances of IGPIOPinOutput are registered with the OSGi service registry:
public IGPIOPinOutput addingService(ServiceReference<IGPIOPinOutput> reference) { .... // Get the service instance IGPIOPinOutput pin = context.getService(reference); // Print out info about it's current state System.out.println(" current pin state is "+(pin.getState()?"HIGH":"LOW")); System.out.println(" setting state to HIGH"); // Set the pin's state to true/HIGH pin.setState(true); return pin; }
Note that when a IGPIOPinService is added, the service.setState(true) method is called, setting the state of the pin to HIGH, and turning on/lighting the LED connected to that pin. When this test code is run on a Raspberry Pi, this appears on the OSGi console:
osgi> start 3 Adding GPIO Pin Output service. id=0 current pin state is LOW setting state to HIGH
and as a result of the call to pin.setState(true) the LED connected to GPIO pin 0 lights up
Here is a short video showing the running of this test code. Thanks to ECF committer Wim Jongman for duplicating this example and making the video available.
Exporting the GPIO Pin Output Service
To make the control of GPIO pin 0 available for remote access, with ECF Remote Services all we need to do is to provide values for some standardized service properties when registering the IGPIOPinOutput service instance. The names and appropriate types/values of these service properties are standardized by OSGi Remote Services, which is specified by OSGi Enterprise specification.
Here is the code to register and export the GPIOPinOutput service
Map<String, Object> pinProps = new HashMap<String, Object>(); pinProps.put("service.exported.interfaces", "*"); pinProps.put("service.exported.configs", "ecf.generic.server"); pinProps.put("ecf.generic.server.port", "3288"); pinProps.put("ecf.generic.server.hostname",InetAddress.getLocalHost().getHostAddress()); pinProps.put("ecf.exported.async.interfaces", "*"); ... // register and export GPIOPin 0 reg = Pi4jGPIOPinOutput.registerGPIOPinOutput(0, pinProps, context);
See here for the entire source for this example.
With ECF's Remote Services implementation present in the runtime, the properties given above will result in the IGPIOPinOutput service being exported as a remote service. The export happens automatically during the registration of the IGPIOPinOutput service (inside the registerGPIOPinOutput method in the above code). With the default discovery policy, exporting this remote service will also advertise/publish it for network-based discovery. For this example, we use the Service Locator Protocol (RFC 2608) for LAN-based network discovery. ECF Remote Services provides multiple alternatives for discovery, with support for both networked an non-network discovery alternatives.
Discovering Remote GPIOPinOutput Services
Once exported, remote consumers (clients) may discover and then call methods on the IGPIOPinOutput remote service. To demonstrate this, here is a simple Eclipse-based user interface to present the discovered IGPIOPinOutput remote service instances. The code for this Eclipse view is in the test/bundles/org.eclipse.ecf.raspberrypi.test.gpio.ide project. When run in the Eclipse ide, the empty view (no discovered IGPIOPinOutput services) looks like this
After discovering an instance of IGPIOPinOutput remote service via SLP LAN-based discovery the view is automatically updated with the IGPIOPinOutput proxy, which is automatically created by the OSGi Remote Services implementation.
With the discovery of this IGPIOPinOutput remote service, and it's appearance in this view, a simple click on the 'State (click to toggle)' cell results in the pin toggling it's state via a call to IGPIOPinUpdate.setState, resulting in both the Eclipse UI being updated with a different icon
and the LED that's connected to the Raspberry Pi lights up
The same as if we had accessed the IGPIOPinOutput service from the Raspberry Pi directly.
Summary
ECF Remote Services provides an easy way to declare arbitrary services, and expose/export them for remote access. Representing Raspberry Pi GPIO as remote services makes a great example of how such remote services can be a boon for the easy creation and distribution of services between devices that make up the Internet of Things.
Technical Notes
- The full source for this example is available via the ECF Raspberry Pi Github Repository. ECF also maintains a number of other examples and custom providers via it's Github repositories.
- 2 ECF provides asynchronous remote services for non-blocking remote procedure call. The example Eclipse code uses the IGPIOPinOutputAsync service interface, which is available here. The Eclipse UI code for this example uses this asynchronous remote service to control the GPIO Pin 0 without any methods that might block the Eclipse UI. The implementation of the IGPIOPinOutputAsync interface is automatically created by the ECF Remote Services proxy creation. Here is a snippet from the implementation of the GPIO Pin View class, showing the use of the asynchronous remote service, as well as the use of Java 8 lambda expressions.
public void toggle() { // Set waiting to true waiting = true; boolean newState = !this.state; // If we have asynchronous access to service, // then use it if (pinOutputAsync != null) { // Set state asynchronously to newState, // and when complete change the UI state // and refresh the viewer pinOutputAsync.setStateAsync(newState).whenComplete( (result, exception) -> { this.waiting = false; if (viewer != null) { // No exception means success if (exception == null) { // Set UI state to newState state = newState; asyncRefresh(); } else showCommErrorDialog(exception); } }); } else { // If we do not have access to async service, then // call pinOutput synchronously pinOutput.setState(newState); state = newState; } }
The entire source for this view class is available here.
More about asynchronous remote services is described in Asynchronous Remote Services.
Related Articles and Documentation
Getting Started with ECF's OSGi Remote Services Implementation