-
Notifications
You must be signed in to change notification settings - Fork 8
WiseML
The main goal of WiseML was to allow standardized storage of traces of senser network experiments. Shawn now supports to create simulations based on such WiseML files. For this, the all new "wiseml" application has been added to Shawn's app folder. To use the WiseML app, just activate it in the CMake configurator, it does not require any special libraries or other prerequisities.
Shawn's WiseML application currently supports the following features of WiseML:
- Reading out the "setup" section to set up the simulation topology ** description ** instances: creating nodes accordingly ** instances: creating unidirectional links
- Reading out scenario timestamps ** and ** and (currently only handles activation and deactivation of a node's links) ** Node postions (aka mobility of nodes) ** Capability values (aka sensor values)
- Reading out trace timestamps ** Node postions (aka mobility of nodes) ** Capability values (aka sensor values)
Concerning mobility, Shawn does not support any kind of interpolation between timestamps in the current version.
The WiseML app supports capabilities now and updates them whenever new values are read out of a trace or scenario timestamp.
Shawn can also write out WiseML files now, including scenarios and traces. It supports all features/tags/informations, the WiseML format description provides, but it needs some setup and programming work to use everything of it. Because of that, we created an own tutorial subchapter for the writer.
To configure Shawn to use WiseML, just configure your world accordingly. For all supported features to work correctly, the simulation world needs to be set up to use the SimpleEdgeModel and the new WiseMlCommunicationModel: : prepare_world edge_model=simple comm_model=wiseml
After that, call the wiseml_world task to actually create your world. There are several parameters needed to configure WiseML. The required "file" parameter should point to your WiseMl file containing the world and trace informations. "scenario_id" and "trace_id" parameters define, which scenario and trace to use in the simulation. Both are optional, so there is no need to actually use any trace informations, e.g. for only using WiseML to setup the topology.
The optional double parameter "timefactor" can be used to define the relationship between WiseML timestamps and Shawn simulation rounds. It is used as a multiplication factor. So for example if timestamps are meant to be "minutes" and simulations rounds should be seconds, multiplicate the timestamp with a timefactor of 60.0.
As usual, the "processors" parameter can be used to define the type of processors to be used in the simulation. An example is shown below: : wiseml_world file=../src/apps/wiseml/examples/example.wiseml scenario_id=urn:wisebed:scenario:sics:1 trace_id=urn:wisebed:trace:sics:1 timefactor=2 processors=helloworld_random
Reading out an automatically updated capability value during a WiseML simulation bases on the "Reading" app, which was created to simulate sensor readings and behaviors. Reading isn't part of the WiseML app, but is an own app by itself. Therefore to use WiseML sensors, the Reading app has to be activated too. So make shure that it is activated in your CMake configuration.
Practically, reading out WiseML capabilities works pretty much the same as reading out Reading sensors. That is, get the desired SensorFactory from the SensorKeeper which resides on the SimulationController and use it to create or get a sensor instance for a specific capablity. This can be done e.g. during the boot-process of a processor (so in your implementation of the "boot" method). The following example shows how such a boot method may look to get a capability sensor: : void MyExampleProcessor:: boot( void ) throw() : { : //Get the simulation controller from the node (which is the owner of the processor): : SimulationController* sim_controller_ = &owner_w().world_w().simulation_controller_w(); : //Get the Sensor(Factory)Keeper: : reading::SensorKeeper* sensor_keeper_ = sim_controller_->keeper_by_name_wreading::SensorKeeper("SensorKeeper"); : //Get the factory of the sensor type you want to use, in this example a string sensor: : reading::SensorFactory factory = sensor_keeper_->find_w("wiseml_string_sensor").get(); : //Cast it to the right type, in this case a WisemlStringSensorFactory: : WisemlStringSensorFactory string_factory = dynamic_cast<WisemlStringSensorFactory>(factory); : //Create a sensor for the wanted capability: : WisemlStringSensor some_sensor_ = string_factory->create("battery", owner_w()); : //Get the sensor's value: : String my_sensor_value = some_sensor_->value(); : }
An example processor is provided which does nearly the same as in the above example, besides some variables are member variables. It writes output to the console if it's sensor value has changed. See apps/wiseml/examples/wiseml_example_processor.h/.cpp. To use it, enable the "examples" app in CMake (if not already done) and change your Shawn configuration file to use the wiseml_example processor by adding the variable "processors=wiseml_example" when calling the wiseml_world task.
Shawn now supports writing simulation topologies, traces and scenarios to WiseML files. This section describes, how to setup and use this writer. It bases on some internal interfaces, which may be accessed by code in your application, and some simulation tasks which may be used in your shawn configuration file. Note that the WiseML writer, unlike the reader, does not need a special world to be set up (like "wiseml_world"). Instead, to make fully use of the writer, you have to provide at least your own processor type, which makes use of the writer's data pipeline (providing the data needed for the WIseML file).
The Setup section of a WiseML files covers basic topology information, as the origin, time informations, description, default values as well as node and link instances and some more. Shawn does not use or even provides some of these informations, but it provides interfaces and simulation tasks which allow to set them up manually in your Shawn configuration file.
: wiseml_setup x=0.0 y=0.0 z=0.0 phi=0.0 theta=0.0 start=<current_time> end= timeinfo_unit=rounds timeinfo_factor=1.0 interpolation= description= The WiseML setup task allows to set the origin (x,y,z,phi,theta as double values), the timestrings which should be used as start time and - if it is needed to set by hand - end time, the unit used for timestamps, a double typed multiplication factor which may be used to alter Shawn's simulation time, the interpolation type to be used to interpolate mobility between timestamps (currently not used by Shawn) and a description string. Please note, that Shawn does not support whitespace characters to be used in string parameters inside a Shawn configuration file. Regarding the description parameter, underline characters are internally replaced with whitespaces, so use them instead.
WiseML allows two different types of timeinfo descriptions. In the first variant, you may provide the start time, end time and unit (for timestamps). In the second variant, you may provide the start time, the duration and the unit. This task supports both. By default, Shawn generates the start timestring automatically, sets unit to "rounds" and fills in automatically the "duration" parameter when the "wiseml_writer" task is called. However, you may also define an end timestring here. Duration is ignored in this case.
Internally, this task also starts gathering the topology, taking the current simulation topology to create node and link instance sections. So be shure to call this task, 'after' the world and it's topology have been created. Generally, this task is meant to be called directly after the world setup, but you may also call it later, e.g. after some simulation steps.
All parameters are optional. Default parameters are the ones shown above.
Examples: : wiseml_setup : wiseml_setup x=5 y=.9 z=-6 start=2010-06-21T11:57:17Z description=A_sample_simulation
: wiseml_default_capability name= datatype= unit= value= forlink=false The WiseML default capability task adds a default node or link capability (a capability which every node/link should have automatically). It takes four string parameters (name, datatype, unit, value) to specify the capability and one bool parameter to specify the target (node==false, which is default / link==true). Besides the "forlink" parameter, all of these parameters are required, so you have to set them up correctly.
Examples: : wiseml_default_capability name=battery datatype=integer unit=percentage value=100 : wiseml_default_capability name=temperature datatype=double unit=celsius value=20 : wiseml_default_capability name=lqi datatype=integer unit=percentage value=100
: wiseml_node_defaults x=0.0 y=0.0 z=0.0 is_gateway=false programDetails= nodetype= description= This task allows to set up the default node section (except for capabilities; see WiseML Default Capability task). It takes as parameters a position (x,y,z as double values), a boolean is_gateway, and string-typed programDetails, nodetype and description fields. All of the parameters are optional, with default values shown above.
Examples: : wiseml_node_defaults : wiseml_node_defaults programDetails=my.node : wiseml_node_defaults x=10 y=0 z=0 programDetails=my.node description=A_node
: wiseml_link_defaults is_virtual=false is_encrypted=false rssi_datatype=<> rssi_unit=<> rssi_default=<> This task allows to set up the default link section (except for capabilities, see default capability task). It takes as parameters boolean typed is_virtual (indicating a virtual link) and is_encrypted (indicating an encrypted connection). Both are optional and set to false by default. Additionally, it is possible to set up the rssi tag by specifying datatype, unit and default value. If one of those three string parameters is set, all three have to be set!
Examples: : wiseml_link_defaults : wiseml_link_defaults is_virtual=true is_encrypted=true : wiseml_link_defaults is_encrypted=true rssi_datatype=integer rssi_unit=percentage rssi_default=100
: wiseml_trace id= Adds a trace with the provided (and required) id to the WisemlDataKeeper.
Examples: : wiseml_trace id=my_trace
: wiseml_scenario id= Adds a scenario with the provided (and required) id to the WisemlDataKeeper.
Examples: : wiseml_scenario id=my_scenario
Additionally to the tasks described above, the setup section can (and in many cases has to) also be configured using the WiseML Data Collector interface. It is available over a keeper called "wiseml_data_keeper" on the simulation controller, but only after the "wiseml_setup" task was called. To access it, add a line like the following to your code:
: WisemlDataKeeper *keeper = simulation_controller.keeper_by_name_w("wiseml_data_keeper");
: WisemlSetupCollector &setup = keeper->setup();
The SetupDataCollector allows to change many setup values by hand. This is useful to for example set a certain link to virtual or make one or more nodes special by setting gateway to true or adding special (non-default) capabilities to it. Some important methods are:
: //Adds a capability to a certain node:
: void add_capability(std::string node, Capability &cap);
: //Adds a capability to a certain link:
: void add_capability(std::string src, std::string tgt, Capability &cap);
: //Allows to change a bool parameter on a certain node. The only parameter
: //currently available and accepted is the "gateway" parameter:
: void set_bool_param(std::string param, bool value, std::string node);
: //Allows to change a bool parameter on a certain link. Parameters
: //currently available and accepted are "virtual" and "encrypted":
: void set_bool_param(std::string param, bool value, std::string src, std::string tgt);
: //Allows to change a string parameter on a certain node. Parameters
: //currently available and accepted are "programDetails", "description" and "nodetype":
: void set_string_param(std::string param, std::string value, std::string node);
WiseML allows to define one or more different scenarios, saving informations about nodes and links being enabled or disabled, sensor values (faulty sensor simulation) and node positions. This data is subdivided into timestamps.
The WiseML Writer provides a special interface, which allows to generate a scenario of this kind. But note, that none of this data is gathered automatically (currently), so you have to explicitly call the interface and provided the necessary data by yourself inside your shawn application.
With the WiseML app being enabled, Shawn provides a special keeper called WisemlDataKeeper. It is available, after the WiseML setup task (see above for more info) has been called. Inside your code, you may access it by calling
: WisemlDataKeeper *keeper = sim_controller.keeper_by_name_w("wiseml_data_keeper");
This keeper allows creation and access to the so called WisemlDataCollector instances. So to create a ScenarioCollector, call
: WisemlScenarioCollector *scenario = keeper->add_scenario("my_scenario");
once (per scenario) in your application. To access it later (after creation), call
: WisemlScenarioCollector *scenario = keeper->scenario("my_scenario");
instead.
WiseML scenarios may also be created in your configuration file instead doing it by code. For this, add the following line somewhere after the call of wiseml_setup: : wiseml_scenario id=my_scenario
The DataCollector classes allow to enter the data, which should be written to the WiseML file. In the case of the scenario data collector class, it provides the following methods:
: enable_node(std::string node);
: disable_node(std::string node);
: enable_link(std::string src, std::string dst);
: disable_link(std::string src, std::string dst);
: node_movement(std::string node);
: capability_value(std::string node, std::string capability, std::string value);
Please note, that e.g. the enable_node method does not really enable any node. Instead, it should be called whenever a node got enabled, to record this action into the WisemL file later on. The same applies to the other methods as well. Every action is automatically written into the timestamp which belongs to the current simulation time. So if your simulation is at round 15 and e.g. your processur is calling something like
: scenario->capability_value("node0815", "battery", "75");
it generates an xml output like
:
:: //...
:: 15
:: //...
::
::: 75
::
:: //...
:
Like the ScenarioCollector, a TraceCollector exists, which works pretty much the same way.
: WisemlDataKeeper *keeper = sim_controller.keeper_by_name_w("wiseml_data_keeper");
: WisemlTraceCollector *trace = keeper->add_trace("my_trace");
: //or if already created:
: WisemlTraceCollector *trace = keeper->trace("my_trace");
Or creating a trace in your configuration file by adding the following line after wiseml_setup:
: wiseml_trace id=my_trace
The TraceCollector provides the following methods:
: // Records node movement (new position automatically gathered from the according node):
: node_movement(std::string node);
: // Records a change of a capability's value:
: capability_value(std::string node, std::string capability, std::string value);
: // Records a change of a link's RSSI value:
: rssi(std::string source; std::string target; std::string value);
WiseML allows to define several scenarios and/or traces in one file. Shawn's WiseML app also supports this. Just create more than one scenario and/or trace
: WisemlDataKeeper *keeper = sim_controller.keeper_by_name_w("wiseml_data_keeper");
: WisemlTraceCollector *trace1 = keeper->add_trace("my_trace:1");
: WisemlTraceCollector *trace2 = keeper->add_trace("my_trace:2");
and access them by their id (in this case my_trace:1 and my_trace:2). The writer task automatically writes all scenarios and traces it finds to the output file.
: wiseml_writer filename=simulation.wiseml The writer task takes all informations and generates a WiseML file out of it. It may be called at any time, but run the above setup tasks before (if needed). Regarding trace and scenario data, it saves all informations gathered between calling "wiseml_setup" and "wiseml_writer". The filename parameter is optional.
This section will cover a complete example of how to save a simulation to a WiseML file, including setting up the topology and recording a trace. The first thing to do is to create a shawn simulation world. In this example, we are creating a simple rectangular world with a random distribution of fifty processors. World size and communication range are set to values which ensure some links. As you may see, we use a special wiseml_example processor type, provided by the WiseML app. We'll show what it does later after this example. : prepare_world edge_model=simple comm_model=disk_graph range=1.5 : rect_world width=25 height=25 count=50 processors=wiseml_example
After setting up the world, we are initializing the WiseML writer by calling the wiseml_setup task. : wiseml_setup x=1.0 y=1.0 z=-1.0 description=An_example_of_the_WiseML_writer
Now, we are able to set up defaults. Note that some of the values are already default values, they are just added here to show how it works. : wiseml_node_defaults is_gateway=false image=wiseml_example nodetype=wiseml_example description=An_example_processor : wiseml_link_defaults is_virtual=false
Additionally to the basic node and link default informations, we will add a default capability (which is a capability every created node has). It is a battery indicator, holding the percentual battery power with a default value of 100 (%). : wiseml_default_capability name=battery datatype=integer unit=percentage value=100
Now, we have to set up a trace in order to get something more written than just the setup section. The example processor used here expects a trace named "example_trace", so we create that one. : wiseml_trace id=example_trace
Now, the WiseML writer has every information it needs before starting to record the trace. Now, we just start the simulation (in this case it does ten simulation rounds). After finishing it, the recorded data is written to a WiseML file called "topology.wiseml". : simulation max_iterations=10 : wiseml_writer filename=topology.wiseml
To put it all together, this is what a complete configuration file would look like. Which is exactly what the example configuration "wiseml_trace_writer.conf" in the "apps/wiseml/examples" does. : prepare_world edge_model=simple comm_model=disk_graph range=1.5 : rect_world width=25 height=25 count=50 processors=wiseml_example : # : wiseml_setup x=1.0 y=1.0 z=-1.0 description=An_example_of_the_WiseML_writer : wiseml_node_defaults is_gateway=false image=wiseml_example nodetype=wiseml_example description=An_example_processor : wiseml_link_defaults is_virtual=false : wiseml_default_capability name=battery datatype=integer unit=percentage value=100 : # : wiseml_trace id=example_trace : # : simulation max_iterations=10 : wiseml_writer filename=topology.wiseml
The example processor provided by WiseML is just a small example of how processor can make use of the WiseML writer interfaces, to record it's data to a trace and/or scenario. Additionally, it is used to show some features regarding reading and playing back WiseML files, so don't be confused about code we don't cover here. The processor's source code lies at "apps/wiseml/examples/wiseml_example_processor.h/.cpp".
Basically, it holds an integer typed member variable "writer_battery_", which contains the current battery value percentage. You may also use some sensor and underlying readings, but for better understanding, we just add this variable here. The initial value is set 100, which means the battery has 100% power.
Inside the work method, which get's automatically called every simulation round on all active processors, we create a random variable between zero and one. If it is lower than 0.5, nothing happens. Otherwise, the processor "worked" and used one percentage of battery power, so the writer_battery_ variable get's decreased by one. If this is the case, the method "value_changed" get's called, which commits the value change to the trace.
: bool worked = (shawn::uniform_random_0i_1i() >= 0.5);
: if(worked && writer_battery_ > 0)
: {
:: writer_battery_--;
:: value_changed("battery");
: }
The method "value_changed" does all the "recording" work. So it first get's the keeper:
: void
: WisemlExampleProcessor::value_changed()
: {
:: WisemlDataKeeper *keeper =
::: sim_controller_->keeper_by_name_w(
::: "wiseml_data_keeper");
If the keeper does not exist, the wiseml_setup task hasn not been called until the current time. Every data change is just ignored in this case. Anyway, if the keeper exists, the method tries to get the trace (the WisemlTraceCollector instance). And if that exists too, it commits the new value to the trace:
:: if(keeper != NULL)
:: {
::: try
::: {
:::: std::stringstream valstr;
:::: valstr << writer_battery_;
: // Getting the Trace
:::: WisemlTraceCollector &trace = keeper->trace("example_trace");
: // Committing the value change to the trace:
:::: trace.capability_value(owner().label(), "battery", valstr.str());
::: }
::: catch(std::runtime_error er)
::: {
:::: WARN("WisemlExampleProcessor",
::::: "WisemlTraceCollector not found!");
::: }
:: }
: }