-
Notifications
You must be signed in to change notification settings - Fork 4
Driver Development Guide
The core idea of the wsn-device-drivers is to make the concurrent execution of Operations transparent for the driver developer, so he has not to care about threading. For this purpose drivers are divided in the following parts:
-
OperationRunnable
: The smallest amount of work that can be implemented by the device developer to perform actions on the device. -
Operation
: A container for theOperationRunnable
that manages the lifecycle and providing an execution context. -
OperationExecutor
: A executor that serialize the execution of all incomingOperation
s. The common used implementation is theExecutorServiceOperationExecutor
For better understanding, the workflow from the call of an operation on the Device
interface till the operation execution on the physical device is described as follows:
- Method call on the
Device
instance. - An instance of the
OperationRunnable
is created and all parameters are set which are needed by the runnable. - An instance of the
Operation
is created and the the previous instantiatedOperationRunnable
is injected. - The created
Operation
container is submitted to theOperationExecutor
. - The
OperationExecutor
add the operation to a queue and returns anOperationFuture
for thisOperation
. This future allows to control theOperation
. - When no other
Operation
is currently running pop the nextOperation
from the Queue.
When the Operation
is submitted to the OperationExecutor
it can have the following states:
- Waiting: The
Operation
was added to the queue and is now waiting for execution. This is the start state. - Running: The
Operation
is now executed in a thread. During the change to this state theonExecute
method is called on the givenOperationCallback
. - Done: The
Operation
has finished successfully. During the change to this state theonSuccess
method is called on the givenOperationCallback
. - Canceled: The
Operation
was canceled by the application. During the change to this state theonCancel
method is called on the givenOperationCallback
. - Timed out: The
Operation
reached the timeout defined during the method call onDevice
. During the change to this state theonFailure
method is called on the givenOperationCallback
with aTimeoutException
as parameter. - Excepted: The
Operation
threw anException
. During the change to this state theonFailure
method is called on the givenOperationCallback
with the caught exception as parameter.
Note: The last four states are finish states.
During the idle time the OperationExecutor
a idle operation is executed which copies all data coming from the device to an OutputStream
which can be read by the InputStream
object returned by the getInputStream
method of the Device
. This allows to read from the device in a save way cause only when no Operation
is running the InputStream
returns data.
Cause the wsn-device-drivers try to solve concurrency problems, you should only use the wsn-device-drivers for operations on the device that need a serialized execution. For other operations this framework would only add unnecessary complexity.
The following describes how to create a simple "Hello World" driver. It is recommended to look in the already provided devices to get more informations on how to create a driver. Also note that the wsn-device-drivers make heavy use of Google Guice so you should be familiar with it.
Add the following dependencies to you pom.xml
<dependency>
<groupId>de.uniluebeck.itm</groupId>
<artifactId>wsn-device-drivers-core</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
An OperationRunnable
defines the smallest amount of work that can be executed in the wsn-device-drivers. The interface is defined below:
public interface OperationRunnable<T> {
T run(ProgressManager progressManager, OperationContext context) throws Exception;
}
As you can see the OperationRunnble
is a generic class. The T
defines the return value of your operation. When your operation returns nothing you can use the Java type Void
.
When implementing the run
method you get a ProgressManager
and an OperationContext
instance. With the ProgressManager
you can publish the current progress of your operation to the caller. The OperationContext
allows to get the current cancel state and to reuse already written operations. When throwing an Exception
it is caught by the Operation
and automatically handled for you.
Cause the wsn-device-drivers define a set of operations that can be executed on the device only the following OperationRunnable
s can be implemented:
ProgramOperation
ReadMacOperation
WriteMacOperation
ReadFlashOperation
WriteFlashOperation
EraseFlashOperation
ResetOperation
SendOperation
Because most of the OperationRunnable
s need parameters like an ROM image or addresses most of them have methods for setting these. To prevent rewriting this code there are the following abstract classes:
AbstractProgramOperation
AbstractReadFlashOperation
AbstractSendOperation
AbstractWriteFlashOperation
AbstractWriteMacAddressOperation
After choosing an appropriate OperationRunnable
you can start implementing the interface. For our example we will implement the AbstractProgramOperation
. This can look like the following:
public class HelloWorldProgramOperation extends AbstractProgramOperation {
public Void run(ProgressManager progressManager, OperationContext context) throws Exception {
System.out.println(new String(getImage()));
return null;
}
}
This Operation uses the getImage()
method from AbstractProgramOperation
to get the image as byte array and prints it on System.out
.
Before we can run this Operation, we need to create a Guice Module. A Guice Module can look like below:
public class HelloWorldModule extends AbstractModule {
public HelloWorldModule() {
this(null);
}
public void configure() {
bind(ProgramOperation.class).to(HelloWorldProgramOperation.class);
}
}
In this module we bind the ProgramOperation
to the HelloWorldProgramOperation
. When you have more than this operation you can add all operations here.
Now we can initialize a Device and run the operation:
Device device = Guice.createInjector(new DeviceModule(), new HelloWorldModule());
device.program("Hello World".getBytes(), 1000, null);
This will output Hello World
on System.out
.
The ProgressManager
allows you to publish the current process of the operation to the onProgress(float progress)
method of the OperationCallback
. Inside the run
method you can use the ProgressManager
as follows:
// Do some work
progressManager.worked(0.5f);
// Do again some work
progressManager.worked(0.25f);
// Do again some work
progressManager.done();
In this example we do the half work and indicate this by calling the worked
method with the done amount of work. This will automatically call the onProgress
method of the OperationCallback
. In the next step we have done again some work and give again the amount of work to the worked
method. With the done
method we indicate that all work is done. This will call the onProgress
method with 1.0f
which is the maximum amount that can be worked in sum. Calling the done
method is optional. This will be automatically called when run
method finished. The done
method is only needed when working with sub ProgressManager
which is shown below:
// Do some work
progressManager.worked(0.5f);
// Create a sub progress manager that manages 0.25f of the overall progress.
ProgressManager subProgressManager = progressManager.createSub(0.25f);
// Do some work
subProgressManager.worked(0.5f);
// Create a sub sub progress manager that manages 0.75f of the sub progress manager overall progress.
ProgressManager subSubProgressManager = subProgressManager.createSub(0.5f);
// Do some work
subSubProgressManager.worked(0.75f);
// We finish our work. Calling done() is now not optional.
subSubProgressManager.done();
// We already reached with the subSubProgressManager a worked amount of 1.0f
subProgressManager.done();
// We have 0.25f work left for the progressManager
progressManager.worked(0.125f);
Creating sub ProgressManager
is handy when you have methods where you want to allocate a unit of work to and you do not want to calculate the single worked values yourself. Sub ProgressManager
become really useful when calling sub operations via the run
method of the OperationContext
. This is explained below.
The OperationContext
can be used for observing if the operation has to be canceled and for starting other operations.
For observating if an operation has to be finish you can use the following code:
if (context.isCanceled()) {
// Exit the run method
}
This code should be used in long running operations.
When creating your operation it is sometimes handy to reuse already created operations. For example you want to program your device but want to save the mac address before flashing. See the code below.
public class MyProgramOperation extends AbstractProgramOperation {
private final ReadMacAddressOperation readMacAddressOperation;
@Inject
public MyProgramOperation(ReadMacAddressOperation readMacAddressOperation) {
this.readMacAddressOperation = readMacAddressOperation;
}
public Void run(ProgressManager progressManager, OperationContext context) throws Exception {
MacAddress macAddress = context.run(readMacAddressOperation, progressManager.createSub(0.25f));
// Program the device
return null;
}
}
In this example we use Guice to inject the already defined ReadMacAddressOperation
. Note that you need in this case the following code in your Guice Module.
bind(ReadMacAddressOperation.class).to(MyReadMacAddressOperation.class);
The injected operation can now be used to execute it via the run
method of the OperationContext
. As you can see we use the createSub
method of the ProgressManager
to associate 0.25f
of the work to the ReadMacAddressOperation
. Note when this operation finished your overall operation progress is 0.25f
.
When you need more than one instance of the same operation you can use a Provider
from the Guice Framework. See the example below.
public class MyProgramOperation extends AbstractProgramOperation {
private final Provider<ReadFlashOperation> readFlashOperationProvider;
@Inject
public MyProgramOperation(Provider<ReadFlashOperation> readFlashOperationProvider) {
this.readFlashOperationProvider = readFlashOperationProvider;
}
public Void run(ProgressManager progressManager, OperationContext context) throws Exception {
for(int i = 0; i < 10; ++i) {
ReadFlashOperation readFlashOperation = readFlashOperationProvider.get();
// Set the address of the readFlashOperation
byte[] result = context.run(readFlashOperation, progressManager.createSub(0.1f));
// Do something with the result
}
return null;
}
}
In this example the Provider
returns always a new instance when calling the get()
method. This should be always done instead of reusing existing operations.
The drivers are intended to work with external devices. For abstracting the connection via e.g. the serial port or sockets the Connection
interface can be used. Currently the wsn-device-drivers are optimized for the usage of serial port connections. Please review the SimpleSerialPortConnection
to get an idea of how creating new types of connections.