Tutorial: OSGi Remote Services for the Raspberry Pi

From Eclipsepedia

Jump to: navigation, search


Contents

Introduction

This tutorial shows how OSGi Remote Services may be used with the Raspberry Pi or other devices. Below an entirely new service is defined, implemented, and deployed on the Raspberry Pi using ECF's implementation of OSGi Remote Services.

Define the Remote Service

First, it's necessary to define the new service interface. With OSGi services, this is done by creating a java interface

public interface IRaspberryPi {
	/**
	 * Get system properties for the Raspberry Pi remote service host.
	 * @return Map<String,String> the system properties for the remote RP
	 */
	public Map<String,String> getSystemProperties();
}

This defines a very simple service to be provided by the Raspberry Pi: giving a remote consumer access to the Raspberry Pi's system properties. Other more complicated services are, of course, possible...e.g. ones that provide access to the full range of capabilities of the Raspberry Pi device.

Asynchronous Remote Service Interface

ECF Remote Services allows remote service consumers that ability to use Java 8's CompletableFuture to access a remote service asynchronously...i.e. without blocking in the face of network I/O. This is through an ECF Remote Services feature called Asynchronous Remote Services. The only requirement is that an asynchronous service interface is declared

public interface IRaspberryPiAsync {
	/**
	 * Get remote system properties via CompletableFuture for non-blocking.
	 * Note:  signature of this method is connected to {@link    
         *        IRaspberryPi#getSystemProperties()}.
	 * 
	 * @return CompletableFuture
	 */
	public CompletableFuture<Map<String,String>> getSystemPropertiesAsync();
}

Note the correspondence between the interface names: IRaspberryPi and IRaspberryPiAsync, and the method names: getSystemProperties() and getSystemPropertiesAsync().

These two service interfaces define the remote service. The source code for these two interfaces, all supporting OSGi meta-data are available as a complete project in the ECF git repo, located here: examples/bundles/org.eclipse.ecf.examples.raspberrypi.management.

Service Host: The Remote Service Implementation

The remote service host...in this case the Raspberry Pi device...must provide an implementation of the IRaspberryPi service interface. Here is a very simple implementation of the IRaspberryPi service interface

/**
 * Implementation of IRaspberryPi service interface.
 */
public class RaspberryPi implements IRaspberryPi {
 
	public Map<String, String> getSystemProperties() {
		Properties props = System.getProperties();
 
		Map<String, String> result = new HashMap<String,String>();
		for (final String name: props.stringPropertyNames())
		    result.put(name, props.getProperty(name));
 
		System.out.println("REMOTE CALL: getSystemProperties()");
		return result;
	}
}

Service Host: Registering the Remote Service

Given the OSGi Remote Services specification, and the ECF Remote Services implementation, the only other code that has to be created is the code that registers the remote service with the OSGi service registry. According to the OSGi Remote Service specification, if a service is registered with the specified standard service properties, and a compliant implementation is present in the framework, then the service will be exported for remote access.

Here is the code to set the service properties and to register the RaspberryPi service

public void start(BundleContext context) throws Exception {
	Dictionary<String,Object> props = new Hashtable<String,Object>();
	// 1.  Add OSGi required remote service properties
	props.put("service.exported.interfaces", System.getProperty(OSGI_SERVICE_EXPORTED_INTERFACES,"*"));
	// Use ECF generic server config.
	props.put("service.exported.configs", "ecf.generic.server");
	// 2.  Setup hostname config (default:localhost)
	String hostname = System.getProperty("ecf.generic.server.hostname");
	if (hostname != null) 
		props.put("ecf.generic.server.hostname",hostname);
	// Setup port config (first available:-1)
	props.put("ecf.generic.server.port",new Integer(System.getProperty("ecf.generic.server.port","-1")));
	// 3.  Setup IRaspberryPiAsync as async remote service
	props.put("ecf.exported.async.interfaces", "*");
	// 4.  This remote service registration will trigger export, and publishing via zeroconf
	registration = context.registerService(IRaspberryPi.class, new RaspberryPi(), props);
 
	System.out.println("IRaspberryPi remote service registered="+registration);
}

