Modules¶
Modules are entities that run independently, usually in their own thread. They can sent and receive arbitrary data to/from other Modules using Channels. Each Module can publish an arbitrary amount of channels and subscribe to any other channel.
As a start, please consider the following most important aspects of Modules.
Most important aspects of Modules
- Modules can subscribe to and publish any amount of Channels.
- Modules can be passive (e.g., only react based on data they receive) or active (e.g., continuously recording Microphone audio data). Of course, they also can be both at the same time.
- Modules can run tasks at a specific intervall rate (periodic code).
- Modules can execute blocking code, however during that time are not able to receive data from Channels (see blocking code in the Module subpage).
- Modules run in their own thread
- Modules can spawn SubModules, that either run in the same or in another independent thread.
- Configuration: Usually, Modules are loaded when CLAID is started. Which Modules to load can be specified in XML configurations
- Modules can have properties (e.g. internal private variables), which can be specified when loading a Module; their values can be set in the XML configuration aswell.
- No code needs to be written in order to load Modules. Different Modules can easily be combined by writing an appropriate XML configuration.
Independent Threads¶
Each Module runs in it's own thread by default. When a Module subscribes to a Channel and a callback is specified, (see section "Communication using Channels" below), the callback function will be run in the own thread of the Module (context switching happens automatically). Otherwise, if a Module is not performing any active task, and no callback function is currently executed, the thread of the Module is put to sleep automatically. A Module that is loaded, but currently not executing any functions, does not consume any CPU time.
Communication using Channels¶
As mentioned above, communication between Modules happens using Channels. When one Module posts data, all Modules that subscribed to that Channel get notified automatically. Therefore, the inserted data is forwareded (by reference, no copy is made) to all Modules that require it. Intuitively, this is as simple as depicted in the following figure.
Note, that this figure is just an example. The number of Modules sending data to or receiving data from a Channel is not limited. Every channel can have an arbitrary amount of Publishers and Subscribers. A Module that publishes a channel can also subscribe to it at the same time and vice versa.
In the following, you can find simple code examples on how to program custom Modules using the ModuleAPI optionally in C++, Java or Python. In the example, two Modules are created:
- MyModule: Simple Module that publishes a Channel ("MyStringChannel") of data type string. The Modules posts data to it once at the beginning (the initialize function is called when the Module is started).
- PrintModule: Subscribes to the same Channel ("MyStringChannel") and prints any incoming data data was posted.
Show code example
class MyModule : public Module
{
Channel<string> channel;
void initialize()
{
channel = publish<string>("MyStringChannel");
channel.post("This is a test");
}
};
class PrinterModule : public Module
{
Channel<string> channel;
void initialize()
{
// Subscribe to the channel and register a callback.
channel = subscribe<string>("MyStringChannel", &PrinterModule::onNewData, this);
}
// Callback function that automatically get's called
// when new data is available.
void onNewData(ChannelData<string> data)
{
// Of course, any other data type can be sent just as easily.
// String was just used for the sake of providing an intuitive example.
std::cout << "Received message: " << data.value() << "\n";
}
};
class MyModule extends Module
{
Channel<String> channel;
private void initialize()
{
channel = publish(String.class, "MyStringChannel");
channel.post("This is a test");
}
};
class PrinterModule extends Module
{
Channel<String> channel;
void initialize()
{
// Subscribe to the channel and register a callback.
channel = subscribe(String.class, "MyStringChannel", "onNewData");
}
// Callback function that automatically get's called
// when new data is available.
void onNewData(ChannelData<string> data)
{
// Of course, any other data type can be sent just as easily.
// String was just used for the sake of providing an intuitive example.
System.out.println("Received message: " + data.value());
}
};
Properties¶
Using CLAID's reflection system, every Module is able to implement properties. Properties are usually internal (private) variables of classes derived from the Module base class (i.e., user-implemented Modules). Variables registered to the reflection system become properties of the Module, that can be specified when loading the Modules. Usually, Modules are loaded using XML configuration. No code is needed to load implemented Modules. Different applications can combine different Modules simply by writing an appropriate configuration. In the configuration, properties of Modules can be specified. Consider the following example. Please note, that more details can be found on the pages about de-/serialization & reflection.
Show property configuration example
In this example, we have a simple Module that publishes a channel with an ID specified by the "ChannelName" property. At startup, messages (strings) are posted to that channel, that can be specified in a vector/list property. The Module is called "MessengerModule", the properties of the Module are specified in the reflect(...) function. In the tab "XML" you can see how to add this Module to a startup configuration and
class MessengerModule : public Module
{
private:
Channel<string> channel;
// Those two variables are registered as properties
// in the reflect function below.
// Thus, they can be specified in an XML configuration.
string channelName;
vector<string> messages;
public:
template<typename Reflector>
void reflect(Reflector& r)
{
r.reflect("ChannelName", channelName, "The ID of the channel to post the message to.");
r.reflect("Messages", messages, "What message(s) to post to the channel.");
}
void initialize()
{
// Publish a channel with the ID set as channelName property.
channel = publish<string>(channelName);
// This will post all strings specified in the messages vector one after another.
for(string message : messages)
{
channel.post(message);
}
}
};
class MessengerModule extends Module
{
private Channel<String> channel;
// Those two variables are registered as properties
// in the reflect function below.
// Thus, they can be specified in an XML configuration.
private string channelName;
private ArrayList<String> messages;
public void reflect(Reflector r)
{
r.reflect("ChannelName", channelName, "The ID of the channel to post the message to.");
r.reflect("Messages", messages, "What message(s) to post to the channel.");
}
public void initialize()
{
// Publish a channel with the ID set as channelName property.
channel = publish(String.class, channelName);
// This will post all strings specified in the messages vector one after another.
for(string message : messages)
{
channel.post(message);
}
}
};
class PrinterModule extends Module
{
Channel<String> channel;
void initialize()
{
// Subscribe to the channel and register a callback.
channel = subscribe(String.class, "MyStringChannel", "onNewData");
}
// Callback function that automatically get's called
// when new data is available.
void onNewData(ChannelData<string> data)
{
// Of course, any other data type can be sent just as easily.
// String was just used for the sake of providing an intuitive example.
System.out.println("Received message: " + data.value());
}
};
Warning
Note, that every property registered in the reflect() function is mandatory to be specified in the XML configuration. If the value is not specified in the configuration, an error will be shown at startup.
Periodic execution of code¶
In the example above, you have seen how to execute code in callbacks based on incoming data. Of course, it's not always the case that you want a Module only to react depending on data (that other Modules sent). Some Modules might sent data periodically (e.g. query a database every hour). With functions provided by the ModuleAPI, you can easily register periodic functions inside a Module, that are called periodically within specified time intervalls.
Show code example
class MyModule : public Module
{
Channel<string> channel;
int counter = 0;
void initialize()
{
channel = publish<string>("MyStringChannel");
// The function will be called every 1000ms.
registerPeriodicFunction("PeriodicFunction", &periodicallyCalledFunction::this, 1000);
}
// This function will automatically be called periodically.
void periodicallyCalledFunction()
{
channel.post("This function has been called " + to_string(counter) + " times.");
counter++;
}
};
class MyModule extends Module
{
private Channel<String> channel;
private int counter = 0;
private void initialize()
{
// The function will be called every 1000ms.
channel = publish(String.class, "MyStringChannel");
registerPeriodicFunction("PeriodicFunction", "periodicallyCalledFunction", 1000);
}
// The function will be called every 1000ms.
private void periodicallyCalledFunction()
{
channel.post("This function has been called " + counter.to_string() + " times.");
counter++;
}
};
Code example
Show code example
class MyModule : public Module
{
Channel<MyDataType> channel;
int counter = 0;
void initialize()
{
channel = publish<MyDataType>("MyDataTypeChannel");
registerPeriodicFunction("PeriodicFunction", &periodicFunction::this, 1000);
registerScheduledFunction("ScheduledFunction",
&scheduledFunction::this, Date::everyDay(), Time(13, 37, 00));
}
// This function will automatically be called periodically every 1000 milliseconds.
void periodicFunction()
{
channel.post("This function has been called " + to_string(counter) + " times.");
counter++;
}
// This function will be called every day at 13:37:00
void scheduledFunction()
{
channel.post("The time is now " + Time::now().toString("%h:%m:%s"));
}
};
class MyModule extends Module
{
private Channel<MyDataType> channel;
private int counter = 0;
void initialize()
{
channel = publish<MyDataType>(MyDataType.class, "MyDataTypeChannel");
registerPeriodicFunction("PeriodicFunction", "periodicFunction", 1000);
registerScheduledFunction("ScheduledFunction",
"scheduledFunction", Date.everyDay(), Time(13, 37, 00));
}
// This function will automatically be called periodically every 1000 milliseconds.
void periodicFunction()
{
channel.post("This function has been called " + counter + " times.");
counter++;
}
// This function will be called every day at 13:37:00
void scheduledFunction()
{
channel.post("The time is now " + Time.now().toString("%h:%m:%s"));
}
};
class MyModule(PythonModule):
def __init__(self):
PythonModule.__init__(self)
def initialize(self):
self.channel = self.publish(MyDataType, "MyDataTypeChannel");
self.counter = 0
registerPeriodicFunction("PeriodicFunction", self.periodicFunction, 1000);
registerScheduledFunction("ScheduledFunction",
self.scheduledFunction, Date.everyDay(), Time(13, 37, 00));
# This function will automatically be called periodically every 1000 milliseconds.
def periodicFunction(self):
self.channel.post("This function has been called " + str(self.counter) + " times.");
self.counter += 1;
# This function will be called every day at 13:37:00
def scheduledFunction(self):
self.channel.post("The time is now " + Time.now().toString("%h:%m:%s"));
Warning
Note: The code within the periodically called function should execute faster than the rate at which it is called. E.g., if the function is called every second, the code should not take longer than 1s to be executed. Otherwise, the function will always lag behind.
Blocking execution of code: SubModules¶
Initialize function is not allowed to be blocking. But you can use "callLater", to have the function being called after initialize. However, if you perform blocking operations in that Module, it can not receive incoming data while the function is active.
Recorder