Make sure you’ve obtained the archive interfaces.middleware.zip (it is in the Android\giraff.android folder from the svn checkout). This file contains the AIDL interface needed to communicate with the middleware. In order to setup your application you need to extract this file to the src/ directory of the project.
Middleware:
Previous versions:
Applications that use this infrastructure require that the middleware application (Android/giraff.android/middleware folder from the svn checkout) and at least one communication connector application (there is only one by now in the communicationconnector.mqtt Android/giraff.android/ folder from the svn checkout) are previously installed to the device.
Also, the middleware needs to be configured before using any of the clients applications, this can be done using the middleware launcher icon. The configuration includes the mandatory parameters:
Notice. In order to convert a jks keystore to a bks keystore:
Make sure to kill the “middleware” Android process after further changes. Note that these parameters may change in the future. Refer to the wiki for more information on the Giraff+ system.
The middleware is implemented as an Android’s bound service that can be activated with the action “it.cnr.isti.giraff.android.BIND_TO_MIDDLEWARE”.
Files provided in the archive come with in-code documentation.
Follows a quick tutorial that explains step-by-step how to implement a full working example application (source code can be found in the folder from the svn checkout). This application probes the sensors of the device and announces the ones it finds, then starts sending the gathered values to the middleware.service.
Note that all you need to know is the documentation of the interface, everything else follows the standard Android’s AIDL practices so you can refer the official documentation.
The example application is composed of a main activity that launches a service in charge of listening on sensors changes:
public class MainActivity extends Activity implements OnCheckedChangeListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ToggleButton startstop = (ToggleButton) findViewById(R.id.startstop); // order matters to avoid unwanted onCheckedChanged startstop.setChecked(SensorsService.isEnabled()); startstop.setOnCheckedChangeListener(this); } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // start if (isChecked) { startService(new Intent(this, SensorsService.class)); } // stop else { stopService(new Intent(this, SensorsService.class)); } } }
A ToggleButton starts or stops the service:
The SensorsService class initializes the accelerometer sensor (creating a descriptor by means of android Bundle done in the SensorsDescriptors class), then calls methods of the middleware to announce it and publishes data to the context bus. To do this the SensorsService follows these steps:
Make sure to add the following import:
import it.cnr.isti.giraff.android.interfaces.middleware.IMiddleware;
In order to request the binding to the service you need to implement a ServiceConnection object that manages the connection to the middleware:
private IMiddleware middleware; private SensorsDescriptors sensorsDescriptors; private ServiceConnection middlewareConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder binder) { middleware = IMiddleware.Stub.asInterface(binder); // enter foreground state startForeground(1, notificationBuilder.build()); // announce all descriptors and register sensor listener sensorsDescriptors.initialize(middleware); } @Override public void onServiceDisconnected(ComponentName className) { middleware = null; // unregister sensor listener and remove all descriptors sensorsDescriptors.finalize(); // exit foreground state stopForeground(true); } };
Some methods requires a listener that need to be implemented on the service application:
private IMiddlewareCallback.Stub callback = new IMiddlewareCallback.Stub() { @Override public void serviceFound(Bundle descriptor) throws RemoteException { // ... } @Override public void serviceRemoved(Bundle descriptor) throws RemoteException { // ... } @Override public void serviceChanged(Bundle descriptor) throws RemoteException { // ... } @Override public void messageReceived(String topic, String payload) throws RemoteException { // ... } };
Each method in the API takes an optional (can be null) resultListener parameter used to asynchronously report successes or errors:
private IMiddlewareResultCallback.Stub resultCallback = new IMiddlewareResultCallback.Stub() { @Override public void error(Bundle info) throws RemoteException { // ... } @Override public void success () throws RemoteException { // ... } };
Your application must request a connection to the middleware by using an intent with a specific action string and passing the previously created ServiceConnection object:
Intent intent = new Intent("it.cnr.isti.giraff.android.BIND_TO_MIDDLEWARE"); bindService(intent, middlewareConnection, Context.BIND_AUTO_CREATE);
Make sure to unbind from the middleware when you’re done, for example in the onDestroy method of your main activity:
@Override protected void onDestroy() { super.onDestroy(); if (middlewareConnection != null) { unbindService(middlewareConnection); } }
Once you have the IMiddleware object you can call remote methods on it, for example for announcing sensors on the sensor bus or to publish data on the context bus.
In the onCreate() method of the SensorsService class we create the SensorsDescriptors class:
public void onCreate() { super.onCreate(); Log.d(TAG, "onCreate()"); // foreground notification builder Intent intent = new Intent(SensorsService.this, MainActivity.class); notificationBuilder = new NotificationCompat.Builder(SensorsService.this); notificationBuilder.setSmallIcon(R.drawable.icon); notificationBuilder.setContentTitle(getResources().getText(R.string.app_name)); notificationBuilder.setContentText(getResources().getText(R.string.foreground_message)); notificationBuilder.setContentIntent(PendingIntent.getActivity(SensorsService.this, -1, intent, 0)); sensorsDescriptors = new SensorsDescriptors(this); }
We called the method sensorsDescriptors.initialize(middleware) in the SensorsService class when we implemented the service connection. This method calls the method to create the sensor descriptors, announces them and then register a listener to be notified about the sensor changes:
public void initialize(IMiddleware middleware) { this.middleware = middleware; try { createSensorDescriptors (); announce(); register(); } catch (RemoteException e) { Log.e(TAG, "cannot announce"); } }
The createSensorDescriptors() creates a descriptor for the accelerometer sensor present on the device using the id taken directly from the database (for sake of consistency with the GiraffPlus ecosystem we choose to publish data on the subtree sensor/ of the context bus, please respect this constraint):
private void createSensorDescriptors () throws RemoteException { Sensor sensor; try { // Gets all the local sensors in a JSON array JSONArray localSensors = new JSONArray (middleware.getSensorsByManufacturer ("ISTI-CNR")); // for each desirable sensor for (int i = 0; i < sensors.size(); i++) { int sensorCode = sensors.keyAt (i); String sensorType = sensors.get (sensorCode); // probe sensor and add to the list sensor = sensorManager.getDefaultSensor (sensorCode); if (sensor != null) { JSONObject sensDesc = null; Log.d(TAG, "found: " + sensorType); for (int j = 0; j < localSensors.length (); j++) if (((String)((JSONObject) localSensors.get (j)).get ("type")).toString ().equals (sensorType)) { sensDesc = (JSONObject) localSensors.get (j); break; } if (sensDesc != null) { // The sensor descriptor exists in the database Bundle serviceDescriptor = new Bundle (); serviceDescriptor.putString ("serviceBusTopic", "sensor/" + sensDesc.getString ("id")); serviceDescriptor.putString ("id", sensDesc.getString ("id")); serviceDescriptor.putString ("type", sensDesc.getString ("type")); serviceDescriptor.putString ("category", "sensor"); serviceDescriptor.putString ("contextBusTopic", "sensor/" + sensDesc.getString ("id")); serviceDescriptor.putString ("description", sensor.toString()); JSONArray params = sensDesc.getJSONArray ("messageFormat"); Bundle[] messageFormat = new Bundle[params.length ()]; for (int j = 0; j < params.length (); j++) { messageFormat[j] = new Bundle (); messageFormat[j].putString ("name", ((JSONObject) params.get (j)).getString ("name")); messageFormat[j].putString ("unit", ((JSONObject) params.get (j)).getString ("unit")); } serviceDescriptor.putParcelableArray ("messageFormat", messageFormat); serviceDescriptor.putParcelableArray ("recipient", new Bundle[] {}); serviceDescriptors.put(sensorCode, new Pair<Sensor, Bundle>(sensor, serviceDescriptor)); } } } } catch (JSONException ex) { Log.e (TAG, ex.getMessage ()); } }
private void announce(IMiddleware middleware) throws RemoteException { this.middleware = middleware; for (int i = 0; i < SERVICE_DESCRIPTORS.size(); i++) { Bundle serviceDescriptor = SERVICE_DESCRIPTORS.get(SERVICE_DESCRIPTORS.keyAt(i)).second; // CALL TO THE MIDDLEWARE API - ANNOUNCE middleware.announce(serviceDescriptor, new IMiddlewareResultCallback.Stub() { @Override public void success () throws RemoteException { Log.d (TAG, "Announce successful"); } @Override public void error (Bundle info) throws RemoteException { Log.e (TAG, "Announce failed"); } }); } }
private void register() { for (int i = 0; i < SERVICE_DESCRIPTORS.size(); i++) { Sensor sensor = SERVICE_DESCRIPTORS.get(SERVICE_DESCRIPTORS.keyAt(i)).first; sensorManager.registerListener(this, sensor, SENSORS_DELAY); } }
Once we have announced the sensors on the service bus we can begin to publish sensed data. We call the publish method of the middleware each time a sensor change is detected:
public void onSensorChanged(SensorEvent event) { Bundle serviceDescriptor = SERVICE_DESCRIPTORS.get(event.sensor.getType()).second; String topic = serviceDescriptor.getString("contextBusTopic"); // build payload JSONObject payload = new JSONObject(); try { payload.put("sensor_id", serviceDescriptor.getString("id")); payload.put("timestamp", event.timestamp); JSONObject values = new JSONObject(); values.put("Acc_X", event.values[0]); values.put("Acc_Y", event.values[1]); values.put("Acc_z", event.values[2]); payload.put("values", values); } catch (JSONException e) { return; } // publish message try { middleware.publish(topic, payload.toString(), false, resultCallbackImpl); } catch (RemoteException e) { Log.e(TAG, "cannot publish"); } }
When we close our application it is mandatory to remove announced sensors from the service bus (for sake of consistency in the GiraffPlus ecosystem). This is done when we disconnect from service in the onServiceDisconnected method, when we call the onServiceDisconnected method:
public void finalize() { try { remove(); unregister(); } catch (RemoteException e) { Log.e(TAG, "cannot remove"); } }
private void remove() throws RemoteException { for (int i = 0; i < SERVICE_DESCRIPTORS.size(); i++) { Bundle serviceDescriptor = SERVICE_DESCRIPTORS.get(SERVICE_DESCRIPTORS.keyAt(i)).second; middleware.remove(serviceDescriptor, new IMiddlewareResultCallback.Stub() { @Override public void success () throws RemoteException { Log.d (TAG, "Remove successful"); } @Override public void error (Bundle info) throws RemoteException { Log.e (TAG, "Remove failed"); } }); } }
private void unregister() { sensorManager.unregisterListener(this); }