<< Indiva HomeWriting New Services for IndivaThis chapter describes how to write a new service in Indiva. An Indiva service basically consists of three components:
Indiva provides some classes to make writing a new service easy. To better explain this, I will show how to write a new service through an example. Suppose we want to write a new service that control a light switch. Let's call it ilight. The following figure shows the class anatomy of the service. Classes in white are classes defined in Indiva, and classes in gray are those written by the user.
The EngineThis service engine is the component that performs the task of the service. For example, in Indiva Video Encoder, the engine is a VideoAgent object. For a routing switcher controller, the engine is the class that communicates with the routing switcher through the RS232 interface. In the example of ilight, the engine is the class called LightSwitch. Let's define the class as follows:
Class LightSwitch
LightSwitch public init { } {
$self set state_ "on"
$self set dim_ "100"
}
LightSwitch public toggle {} {
if {[$self set state_] == "on"} {
$self set state_ "off"
} else {
$self set state_ "on"
}
}
LightSwitch public on {} {
$self set state_ "on"
}
LightSwitch public off {} {
$self set state_ "off"
}
LightSwitch public dim {b} {
$self set dim_ $b
}
OK, We are not actually controlling any light here, just pretend that we are. Light switch has a method toggle that can turn the light on and off, and a method dim that can change the brightness of the light. Indiva does not impose any restriction on the engine. You can write it anyway you want. This makes it easy to write new services using existing Mash classes. The Application ClassA service's application class should inherit from the class IndivaServiceApplication. This inheritance provides your application class with a few things:
To make your application class complete, you must override the methods new_service and get_service in IndivaServiceApplication. new_service creates and returns a new service object, while get_service returns the service object. get_service takes in an additional argument, the service id, which identify the service object to return. This only makes sense if you have multiple service objects. Normally you just ignore this parameter. (See IVEApplication for an example of multiple service objects.) Since Indiva services run Active Service protocol, your service exits if it does not receive alive messages from the Indiva Manager after a certain period of time. Before your service exits, a callback "on_exit" is called. You can specify this callback to customize what to do before your service exits. This is a useful place to clean up any unfinished matters, such as closing files. To customize this callback, call method "on_exit", passing in the your code to run before the service exits. Here is a sample Application class:
import IndivaServiceApplication
Class ILightApplication -superclass IndivaServiceApplication
ILightApplication instproc init { argv } {
$self next $argv "ilight"
$self instvar light_
set light_ [new LightSwitch]
$self on_exit "delete $light_"
}
ILightApplication instproc new_service { } {
$self instvar light_
$self set service_ [new ILightService $light_]
}
ILightApplication instproc get_service { service_id } {
return [$self set service_]
}
The ServiceThe service component interface is the bridge between the engine and the Indiva manager. Your service component must be a subclass of IndivaService. The Indiva Manager will make RPC calls to your IndivaService subclass. It expects the following methods:
Your service should override these methods to do what is appropriate for your intended task. These methods are detailed next. start { args }The start method is called when the Indiva Manager is ready to run your service. For example, for Indiva Video Encoder, start causes frames to be captured, encoded and transmitted. For "always-on" services, such as camera control, this does not make sense. stop { args }The stop method stops whatever the service is running. For "one-time" services such as switching a video signal, this does not make sense. gui { parent }gui takes in a Tk widget path parent, and returns a list of two elements. The first element must be a script for creating, laying out and PACKING GUI elements, using parent as parent. It MUST not contain any code that configures widget callbacks. The second element must be a list of {type item command} that specify the callbacks of the widgets. type can be either widget or variable. If type is widget, then command will be configured as the callback to widget whose path is item. If type is variable, then command will be configured as the write trace callback for variable whose name is item. In both cases, command must be written as if it will be executed locally. Note that if you want to add a callback to a traced variable, you need to ensure that the variable name is unique over all possible services. One way to do this is to incorperate the string $parent into the name of the variable. event { type args }This method is called whenever the Indiva Manager receives some events that are meant for this service. type indicate the type of events. configure { args }This method pass a list of {-key value} pairs for configuring services. You can safely ignore options that are not appropriate for your service. friendlyname { }This method should return a short, user-friendly description of the service. This may be displayed to the user by applications. (Recommendation: at most three words, don't tell a story :-) ) Here is the ilight example:
import IndivaService
Class ILightService -superclass IndivaService
ILightService public init { light } {
$self set light_ $light
$self next
}
ILightService public start { args } {
[$self set light_] on
}
ILightService public stop { args } {
[$self set light_] off
}
ILightService public gui { parent } {
$self instvar light_
set f $parent.f
set layout "
frame $f
button $f.toggle -text "Toggle Light"
scale $f.dim -from 0 -to 100
pack $f $f.toggle $f.dim
"
lappend commands widget $f.toggle "$light_ toggle"
lappend commands widget $f.dim "$light_ dim"
return [list $layout $commands]
}
ILightService public friendlyname {} {
return "Lighting Control"
}
# sample call: "configure /home/garage/light -switch on -dim 30"
ILightService public configure {args} {
$self instvar light_
foreach {key value} $args {
switch -- $key {
-switch {
if {$value == "on" || $value == "off"} {
eval $light_ $value
}
}
-dim {
$light_ dim $value
}
}
}
}
Here is another version of ILight instproc gui, using an option menu. This shows how traced varilable callback can be used. Note the use of multiple backslash escapes on the variable name.
ILightService public gui { parent } {
$self instvar light_
set f $parent.f
set layout "
frame $f
global switch${parent}
tk_optionMenu $f.toggle switch${parent} on off
scale $f.dim -from 0 -to 100
pack $f $f.toggle $f.dim
"
lappend commands variable switch${parent} "eval $light_ \$\{switch${parent}\}"
lappend commands widget $f.dim "$light_ dim"
return [list $layout $commands]
}
Coding ConventionIt is recommended that all indiva services follow the coding conventions below, in addition to the normal OTcl convention.
Defining New Actions in IndivaThe previous chapter describes how you can write a new service. But your service won't do anything unless it is launched by Indiva Manager. This chapter describe how to make Indiva Manager aware of your new service. As describe before, one of the attribute associated with resources is action. The value of the action attribute tells Indiva Manager how to handle a particular type of resource. For example, Indiva Manager currently define the action encode to launch ive and encode video stream from composite video output ports. To make Indiva Manager aware of your services, you need to define your own action. The rest of this chapter shows what is involve in this process. We will demostrate this using the ilight example. First, pick a name for your action. Then, define a subclass to IMgrAction. The name of the new class must be "IMgrAction/", followed by the name of the action, with first character capitalized, i.e., IMgrAction/[string toupper $name_of_action 0]. This is how Indiva Manager knows which class to use when it encounters a particular action. Indiva Manager expects certain methods to be defined in your IMgrAction subclass, and will call those methods when creating/deleting/reconfiguring flows. The methods will be describe next. You must define the following proc (NOT instproc) methods in your IMgrAction subclass.
The arguments to these methods are as follows. $mgr is a handle to the Indiva Manager object. $graph is a handle to the resource graph. $flow is the flow object we are currently creating or reconfiguring. $from and $to are the names of the resources at both end of the flow segment, where our service is going to be attached to. $froms is similar to $from, except that it is a list of resources. $ctrl is the handle to a remote service object that controls the service. $arguments is a list of additional arguments we might want to pass to the service. We now describe the methods in more details. get_offerMethod get_offer will be called when Indiva Manager needs to decide which path to take to create a flow. The argument $flow at this point is not yet fully constructed. get_offer must look at the list of resources in $froms and decide which resource to use. The chosen resource is returned. If you don't care much about resource allocation, robustness and stuffs, you can just return [lindex $froms 0]. create_serventThis method will be called to launch a remote service agent ("servent" in Active Service terminology). This could happen either as part of flow creation, or when Indiva Manager wants a control a remote device. Typically, you call "rexec" to launch the service. The argument to rexec is the name of the script to launch. rexec also supports the following options:
start_serviceThis is call to start a remote service. You typically calls $ctrl start here, passing in appropriate parameters derived from the resources. redir_service
|