This code (from the bundle activator) is called by the OSGi framework when the bundle is started. What it's doing:

  1. the OSGi standard service properties are set...i.e. service.exported.interfaces and service.exported.configs
  2. some configuration-specific properties are set...i.e. ecf.generic.server.hostname and ecf.generic.server.port
  3. the ECF Asynchronous Remote Service property is set...i.e. ecf.exported.async.interfaces
  4. the service is registered via context.registerService with the IRaspberryPi.class, a new instance of the RaspberryPi implementation class, and the previously set service props.

The source code for both the RaspberryPi implementation and the remote service registration is available in a project in the ECF git repo, located here: examples/bundles/org.eclipse.ecf.examples.raspberrypi.management.host.

Raspberry Pi Prerequisites

The main prerequisite for running our new remote service on the Raspberry Pi is the availability of a Java 8 virtual machine for the Pi. here are instructions for installing Java 8 on the Pi. Once this is done, all we need to do is create a framework that includes:

  1. An OSGi Framework
  2. A remote services implementation
  3. The two host bundles above i.e. org.eclipse.ecf.examples.raspberrypi.management and org.eclipse.ecf.examples.raspberrypi.management.host
  4. All dependencies for 1-3

To easily this set of bundles, we will use an Eclipse product config, defined in the ECF git repo, in the following feature project examples/bundles/org.eclipse.ecf.examples.raspberrypi.management.host.feature.

Running the Raspberry Pi Host

When the products/RaspberryPiManagmentHost.product is exported using Eclipse, and the output contents copied over to the Raspberry Pi filesystem, it looks something like this on the RaspberryPi disk

root@raspberrypi:/home/pi/rpi/rp2/raspberrypimgmt1# ls -A -R
.:
configuration  eclipse.ini  .eclipseproduct  features  plugins  rpimgmthost.bat  rpimgmthost.sh

./configuration:
config.ini

./features:
org.eclipse.ecf.examples.raspberrypi.management.host.feature_1.0.0.201404281938

./features/org.eclipse.ecf.examples.raspberrypi.management.host.feature_1.0.0.201404281938:
feature.xml

./plugins:
ch.ethz.iks.slp_1.1.0.v20140427-1612.jar
org.apache.felix.gogo.command_0.10.0.v201209301215.jar
org.apache.felix.gogo.runtime_0.10.0.v201209301036.jar
org.apache.felix.gogo.shell_0.10.0.v201212101605.jar
org.eclipse.core.jobs_3.6.0.v20140407-1602.jar
org.eclipse.ecf_3.4.0.v20140427-1612.jar
org.eclipse.ecf.console_1.0.0.v20140427-1612.jar
org.eclipse.ecf.discovery_5.0.0.v20140427-1612.jar
org.eclipse.ecf.examples.raspberrypi.management_1.0.0.201404281938.jar
org.eclipse.ecf.examples.raspberrypi.management.host_1.0.0.201404281938.jar
org.eclipse.ecf.identity_3.4.0.v20140427-1612.jar
org.eclipse.ecf.osgi.services.distribution_2.0.300.v20140427-1612.jar
org.eclipse.ecf.osgi.services.remoteserviceadmin_4.0.0.v20140427-1612.jar
org.eclipse.ecf.osgi.services.remoteserviceadmin.proxy_1.0.0.v20140427-1612.jar
org.eclipse.ecf.provider_4.5.0.v20140427-1612.jar
org.eclipse.ecf.provider.jslp_3.2.0.v20140427-1612.jar
org.eclipse.ecf.provider.remoteservice_4.1.0.v20140427-1612.jar
org.eclipse.ecf.remoteservice_8.4.100.v20140427-1612.jar
org.eclipse.ecf.remoteservice.asyncproxy_2.0.0.v20140410-1838.jar
org.eclipse.ecf.sharedobject_2.5.0.v20140427-1612.jar
org.eclipse.equinox.common_3.6.200.v20130402-1505.jar
org.eclipse.equinox.concurrent_1.1.0.v20130327-1442.jar
org.eclipse.equinox.console_1.1.0.v20140131-1639.jar
org.eclipse.equinox.event_1.3.100.v20140115-1647.jar
org.eclipse.osgi_3.10.0.v20140407-2102.jar
org.eclipse.osgi.services_3.4.0.v20140312-2051.jar
org.eclipse.osgi.services.remoteserviceadmin_1.5.100.v20140427-1612.jar

