Filters all follow a very typical format.
includes MotephoneTypes;
module TYPICAL_FILTER_MODULE {
provides {
interface FilterControl as FC_Reset;
interface FilterControl as FC_Enable;
// additional FilterControl interfaces
interface SingleFilter as Filter_p;
}
uses {
interface SingleFilter as Filter_u;
interface Leds;
interface SampleSet;
}
}
implementation {
#include "SnackConstants.h"
bool m_enabled;
// other global variables
command result_t FC_Reset.setValue(uint16_t val){
// reset variables
return SUCCESS;
}
command result_t FC_Reset.getValue(uint16_t *val){
if (val) *val = 0;
return SUCCESS;
}
command result_t FC_Enable.setValue(uint16_t val){
// enable/disable filter
return SUCCESS;
}
command result_t FC_Enable.getValue(uint16_t *val){
if (val) { *val = m_enabled; return SUCCESS; }
return FAIL;
}
// more FilterControl definitions
command uint16_t Filter_p.bufferSize(uint16_t headersize){
uint16_t bsz;
// allocate room for attributes
bsz = call Filter_u.bufferSize(headersize + sizeof(attr_t) + sizeof(/*attribute*/));
// calculate data compression/inflation
return bsz;
}
command void Filter_p.put(sample_set_t *input){
// process data and push data along
call Filter_u.put(input);
}
}
FilterControl Interface
A Filter is controlled through its FilterControl interfaces. The interface
itself is composed of two simple function: a set and a get function.
Example: vango/tos/interface/FilterControl.nc
...
interface FilterControl {
command result_t setValue(uint16_t val);
command result_t getValue(uint16_t *val);
}
When a filter declares a FilterControl interface (excluding Reset and Enable),
it is providing a means by which the user (via the ControlM module) can tweak a filter
variable or modify the way the filter processes data. The set function
modifies a variable, the get function probes the variable state. For example,
the GateM filters data that does not meet a certain threshold requirements; a
FC_Threshold FilterControl interface is added to adjust the threshold at
which data is cut off.
Example: vango/tos/lib/GateM.nc
...
module GateM {
provides {
...
interface FilterControl as FC_Threshold;
...
}
...
}
implementation {
...
uint16_t m_threshold;
...
command result_t FC_Threshold.setValue(uint16_t val){
dbg(DBG_USR1, "%s [%d] - val %d\n",__FILE__,__LINE__, val);
m_threshold = val;
return SUCCESS;
}
command result_t FC_Threshold.getValue(uint16_t *val){
if (val) { *val = m_threshold; return SUCCESS; }
return FAIL;
}
...
}
SingleFilter
In addition to controlling the filter, data needs to be pushed though the
module; the SingleFilter interface provides that functionality. There are two
finctions in the interface: put and bufferSize.
Example: vango/tos/interface/SingleFilter.nc
...
interface SingleFilter {
command void put(sample_set_t *input);
command uint16_t bufferSize(uint16_t headersize);
}
Put is used to push data through a filter, bufferSize is used to calculate
the amount of data that can be passed through a sample set.
A filter that only provides a SingleFilter interface is a data sink
(i.e. PacketizeM). A filter that only uses a SingleFilter interface is a data
source (i.e. Sampler). The SingleFilter interface, however, is both provided and
used in a typical filter. A filter that both uses and provides a SingleFilter
interface is declaring that it can take a SampleSet as input on the uses
interface and output a SampleSet on the provides interface. GateM is such a
filter; it takes a SampleSet as input, checks the amplitude of the data, marks
SampleSets that passes the threshold requirement, and passes the SampleSet to
its provides interface.
There can be more that one SingleFilter that is provided or used in any
filter. For example, the Sampler uses three different SingleFilter interfaces.
The Sampler is capable of using the MSP430's three different DMAs to gather
ADC data form three different channels. Since the data generated from the
different channels may need to be processed by different filter stacks, each
channel has its own SingleFilter interface.
BufferSize
The bufferSize function is used to calculate how large a buffer the Sampler
needs to allocate to accommodate all of the Attributes and data requirements
of the filters. The bufferSize function is needed in a high-speed sampling
framework to adjust the amount of data generated at the Sampler per buffer so
as not to overload or underload the filters.
The size of a SampleSet is dynamically calculated during run time on the
mote. Most of a SampleSet's size comes from the data buffer and the number of
attributes added by the Filters. The SampleSet size calculation is initiated
by the Sampler via the bufferSize interface function. Each filter is given a
say in the expected data buffer and attribute size.
In a provides bufferSize function, a Filter first calculates whether it needs
to use attributes and passes the total attribute size to the next filter in
the chain in the form of a parameter in a bufferSize function call. In the
end, this information lets Packetizer know how large the header will be -
attributes are part of the header.
...
command uint16_t Filter_p.bufferSize(uint16_t headersize){
uint16_t bsz;
bsz = call Filter_u.bufferSize(headersize + sizeof(attr_t) + sizeof(/*attribute*/));
// calculate data compression/inflation
return bsz;
}
...
The very last Filter bufferSize call will enter the Packetizer's
bufferSize function. The function will use the headersize parameter to
allocate room for the packet header and set aside the rest of the packet for
the data buffer. The size of this data buffer will be returned by the
Packetizer's bufferSize function to be the buffer size of the data attribute
in the SampleSet. This number, however, is not set in stone. As each Filter's
bufferSize function is given control during the unwinding of the function
call chain; the local Filter's bufferSize function is allowed to adjust the
size of the data buffer. In the case of a compressor, the buffer limit can
be increased resulting in more data collected by the Sampler; because of
the compression, the data will still fit into a single packet.
Creating New Filters
New Filters are created by taking the TYPICAL_FILTER_MODULE, adding a
FilterControl interface for each variable that is modifiable outside of the
Filter, and implementing the SingleFilter put and bufferSize functions. (Enable
and reset FilterControl interfaces are not needed, but make sence in most cases).
Adding New Filters to VanGo
Adding the newly created Filter to the VanGo framework takes a little more
effort. There are five main additions that need to take place:
-
Add an identifier to the filter_param_t enumeration in the MotephoneTypes.nc
file for each FilterControl interface. The accepted identifier format is:
"FILTER_PARAM_" + name of the Filter + "_" + name of the interface. This
identifier will be used to identify the Filter's interfaces in ControlM.
...
FILTER_PARAM_GATE_ENABLE,
FILTER_PARAM_GATE_RESET,
FILTER_PARAM_GATE_THRESHOLD,
FILTER_PARAM_GATE_COUNT,
FILTER_PARAM_GATE_SUMMARY,
...
-
Create a default value for each FilterControl interface to be used to
initialize the Filter at boot-up. The defaults are added to the
SnackConstants.h file and follow the format: name of Filter + "___" + name
of interface.
...
#define GateM____
#define GateM____reset (0)
#define GateM____threshold (1000)
#define GateM____count (10)
#define GateM____summary (1)
#define GateM____enable (1)
...
-
Wire the new Filter's FilterControl interfaces to ControlM using the ControlM's
single parameterized FilterControl interface. ControlM can control many
different Filters because of this parameterization; this type of
parameterization is best described as a named port - ControlM controls
Filters through a named port. In addition to wiring the Filter to ControlM,
wire any other necessary modules (i.e.: SampleSetM, Leds).
...
ControlM.FilterControl[ FILTER_PARAM_GATE_RESET ] -> GateM.FC_Reset;
ControlM.FilterControl[ FILTER_PARAM_GATE_ENABLE ] -> GateM.FC_Enable;
ControlM.FilterControl[ FILTER_PARAM_GATE_THRESHOLD ] -> GateM.FC_Threshold;
ControlM.FilterControl[ FILTER_PARAM_GATE_COUNT ] -> GateM.FC_Count;
ControlM.FilterControl[ FILTER_PARAM_GATE_SUMMARY ] -> GateM.FC_Summary;
...
-
Instruct ControlM how to initialize the Filter at boot-up by setting the
Filter's FilterControl interfaces to their default values as defined in the
SnackConstants.h file.
...
void set_defaults(){
...
call FilterControl.setValue[ FILTER_PARAM_GATE_RESET ]((uint16_t)GateM____reset);
call FilterControl.setValue[ FILTER_PARAM_GATE_THRESHOLD ]((uint16_t)GateM____threshold);
call FilterControl.setValue[ FILTER_PARAM_GATE_COUNT ]((uint16_t)GateM____count);
call FilterControl.setValue[ FILTER_PARAM_GATE_SUMMARY ]((uint16_t)GateM____summary);
call FilterControl.setValue[ FILTER_PARAM_GATE_ENABLE ]((uint16_t)GateM____enable);
...
}
...
-
To actuate the filters variables on a mote from the microserver, instruct
ControlSocketM how to interpret the new commands. Commands usually follow
the format: name of Filter + "-" + name of command. After creating the
names, bind them in the get_command_id function in ControlSocketM.nc to the
filter_param_t identifier created above.
filter_param_t get_command_id(char *str_command){
...
else if (strcmp(str_command,"gate-enable")==0){
return FILTER_PARAM_GATE_ENABLE;
}
else if (strcmp(str_command,"gate-threshold")==0){
return FILTER_PARAM_GATE_THRESHOLD;
}
else if (strcmp(str_command,"gate-count")==0){
return FILTER_PARAM_GATE_COUNT;
}
else if (strcmp(str_command,"gate-summary")==0){
return FILTER_PARAM_GATE_SUMMARY;
}
...
}
After binding the name, tell ControSocketM how to interpret the command's
parameters. There are three types of parameters that can be passed to a filter
command: bool, numeric, and blob. Bool consists of either 0 for false and anything
else for true, or the litterals "true" or "false"; numeric consists of an integer;
and blob consists of a comma-separated collection of integers. To instruct
ControlSocketM how to interpret each commands parameters, list the identifier
in the build_attr_block function under the appropriate case in the switch
statement.
attr_block_t * build_attr_block(char *buf, uint8_t len, addr_t *addr){
...
switch(cmd){
...
// --- BOOL ---
case FILTER_PARAM_GATE_ENABLE:
case FILTER_PARAM_GATE_SUMMARY:
...
// --- NUMERIC ---
...
case FILTER_PARAM_GATE_THRESHOLD:
case FILTER_PARAM_GATE_COUNT:
...
// --- NONE ---
case FILTER_PARAM_GATE_RESET:
// --- BLOB ---
...
}
Filters at Run Time
Filters are scheduled to run synchronously as TinyOS tasks from the TinyOS
task scheduler. The Sampler starts the filter chain data processing and as
long as no Filter post tasks to process data, the data will be pushed through
all the Filters uninterrupted.
Filter Variable Actuation
The filter variables can be modified remotely by sending commands over a
socket. The VanGo microserver application has a built-in server that listens
for incoming connections on the default port 8003. Once a connection is
established, simple ASCII command strings will trigger the microserver to
actuate mote filter parameters. The commands are defined in the
ControlSocketM.nc module and follow a common format:
Example: vango/tos/platform/emstar/ControlSocketM.nc
...
/*
msg format :
destination <colon> command-1 <semi-colon> command-2 ...
command format (whitespace delimited):
command [<space>|<tab>]+ value
value format (a value containing more than one field is comma delimited):
value [,next_value]*
recognized non-numeric destinations: local, broadcast
recognized non-numeric values: true, false
values are 16-bit unsigned
e.g.:
'broadcast: fir-set-vector 4,12,1,0,3; gate-enable true\n'
*/
...
Other than using conventional socket programming, commands can be sent to the
microserver using tools like telnet or netcat.
Example: Command Session
broadcast: gate-enable false
2: gate-enable true; gate-threshold 150
4: gate-enable true; gate-threshold 300; gate-summary true
ControlSocketM runs an EmTOS socket server on port 8000 used to receive the
ASCII commands. The received commands are translated into attributes and then
into attribute blocks, packetized, and sent over the radio to the motes. The
attributes are composed of the attribute type and the attribute value. The
attribute type, in this case, is the command number as defined in the
filter_param_t enumeration in the
MotephonesTypes.nc file and the attribute value is the value passed with the
command.
|
In the current implementation of attributes, there is a limit to the number of
bytes attributes can take up in total. The max is defined as the
MAX_ATTR_BYTES #define in the MotephonesTypes.nc file. |
Example: vango/tos/lib/MotephonesTypes.nc
...
typedef struct attr_s {
uint8_t type;
uint8_t length;
uint8_t value[0];
} __attribute__ ((packed)) attr_t;
struct attr_block_s {
uint8_t current;
uint8_t count;
uint8_t attrs[MAX_ATTR_BYTES];
} __attribute__ ((packed));
...
All received packets are forwarded to ControlM; ControlM reads the attributes
in the attribute block and calls the appropriate filter function to change the
value of the filter. ControlM calls its FilterControl setValue function with
the attribute type as the command identifier and the attribute as the value to
the function.
Example: vango/tos/lib/ControlM.nc
...
call FilterControl.setValue[attr->type](*((uint16_t *)(attr->value)));
...