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.
Tutorial: OSGi Remote Services for the Raspberry Pi
Contents
- 1 Introduction
- 2 Define the Remote Service
- 3 Service Host: The Remote Service Implementation
- 4 Service Host: Registering the Remote Service
- 5 Raspberry Pi Prerequisites
- 6 Running the Raspberry Pi Host
- 7 Service Host: Starting the Remote Service
- 8 Service Consumer: Accessing the Remote Service
- 9 Related Articles
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:
- the OSGi standard service properties are set...i.e. service.exported.interfaces and service.exported.configs
- some configuration-specific properties are set...i.e. ecf.generic.server.hostname and ecf.generic.server.port
- the ECF Asynchronous Remote Service property is set...i.e. ecf.exported.async.interfaces
- 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:
- An OSGi Framework
- A remote services implementation
- The two host bundles above i.e. org.eclipse.ecf.examples.raspberrypi.management and org.eclipse.ecf.examples.raspberrypi.management.host
- All dependencies for 1-3
To easily build 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. (When you use the "Product export wizard" be sure to uncheck "Synchronize before exporting".)
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.
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.
- the remote service interfaces in org.eclipse.ecf.examples.raspberrypi.management
- the host impl and egistration code in org.eclipse.ecf.examples.raspberrypi.management.host
- the host feature to build and deploy to the Raspberry Pi in org.eclipse.ecf.examples.raspberrypi.management.host
- 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