Service Host: Starting the Remote Service

To start this application, register and export the RaspberryPi remote service, type

root@raspberrypi:/home/pi/rpi/rp2/raspberrypimgmt1# sudo ./rpimgmthost.sh 192.168.1.80

where the ip address on the LAN is given at the end (i.e. 192.168.1.80). After some time, it will produce the following output

Hostname: 192.168.1.80
Port: 3288
javaprops=-Decf.generic.server.hostname=192.168.1.80 -Decf.generic.server.port=3288 -Declipse.ignoreApp=trueutdown=true
equinox=plugins/org.eclipse.osgi_3.10.0.v20140407-2102.jar
Debug options:
    file:/home/pi/rpi/rp2/raspberrypimgmt1/.options not found
Time to load bundles: 333
IRaspberryPi remote service registered={org.eclipse.ecf.examples.raspberrypi.management.IRaspberryPi}={ecf.generic.server.port=3288, ecf.exported.async.interfaces=*, ecf.generic.server.hostname=192.168.1.80, service.exported.configs=ecf.generic.server, service.exported.interfaces=*, service.id=77, service.bundleid=28, service.scope=singleton}

osgi>

This indicates tht the IRaspberryPi remote service has successfully been registered.

The osgi> prompt indicates that the OSGi console is available for input, allowing you to give commands to (e.g.) show the status of all the bundles in the framework:

osgi> ss
"Framework is launched."

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.10.0.v20140407-2102
3       ACTIVE      org.apache.felix.gogo.command_0.10.0.v201209301215
4       ACTIVE      org.apache.felix.gogo.runtime_0.10.0.v201209301036
5       ACTIVE      org.apache.felix.gogo.shell_0.10.0.v201212101605
6       ACTIVE      org.eclipse.ecf_3.4.0.v20140427-1612
7       ACTIVE      org.eclipse.ecf.console_1.0.0.v20140427-1612
8       ACTIVE      org.eclipse.ecf.discovery_5.0.0.v20140427-1612
9       ACTIVE      org.eclipse.ecf.identity_3.4.0.v20140427-1612
10      ACTIVE      org.eclipse.ecf.osgi.services.distribution_2.0.300.v20140427-1612
11      ACTIVE      org.eclipse.ecf.osgi.services.remoteserviceadmin_4.0.0.v20140427-1612
12      ACTIVE      org.eclipse.ecf.osgi.services.remoteserviceadmin.proxy_1.0.0.v20140427-1612
13      ACTIVE      org.eclipse.ecf.provider_4.5.0.v20140427-1612
14      ACTIVE      org.eclipse.ecf.remoteservice_8.4.100.v20140427-1612
15      ACTIVE      org.eclipse.ecf.remoteservice.asyncproxy_2.0.0.v20140410-1838
16      ACTIVE      org.eclipse.ecf.sharedobject_2.5.0.v20140427-1612
17      ACTIVE      org.eclipse.equinox.common_3.6.200.v20130402-1505
18      ACTIVE      org.eclipse.equinox.concurrent_1.1.0.v20130327-1442
19      ACTIVE      org.eclipse.equinox.console_1.1.0.v20140131-1639
20      ACTIVE      org.eclipse.equinox.event_1.3.100.v20140115-1647
21      ACTIVE      org.eclipse.core.jobs_3.6.0.v20140407-1602
22      ACTIVE      org.eclipse.osgi.services_3.4.0.v20140312-2051
23      ACTIVE      org.eclipse.osgi.services.remoteserviceadmin_1.5.100.v20140427-1612
24      ACTIVE      org.eclipse.ecf.provider.remoteservice_4.1.0.v20140427-1612
25      ACTIVE      ch.ethz.iks.slp_1.1.0.v20140427-1612
26      ACTIVE      org.eclipse.ecf.provider.jslp_3.2.0.v20140427-1612
27      ACTIVE      org.eclipse.ecf.examples.raspberrypi.management_1.0.0.201404281938
28      ACTIVE      org.eclipse.ecf.examples.raspberrypi.management.host_1.0.0.201404281938
osgi>

