This document is a guide to adding new device types to RM by means of custom Device Control Unit Providers. The guide is aimed at developers familiar with the control unit model (see Conceptual Guide).
Information about the generic issues in implementing a Control Unit Provider, are described in the Basic Principles of Control Unit Provider SPIs document.
Device CU Provider Interface
As described in the Basic Principles in Control Unit Provider SPIs guide, to add a new device type to the RM device management system, you have to implement a Device Root Control Unit Provider. In particular, a Device Control Unit Provider should implement the com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootControlUnitProvider interface.
The Device Root Control Unit Provider SPI is supported only on a backend framework having the management server role. It is exported by the RM Control Unit Lib bundle (packages/gdm/gdmlib.jar) and is handled by the Control Unit Manager bundle (packages/gdm/cu.jar).
During development, you can also use the lib/api/gdm-api.jar JAR file for compilation and for code complete in a Java editor.
Example of a Device CU Provider
The code snippets used in this document for illustration of how to implement a Device Control Unit Provider, form a complete provider which has the following features:
The provider supports control units of type "my.device.cu" with friendly type name "My Device".
Each device control unit has state variables "ID", "name" and "on" state, and actions turn on and turn off.
Device control units are created with arguments "ID", "name" and "on" state.
The example Device Control Unit Provider is available as a part of the Control Units Demo (demo/controlunits) distributed with RM. The Device Control Unit Provider is packed in a separate bundle deviceproviderdemo.jar located in demo/controlunits/bundles, and its source code available in the demo/controlunits/src under the demo.controlunits.device Java package.
Communicate with the Generic Device Manager
To be able to use the tree and control unit storage of RM as well as to notify the system of changes in a device root control unit, it is recommended that you save the DeviceRootSystemContext instance passed to the init method of your DeviceRootControlUnitProvider object.
DeviceRootSystemContext subclasses ControlUnitSystemContext by adding the getDeviceManager method for access to the Generic Device Manager (com.prosyst.mprm.admin.devices.DeviceManager). Providers can use it to access the RM device management tree instead of getting the Device Manager as a service from the backend OSGi framework. Another option that DeviceRootSystemContext offers is that you can use it to store into or retrieve from the storage the persistent capabilities of a device instance (see "System Profiles and Properties" from the System Package documentation).
In addition, in the body of the init method you can perform some initialization activities, such as persistently storing the metatype of the provided device type in the RM Control Unit Database by calling the addMetadataRecord method. Later, on startup you can get the saved Metatype Provider from the database.
The activities during provider initialization are provided in the following example, which is an implementation of the init provider method – saving the DeviceRootSystemContext, and loading and storing device control unit metatype. Metatype loading is done from an XML within the provider bundle by using the com.prosyst.mprm.util.metatype.MetaTypeProviderInfo utility (see "Provide Device Metadata" subsection below).
import java.io.IOException; import java.io.InputStream; import java.util.Hashtable; import org.osgi.framework.BundleContext; import org.osgi.service.metatype.MetaTypeProvider; import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootControlUnitProvider; import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootSystemContext; import com.prosyst.mprm.common.ManagementException; import com.prosyst.mprm.util.metatype.MetaTypeProviderInfo; public class MyDeviceCUProvider implements DeviceRootControlUnitProvider { DeviceRootSystemContext ctx; static final String CU_TYPE = "my.device.cu"; static final String CU_VERSION = "1.0.0"; MetaTypeProvider metatype; BundleContext bc; public void init(DeviceRootSystemContext ctx) { // Save the DeviceRootSystemContext for later use this.ctx = ctx; cuIds = new Hashtable(); // Load the metatype metatype = getMetatypeProvider(); try { // Save persistently the metatype ctx.addMetadataRecord(CU_VERSION, metatype); } catch (ManagementException e) { e.printStackTrace(); } } // Helper method which retrieves the metatype from an XML within // the provider's bundle JAR private MetaTypeProvider getMetatypeProvider() { try { InputStream xml = MyDeviceCUProvider.class.getResourceAsStream("/cu.xml"); if (xml != null) { MetaTypeProviderInfo metaInfo = new MetaTypeProviderInfo(xml); return metaInfo; } } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; }}Register and Delete Devices
If the Device Root Control Unit Provider is going to support user-initiated device registration and unregistration, the provider should implement the createControlUnit and destroyControlUnitDeviceRootControlUnitProvider methods.
Create a Device Root Control Unit
The DeviceRootControlUnitProvider's createControlUnit method is expected to contact the target device if online, or in case the device is not available at the moment of the registration request, to schedule asynchronously getting initial device information when the device becomes available. In both cases, the createControlUnit method should return an ID string containing a unique ID for the device root control unit. The ID should be wrapped in the passed ProviderResult object.
Optionally, you can benefit from the RM persistent storage to avoid data losses on system restarts.
Save the
ControlUnitStatein the Control Unit Database by calling thesaveControlUnitStatemethod of theDeviceRootSystemContextinstance, previously provided by RM in a call to theinitmethod (see "Communicate with the Generic Device Manager" section below).Add node information (parent path and node display name) to the Device Tree Database by using the addTreeNodeInfo method of the
DeviceRootSystemContextinstance. This information is required in GUI applications, such as the console, for displaying the device within the device tree.
Finally, to be able to interact efficiently with administration application based on RM device management, it is recommended to notify the system of the newly-created device root control unit by calling the controlUnitEvent method of the DeviceRootSystemContext instance.
The couple of examples below contain statements for creating control units. The first one holds an example implementation of the createControlUnit provider method. The arguments to create a device control unit are device ID, device name and device on/off. The provider checks if the metatype for the control unit type ("my.device.cu") is available and if the device ID is not duplicate. Next, it creates an instance of MyDevice from the second example and populates it with data according to the passed constructor arguments.
import org.osgi.service.metatype.MetaTypeProvider; import com.prosyst.mprm.admin.devices.ControlUnitID; import com.prosyst.mprm.admin.devices.event.ControlUnitEvent; import com.prosyst.mprm.backend.ms.cu.spi.ControlUnitState;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootControlUnitProvider;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootSystemContext;import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;import com.prosyst.mprm.common.ManagementException; public class MyDeviceCUProvider implements DeviceRootControlUnitProvider { DeviceRootSystemContext ctx = null; . . . public void createControlUnit(String parentPath, String constructorId, Object args, ProviderResult result) throws ManagementException { Object[] args1 = (Object[]) args; boolean on = ((Boolean)args1[0]).booleanValue(); String cuId = (String) args1[1]; String displayName = (String)args1[2]; ControlUnitID cuIdInst = new ControlUnitID(CU_TYPE, cuId); if (ctx.retrieveControlUnitState(cuIdInst) != null) { result.setResult(null, new Exception("Device with ID " + cuId + " already exists in RM!")); return; } // Create the state of the new device and save it persistently MyDevice device = new MyDevice(this); device.setID(cuId); device.setOn(on); device.setName(displayName); ctx.saveControlUnitState(device); // Specifying node information about the newly added device node ctx.addTreeNodeInfo(parentPath, cuId, displayName, null); // Fire an event about the newly-created device ctx.controlUnitEvent(new ControlUnitEvent(ControlUnitEvent.CONTROL_UNIT_ADDED, CU_TYPE, cuId, CU_TYPE, cuId)); // Return the device ID to RM result.setResult(cuId, null); } . . .} The ControlUnitState implementation is provided in the following example:
import java.util.Vector;import com.prosyst.mprm.admin.devices.ControlUnitID;import com.prosyst.mprm.admin.devices.event.StateVariableEvent;import com.prosyst.mprm.backend.ms.cu.spi.ControlUnitState;public class MyDevice implements ControlUnitState { private MetaTypeProvider metatype = null; private ControlUnitID cuIdInst = null; private MyDeviceCUProvider provider = null; static final String ON_VAR_ID = "on"; boolean on; static final String NAME_VAR_ID = "name"; String name = ""; static final String ID_VAR_ID = "id"; String id = ""; static final String ON_ACTION_ID = "turnOn"; static final String OFF_ACTION_ID = "turnOff"; private Vector stateInterface; public MyDevice(MyDeviceCUProvider provider) { this.provider = provider; }// Methods inherited from ControlUnitState public ControlUnitID getControlUnitID() { return new ControlUnitID(MyDeviceCUProvider.CU_TYPE, id); } public String[] getStateInterface() { String[] result = new String[stateInterface.size()]; stateInterface.copyInto(result); return result; } public Object getStateVariable(String stateVarId) { if (stateVarId.equals(ON_VAR_ID)) { return new Boolean(on); } else if (stateVarId.equals(ID_VAR_ID)) { return id; } else if (stateVarId.equals(NAME_VAR_ID)) { return name; } return null; } // Methods for populating the state parameters public void setName(String name) { this.name = name; addInStateInterface(NAME_VAR_ID); } public void setID() { this.id = id; addInStateInterface(ID_VAR_ID); } public void setOn(boolean on) { this.on = on; addInStateInterface(ON_VAR_ID); provider.getContext().stateVariableChanged(new StateVariableEvent(MyDeviceCUProvider.CU_TYPE, id, MyDeviceCUProvider.CU_TYPE, id, ON_VAR_ID, new Boolean(on))); } private void addInStateInterface(String sv) { if(stateInterface == null) { stateInterface = new Vector(); } if(! stateInterface.contains(sv)) { stateInterface.addElement(sv); } }}Destroy a Device Root Control Unit
The destroyControlUnit method is called when a request to delete a device has been made through the Generic Device Manager. The Device Root Control Unit Provider is expected to remove the device from the tree and from the RM storage, and fire an event. Finally, set the control unit ID as a string to the ProviderResult object.
The code that follows contains an example of how a Device Control Unit Provider can remove a device from RM. It implements the destroyControlUnit method for device unregistration.
import com.prosyst.mprm.admin.devices.ControlUnitID;import com.prosyst.mprm.admin.devices.event.ControlUnitEvent;import com.prosyst.mprm.backend.ms.cu.spi.ControlUnitState;import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootControlUnitProvider;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootSystemContext;import com.prosyst.mprm.common.ManagementException;public class MyDeviceCUProvider implements DeviceRootControlUnitProvider { DeviceRootSystemContext ctx = null; static final String CU_TYPE = "my.device.cu"; . . . public void destroyControlUnit(String cuId, ProviderResult result) throws ManagementException { // Check if the device exists ControlUnitState cu = ctx.retrieveControlUnitState(new ControlUnitID(CU_TYPE, cuId)); if (cu == null) { result.setResult(null, new ManagementException("[MyDeviceCUProvider] Device with this ID does not exist!")); } // Delete the node data of the device ctx.deleteTreeNodeInfo(cuId); // Delete the saved device state ctx.deleteControlUnitState(new ControlUnitID(CU_TYPE, cuId, CU_TYPE, cuId)); // Fire an event about the removed device ctx.controlUnitEvent(new ControlUnitEvent(ControlUnitEvent.CONTROL_UNIT_REMOVED, CU_TYPE, cuId, CU_TYPE, cuId)); // Return the ID of the removed device to RM result.setResult(cuId, null); } . . .} Provide Proper Device State
When a request from a management application comes for getting the actual state of a device through the Generic Device Manager, the getControlUnit method of the Device Root Control Unit Provider is called. The provider should return a ControlUnitState instance reflecting the state of the device.
The code below contains a simple implementation of the getControlUnit method, which retrieves the state from the RM Control Unit Database. The provider is implemented to save each state variable change, i.e. each state change, in the RM storage (see the examples from the "Support Action Invocation" section below ).
import com.prosyst.mprm.admin.devices.ControlUnitID;import com.prosyst.mprm.backend.ms.cu.spi.ControlUnitState;import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootControlUnitProvider;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootSystemContext;import com.prosyst.mprm.common.ManagementException;public class MyDeviceCUProvider implements DeviceRootControlUnitProvider { DeviceRootSystemContext ctx = null; static final String CU_TYPE = "my.device.cu"; . . . public void getControlUnit(String cuId, ProviderResult result) throws ManagementException { // Retrieve the device state from the RM database // and return it to RM ControlUnitID cuId = new ControlUnitID(CU_TYPE, cuId); ControlUnitState cu = ctx.retrieveControlUnitState(cuId); if (cu == null) { result.setResult(cu, new Exception("[MyDeviceCUProvider] Device does not exist!")); } result.setResult(cu, null); } . . .} Support State Variable Query
In the cases where a management application is interested only in the value of a particular device property, represented as a state variable, the Generic Device Manager will call the queryStateVariable method of the provider. The provider should return the value of the variable, obeying the correct value type, in the passed ProviderResult object.
ControlUnitState.The example below illustrates satisfying a query for a specific state variable. The queryStateVariable method retrieves the state of the target control unit from the RM storage and gets the value of the requested variable from the state.
import com.prosyst.mprm.admin.devices.ControlUnitID; import com.prosyst.mprm.backend.ms.cu.spi.ControlUnitState;import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult; import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootControlUnitProvider; import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootSystemContext; import com.prosyst.mprm.common.ManagementException;public class MyDeviceCUProvider implements DeviceRootControlUnitProvider { DeviceRootSystemContext ctx = null; static final String CU_TYPE = "my.device.cu"; . . . public void queryStateVariable(String cuId, String varId, ProviderResult result) throws ManagementException {// Get the component's state from the RM database ControlUnitState cu = ctx.retrieveControlUnitState(new ControlUnitID(CU_TYPE, cuId));// Get the variable's value and return it to RM if (varId.equals(MyDevice.ON_VAR_ID)) { result.setResult((Boolean) cu.getStateVariable(varId), null); } else if (varId.equals(MyDevice.NAME_VAR_ID) || varId.equals(MyDevice.ID_VAR_ID)) { result.setResult((String) cu.getStateVariable(varId), null); } } . . .}Support Action Invocation
From the Generic Device Manager
When a management application invokes an action on a device root control unit by using the Generic Device Manager (refer to System-Wide Device Management), the RM system locates the proper Device Root Control Unit Provider and invokes the provider's invokeAction(String controlUnitId,String actionId, Object arguments, ProviderResult result) method. Hence, if the control units managed by your provider support some actions, in the body of this method implement the operations for sending the corresponding command to the device. If an action returns some output, set it to the passed ProviderResult instance. If the action does not have an output, set null as result in ProviderResult.
The following example implements the invokeAction method, dedicated to calls from the Generic Device Manager, adding support of the turn on and turn off actions – the actions simply change the value of the "on" state variable of the target device control unit, save the change in the RM storage and fire a change event.
import com.prosyst.mprm.admin.devices.ControlUnitID;import com.prosyst.mprm.admin.devices.event.StateVariableEvent;import com.prosyst.mprm.backend.ms.cu.spi.ControlUnitState;import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootControlUnitProvider;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootSystemContext;import com.prosyst.mprm.common.ManagementException; public class MyDeviceCUProvider implements DeviceRootControlUnitProvider {DeviceRootSystemContext ctx = null;static final String CU_TYPE = "my.device.cu"; . . . public void invokeAction(String cuId, String actionId, Object args, ProviderResult result) { try { invokeAction0(cuId, actionId); } catch (ManagementException e) { result.setResult(null, e); } result.setResult(null, null);} // Helper method, turning the device on and off private void invokeAction0(String cuId, String actionId) throws ManagementException { ControlUnitID targetCuId = new ControlUnitID(CU_TYPE, cuId); // Get the current device state from the database ControlUnitState cuState = (ControlUnitState) ctx.retrieveControlUnitState(targetCuId); if (cuState == null) { throw new ManagementException("[MyDeviceCUProvider] Device does not exist!"); } else { // Change the state of the device and save it persistently MyDevice newState = new MyDevice(this); newState.setID(id); newState.setName((String) cuState.getStateVariable(MyDevice.NAME_VAR_ID)); if (actionId.equals(MyDevice.ON_ACTION_ID)) { newState.setOn(true); ctx.saveControlUnitState(newState); } else if (actionId.equals(MyDevice.OFF_ACTION_ID)) { newState.setOn(false); ctx.saveControlUnitState(newState); } else { throw new ManagementException("[MyDeviceCUProvider] Unsupported action!"); } } } . . .}From the Operation Manager
Another aspect of control unit actions is the ability for a management application to invoke an action on a control unit by using management operations created by using the Rule Engine (refer to Scripting and Rule-Based Management). In this case, the system will call the invokeAction(String controlUnitId, String actionId, Object arguments,com.prosyst.mprm.backend.ms.commands.spi.InterpretationResult result) method of your Device Root Control Unit Provider. You can indicate success, warning or error in the InterpretationResult.
Here, it is not required to execute the action immediately if the target device is not available - you can make the wrapping operation pending over the device my simply returning the invokeAction method without setting any result to the passed InterpretationResult instance. Later, when the device becomes available, you can finish the action execution by setting result to the InterpretationResult or by calling the forcePendingCommand method of the DeviceRootSystemContext (refer to "Communicate with the Generic Device Manager" section below), which will result with another call to the invokeAction method.
In addition, you don't have to save the whole InterpretationResult instance provided in the invokeAction method if the action will be executed later. Instead, you can use the createInterpretationResult method of the provider-specific DeviceRootSystemContext supplying the operation ID and command ID saved from the InterpretationResult argument of the invokeAction method.
The following example contains an implementation of the invokeAction method for action invocation by means of management commands. The handling of the turn on and turn off actions is the same as in listing above, but this time the invocation is postponed for 2 minutes. During the 2 minutes, RM will consider the operation as pending over the device.
import com.prosyst.mprm.admin.devices.ControlUnitID;import com.prosyst.mprm.admin.devices.event.StateVariableEvent;import com.prosyst.mprm.backend.ms.commands.spi.InterpretationResult;import com.prosyst.mprm.backend.ms.cu.spi.ControlUnitState;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootControlUnitProvider;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootSystemContext;import com.prosyst.mprm.common.ManagementException;public class MyDeviceCUProvider implements DeviceRootControlUnitProvider { DeviceRootSystemContext ctx; static final String CU_TYPE = "my.device.cu"; ForceCommandTimer cmdThread; . . . public void invokeAction(String cuId, String actionId, Object args, final InterpretationResult result) throws Exception { // Start the thread for pending operations over the handled devices. The thread is common to all // all devices if (cmdThread == null) { cmdThread = new ForceCommandTimer(); cmdThread.setName("Command Pender"); cmdThread.start(); } // Schedule the command for execution after a certain timeout cmdThread.forceCommand(result.getOperationId(), cuId, result.getCommandId(), actionId); } // Helper method, turning the device on and off private void invokeAction0(String cuId, String actionId) throws ManagementException { ControlUnitID targetCuId = new ControlUnitID(CU_TYPE, cuId); // Get the current device state from the database ControlUnitState cuState = (ControlUnitState) ctx.retrieveControlUnitState(targetCuId); if (cuState == null) { throw new ManagementException("[MyDeviceCUProvider] Device does not exist!"); } else { // Change the state of the device and save it persistently MyDevice newState = new MyDevice(metatype, targetCuId, this); if (actionId.equals(MyDevice.ON_ACTION_ID)) { newState.setOn(true); ctx.saveControlUnitState(newState); } else if (actionId.equals(MyDevice.OFF_ACTION_ID)) { newState.setOn(false); ctx.saveControlUnitState(newState); } else { throw new ManagementException("[MyDeviceCUProvider] Unsupported action!"); } }} // Makes the current operation pending over a device for 2 minutes class ForceCommandTimer extends Thread { Object monitor = new Object(); private boolean running = true; private boolean force = false; private String commandId; private String operationId; private String cuId; private String actionId; public void run() { while (running) { synchronized (monitor) { try { monitor.wait(2 * 60 * 1000); if (force) { try { // Get the result to wrap the output into InterpretationResult result = ctx.createInterpretationResult(operationId, CU_TYPE, cuId, commandId); // Turn the device on or off invokeAction0(cuId, actionId); // Indicate success to complete the operation result.setSuccess(null); } catch (ManagementException e) { e.printStackTrace(); } force = false; } } catch (InterruptedException e) { e.printStackTrace(); } } } } synchronized void forceCommand(String operationId, String cuId, String commandId, String actionId) { this.operationId = operationId; this.cuId = cuId; this.commandId = commandId; this.actionId = actionId; force = true; } // Kills the thread synchronized void dispose() { force = false; running = false; synchronized (monitor) { monitor.notify(); } } } . . .}Synchronize RM with the Actual Device State
On request from a management application for synchronization of a device state, the Generic Device Manager will call the synchronizeState method of your Device Root Control Unit Provider. In return, the provider should contact the device and get the required information. The ProviderResult, passed back to RM, should contain a ControlUnitState with the synchronized device properties.
The following code implements the synchronizeState method by simply calls the getControlUnit method to get the saved state of the target control unit.
import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootControlUnitProvider;import com.prosyst.mprm.common.ManagementException;public class MyDeviceCUProvider implements DeviceRootControlUnitProvider { . . . public void synchronizeState(String cuId, ProviderResult result) throws ManagementException { getControlUnit(cuId, result); } . . .} Provide Device Metadata
To describe the interface of the provided device root control unit type to RM and management applications, the Device Root Control Unit Provider should return a Metatype Provider (org.osgi.service.metatype.MetaTypeProvider) in its getMetaType method.
The Metatype Provider should export metadata with AttributeDefinitions for state variables and extended ObjectClassDefinitions for actions.
Basically, there are two ways for providing a device root Metatype Provider:
By implementing all required interfaces from the OSGi Metatype API (
org.osgi.service.metatype) and from the (org.mbs.services.metatype) Metatype Extension API.By writing a metadata XML following the Bosch Digital defined DTD and converting it to a Metatype Provider through the
com.prosyst.mprm.util.metatype.MetaTypeProviderInfoutility (available in the System Package).
The example below returns the MetaTypeProvider instance generated from the metadata XML from the subsequent example through the MetaTypeProviderInfo utility in the previous listings.
import java.io.IOException;import java.io.InputStream;import java.net.URL;import com.prosyst.mprm.util.metatype.MetaTypeProviderInfo;import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult; import org.osgi.service.metatype.MetaTypeProvider; import com.prosyst.mprm.common.ManagementException;public class MyDeviceCUProvider implements DeviceRootControlUnitProvider { MetaTypeProvider metatype = null; static final String CU_TYPE = "my.device.cu"; static final String CU_VERSION = "1.0.0"; . . . public void getMetaType(ProviderResult result) throws ManagementException { MetatypeProvider mtp = cuSystemContext.getMetadataRecord (CU_TYPE, CU_VERSION); if (mtp == null) { mtp = loadMetatype(); } result.setResult(mtp, null); } private MetatypeProvider loadMetatype() throws ManagementException { try { URL mtpURL = bc.getBundle().getResource("device.xml"); InputStream in = mtpURL.openStream(); MetaTypeProviderInfo mtp = new MetaTypeProviderInfo(in); cuSystemContext.addMetadataRecord(CU_VERSION, mtp); return mtp; } catch (Exception e) { throw new ManagementException(e); } } . . .}The example below provides metadata XML for a device control unit type:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><!DOCTYPE metatype-provider SYSTEM "metatype.dtd"><metatype-provider> <objectclass> <locale>en</locale> <name>My Device</name> <id>my.device.cu</id> <description/> <attribute modifier="req" load="true"> <name>Device ID</name> <id>id</id> <description/> <type>&string;</type> <cardinality>0</cardinality> </attribute> <attribute modifier="req"> <name>Device Display Name</name> <id>name</id> <description/> <type>&string;</type> <cardinality>0</cardinality> </attribute> <attribute modifier="req"> <name>Switched State</name> <id>on</id> <description/> <type>&boolean;</type> <cardinality>0</cardinality> </attribute> <objectclass> <locale>en</locale> <name>Create My Device</name> <id>$create.</id> <description/> <attribute modifier="in"> <name>On</name> <id>on</id> <description/> <type>&boolean;</type> <cardinality>0</cardinality> <value> <scalar>false</scalar> </value> </attribute> <attribute modifier="in"> <name>ID</name> <id>id</id> <description/> <type>&string;</type> <cardinality>0</cardinality> </attribute> <attribute modifier="in"> <name>Name</name> <id>name</id> <description/> <type>&string;</type> <cardinality>0</cardinality> </attribute> </objectclass> <objectclass> <locale>en</locale> <name>Delete Device</name> <id>$destroy</id> <description/> </objectclass> <objectclass> <locale>en</locale> <name>Turn ON</name> <id>turnOn</id> <description/> </objectclass> <objectclass> <locale>en</locale> <name>Turn OFF</name> <id>turnOff</id> <description/> </objectclass> </objectclass></metatype-provider>Handle Node Properties
This section discusses exporting node inheritable properties on behalf of a Device Root Control Unit Provider, which are accessible from the device management tree and represent parameters specific to the supported device type.
Export Node Properties
As Node Property Metatypes
You can export node properties in the form of metadata compliant with the LDAP-like format defined by the OSGi Service Compendium. Metadata for a specific device type is distributed in one or more Metatype Providers (org.osgi.service.metatype.MetaTypeProvider). Such a Metatype Provider should export one main ObjectClassDefinition containing AttributeDefinitions, each reserved for a node property. The node property AttributeDefinitions has to be defined as "required".
There are two options in implementing a node Metatype Provider to RM:
By directly implementing the OSGi
MetaTypeProviderinterface.By writing an XML file according to the Bosch Digitalmetadata XML format and easily convert it to a
MetaTypeProviderinstance. Node metatype support re-uses the format of metadata XML files for control units, whose DTD is defined in the Control Unit Metatyping document. The XML file can be converted to aMetaTypeProviderby instantiating thecom.prosyst.mprm.util.metatype.MetaTypeProviderInfoutility class providing anInputStreamto the XML file as argument to theMetaTypeProviderInfoconstructor.
Having supplied a MetaTypeProvider implementation, to export the node metadata to RM, register the MetaTypeProvider object as a service in the OSGi framework of the proper management server. The Metatype Provider service should have the following registration properties:
DeviceRootControlUnitProvider.MTP_EXTENSION_KEYwith valueDeviceRootControlUnitProvider.NODE_PROPERTIES.org.mbs.services.cu.ControlConstants.TYPEwith value the device type the properties are associated with.org.mbs.services.cu.ControlConstants.VERSIONwith value the device type version the properties are compatible with.
ObjectClassDefinition.Following is an example node metatype defined in an XML file (e.g. called nodemtp.xml and placed in the root directory of the provider bundle JAR file). The node metatype is registered for the device type, my.device.cu, used in this document to illustrate the basic principles in developing Device Root Control Unit Providers.
The metadata XML in the code below describes two node properties – an integer number with ID prop.one and a string array with ID prop.two.
<?xml version="1.0" encoding="UTF-8"?><metatype-provider> <objectclass> <locale/> <name>Simple Node Metatype</name> <id>My Device</id> <description/> <attribute modifier="req"> <name>Property One</name> <id>prop.one</id> <description/> <type>∫</type> <cardinality>0</cardinality> <value> <scalar>5</scalar> </value> </attribute> <attribute modifier="req"> <name>Property Two</name> <id>prop.two</id> <description/> <type>&string;</type> <cardinality>10</cardinality> <value> <array> <scalar>String One</scalar> <scalar>String Two</scalar> </array> </value> </attribute> </objectclass></metatype-provider>The example below contains the Java code for registering the node metatype for devices of type my.device.cu.
import java.net.URL;import java.util.Hashtable; import org.mbs.services.cu.ControlConstants;import org.osgi.framework.BundleActivator;import org.osgi.framework.BundleContext;import org.osgi.framework.ServiceRegistration;import org.osgi.service.metatype.MetaTypeProvider;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootControlUnitProvider;import com.prosyst.mprm.util.metatype.MetaTypeProviderInfo; public class MyCUProviderActivator implements BundleActivator { private ServiceRegistration nodeMtpReg; public void start(BundleContext bc) throws Exception { . . . Hashtable nodeMtpProps = new Hashtable(3); nodeMtpProps.put(DeviceRootControlUnitProvider.MTP_EXTENSION_KEY, DeviceRootControlUnitProvider.NODE_PROPERTIES); nodeMtpProps.put(ControlConstants.TYPE, MyDeviceCUProvider.CU_TYPE); nodeMtpProps.put(ControlConstants.VERSION, MyDeviceCUProvider.CU_VERSION); URL xmlIn = bc.getBundle().getResource("nodemtp.xml"); MetaTypeProviderInfo metatype = new MetaTypeProviderInfo(xmlIn.openStream()); nodeMtpReg = bc.registerService(MetaTypeProvider.class.getName(), metatype, nodeMtpProps); } . . .}Directly in the Device Manager
You can export node properties along with initial values by directly calling the setProperties method on the relevant node (device or device group). For concrete device nodes you might also use the setNodeProperties method of the DeviceRootSystemContext callback instance.
Get the Values of Node Properties
To get the current values of the node properties related to the device type that you provide:
Use the
getNodePropertyof theDeviceRootSystemContextcallback instance – Through this method you can get the value of a node property associated with the specific device node.Use the
getPropertymethod on acom.prosyst.mprm.admin.devices.Noderetrieved fromcom.prosyst.mprm.admin.devices.DeviceManager(get it withgetDeviceManagerofDeviceRootSystemContext)– Through this call you can get the value of a node property not only for the device node but for another node of the device management tree. Refer to the System-Wide Device Management document for more information on handling nodes with the help of the Device Manager API.
Provide Icons for the Device (Optional)
To provide icons that will represent each device of the same type and its actions, you need to implement the getIcon method. Depending on the passed object class ID and on the device root control unit version, you can provide different icons.
Basically, you can retrieve icons from a file or from the system-provided control unit storage. To handle icons in the CU storage, use the addIcon, getIcon and deleteIcons methods of the provider's DeviceRootSystemContext.
The ProviderResult passed to the getIcon method, should contain an input stream to the icon to be displayed.
The code below provides separate icons (located in the root of the provider JAR file) device type, device constructor, device destructor, turn on action and turn off action.
import java.io.IOException;import java.io.InputStream;import java.net.URL;import org.osgi.framework.BundleContext; import com.prosyst.mprm.backend.ms.cu.spi.ProviderResult;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootControlUnitProvider;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootSystemContext;import com.prosyst.mprm.common.ManagementException; public class MyDeviceCUProvider implements DeviceRootControlUnitProvider { . . . public void getIcon(String arg0, String ocdId, int version, ProviderResult result) throws ManagementException { try { String iconPath = ""; if (ocdId.equals(CU_TYPE)) { iconPath = "/type.png"; } else if (ocdId.equals("$create.")) { iconPath = "/device.png"; } else if (ocdId.equals("$destroy")) { iconPath = "/delete.png"; } else if (ocdId.equals("turnOn")) { iconPath = "/on.png"; } else if (ocdId.equals("turnOff")) { iconPath = "/off.png"; } URL iconURL = bc.getBundle().getEntry(iconPath); InputStream iconIn = iconURL.openStream(); result.setResult(iconIn, null); } catch (IOException e) { result.setResult(null, e); e.printStackTrace(); } } . . .}Register the Device Root CU Provider as a Service
After implementing the DeviceRootControlUnitProvider interface, you need to register it as an OSGi-compliant service on the RM backend hosts with the management server role. The service must have at least the "mbs.control.type" registration property (org.mbs.services.cu.ControlConstants.TYPE). Its value shows the type of the devices this Device Root Control Unit Provider service handles. In addition, the provider service can have the "mbs.control.version" (org.mbs.services.cu.ControlConstants.VERSION) registration property indicating the supported device type version in case versioning is supported.
The code below registers Device Provider as a service: it is a bundle activator, which registers and unregisters the example Device Root Control Unit Provider as a service on a backend framework for device type "my.device.cu" and version "1.0".
import java.util.Hashtable;import org.mbs.services.cu.ControlConstants;import org.osgi.framework.BundleActivator;import org.osgi.framework.BundleContext;import org.osgi.framework.ServiceReference;import org.osgi.framework.ServiceRegistration;import com.prosyst.mprm.backend.ms.cu.spi.root.DeviceRootControlUnitProvider;public class MyCUProviderActivator implements BundleActivator { ServiceRegistration sReg = null; private MyDeviceCUProvider provider; public void start(BundleContext bc) throws Exception { provider = new MyDeviceCUProvider(bc); Hashtable props = new Hashtable(); props.put(ControlConstants.TYPE, MyDeviceCUProvider.CU_TYPE); props.put(ControlConstants.VERSION, MyDeviceCUProvider.CU_VERSION); sReg = bc.registerService(DeviceRootControlUnitProvider.class.getName(), provider, props); } public void stop(BundleContext bc) throws Exception { // Kill the pending thread if (provider != null) { if (provider.cmdThread != null) { provider.cmdThread.dispose(); provider.cmdThread = null; } } if (sReg != null) { sReg.unregister(); sReg = null; } }}