Service Consumer: Accessing the Remote Service

Now that the IRaspberryPi remote service is running on the Pi, we need to have some other process discover and then use the IRaspberryPi remote service. First, we need some code to actually call the IRaspberryPiAsync.getSystemPropertiesAsync() method once the IRaspberryPiAsync method has been discovered and imported. Here's is such code:

void bindRaspberryPi(IRaspberryPiAsync rpi) {
	CompletableFuture<Map<String,String>> future = rpi.getSystemPropertiesAsync();
	future.thenAccept((map) -> {
		System.out.println("Found RaspberryPi");
		for(String key: map.keySet()) 
			System.out.println("  "+key+"="+map.get(key));
	});
}

When the IRaspberryPiAsync service is discovered and injected into our code by OSGi Declarative Services, we simply call the getSystemPropertiesAsync() method, and then when the CompletableFuture is complete the functional interface defined for the future.thenAccept will be run, displaying to system out the values in the map returned from the Raspberry Pi.

Here is a picture of Eclipse with the Raspberry Pi remote service discovered on the LAN (192.168.1.80). See in the Service Discovery view and the Properties view...this is the IRaspberryPi remote service dynamically discovered on this LAN using the SLP service discovery protocol, and presented in the ECF Service Discovery viewer.

Raspberrypimgmt1.png

Finally, here is the output when the consumer code is executed. For brevity, I've removed some of the longer Pi system properties.

osgi> Found RaspberryPi
  awt.toolkit=sun.awt.X11.XToolkit
  ecf.generic.server.port=3288
  file.encoding.pkg=sun.io
  java.specification.version=1.8
  sun.cpu.isalist=
  sun.jnu.encoding=UTF-8
  org.osgi.framework.version=1.8.0
  java.class.path=plugins/org.eclipse.osgi_3.10.0.v20140407-2102.jar
  osgi.nl=en_GB
  java.vm.vendor=Oracle Corporation
  osgi.syspath=/home/pi/rpi/rp2/raspberrypimgmt1/plugins
  sun.arch.data.model=32
  java.vendor.url=http://java.oracle.com/
  org.osgi.framework.system.capabilities=osgi.ee; osgi.ee="OSGi/Minimum"; 
version:List<Version>="1.0, 1.1, 1.2",osgi.ee; osgi.ee="JRE"; 
version:List<Version>="1.0, 1.1",osgi.ee; osgi.ee="JavaSE"; 
version:List<Version>="1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8"
  user.timezone=US/Pacific
  org.osgi.framework.uuid=20a8e8e2-48cf-0013-1c66-fbaa4ae339ea
  os.name=Linux
  org.osgi.framework.processor=ARM
  java.vm.specification.version=1.8
  ecf.generic.server.hostname=192.168.1.80
  sun.java.launcher=SUN_STANDARD
  user.country=GB
  sun.boot.library.path=/opt/jdk1.8.0/jre/lib/arm
  osgi.console=
  sun.java.command=plugins/org.eclipse.osgi_3.10.0.v20140407-2102.jar -configuration 
file:configuration -os linux -ws gtk -arch arm -console -consoleLog -debug
  sun.cpu.endian=little
  org.osgi.supports.framework.requirebundle=true
  osgi.logfile=/home/pi/rpi/rp2/raspberrypimgmt1/configuration/1398739766503.log
  osgi.arch=arm
  osgi.install.area=file:/home/pi/rpi/rp2/raspberrypimgmt1/plugins/
  user.home=/root
  user.language=en
...
  org.osgi.framework.language=en
  java.specification.vendor=Oracle Corporation
  gosh.args=--noshutdown
  java.home=/opt/jdk1.8.0/jre
  file.separator=/
  osgi.noShutdown=true
  line.separator=

  org.osgi.framework.executionenvironment=OSGi/Minimum-1.0,OSGi/Minimum-1.1,
OSGi/Minimum-1.2,JRE-1.1,J2SE-1.2,J2SE-1.3,J2SE-1.4,J2SE-1.5,JavaSE-1.6,JavaSE-1.7,JavaSE-1.8
  java.vm.specification.vendor=Oracle Corporation
  java.specification.name=Java Platform API Specification
  java.awt.graphicsenv=sun.awt.X11GraphicsEnvironment
  sun.boot.class.path=/opt/jdk1.8.0/jre/lib/resources.jar:/opt/jdk1.8.0/jre/lib/rt.jar:
/opt/jdk1.8.0/jre/lib/sunrsasign.jar:/opt/jdk1.8.0/jre/lib/jsse.jar:/opt/jdk1.8.0/jre/lib/jce.jar:
/opt/jdk1.8.0/jre/lib/charsets.jar:/opt/jdk1.8.0/jre/lib/jfr.jar:/opt/jdk1.8.0/jre/classes
  sun.management.compiler=HotSpot Client Compiler
  java.runtime.version=1.8.0-b132
  user.name=root
  path.separator=:
  osgi.compatibility.bootdelegation=false
  osgi.framework.useSystemProperties=true
  os.version=3.10.25+
  java.endorsed.dirs=/opt/jdk1.8.0/jre/lib/endorsed
  org.osgi.framework.vendor=Eclipse
  eclipse.consoleLog=true
  java.runtime.name=Java(TM) SE Runtime Environment
  org.osgi.supports.framework.fragment=true
...
  file.encoding=UTF-8
  java.vm.name=Java HotSpot(TM) Client VM
  osgi.framework=file:/home/pi/rpi/rp2/raspberrypimgmt1/plugins/org.eclipse.osgi_3.10.0.v20140407-2102.jar
  osgi.configuration.area=file:/home/pi/rpi/rp2/raspberrypimgmt1/configuration/
  osgi.os=linux
  java.vendor.url.bug=http://bugreport.sun.com/bugreport/
  org.osgi.framework.os.name=Linux
  java.io.tmpdir=/tmp
  eclipse.home.location=file:/home/pi/rpi/rp2/raspberrypimgmt1/plugins/
  org.osgi.supports.framework.extension=true
  java.version=1.8.0
  user.dir=/home/pi/rpi/rp2/raspberrypimgmt1
  osgi.debug=
  os.arch=arm
  osgi.ws=gtk
  osgi.bundles.defaultStartLevel=4
  java.vm.specification.name=Java Virtual Machine Specification
  java.awt.printerjob=sun.print.PSPrinterJob
  sun.os.patch.level=unknown
  eclipse.startTime=1398739765280
  eclipse.ignoreApp=true
  java.library.path=/usr/java/packages/lib/arm:/lib:/usr/lib
  org.osgi.framework.os.version=3.10.25
  java.vendor=Oracle Corporation
  java.vm.info=mixed mode
  java.vm.version=25.0-b70
  sun.arch.abi=gnueabihf
  sun.io.unicode.encoding=UnicodeLittle
  java.ext.dirs=/opt/jdk1.8.0/jre/lib/ext:/usr/java/packages/lib/ext
  eclipse.stateSaveDelayInterval=30000
  java.class.version=52.0

The source code for this consumer, along with the OSGi Declarative Services metadata is available in the ECF git repo, located in this project: examples/bundles/org.eclipse.ecf.examples.raspberrypi.management.consumer.

Here is a project set file, to aid the in the cloning and import of all four of the source code projects referred to above, i.e.

  1. the remote service interfaces in org.eclipse.ecf.examples.raspberrypi.management
  2. the host impl and egistration code in org.eclipse.ecf.examples.raspberrypi.management.host
  3. the host feature to build and deploy to the Raspberry Pi in org.eclipse.ecf.examples.raspberrypi.management.host
  4. the IRaspberryPiAsync non-blocking consumer code in org.eclipse.ecf.examples.raspberrypi.consumer

Related Articles

Getting Started with ECF's OSGi Remote Services Implementation

Asynchronous Proxies for Remote Services

Building your first Asynchronous OSGi Remote Service Tutorial

Download ECF Remote Services/RSA Implementation

How to Add Remote Services/RSA to Your Target Platform