The EventBroker allows to post events to specific topics, as in a Publish-Subscribe architecture.
The following image shows a simple scenario with two topics (TOPIC_1
and TOPIC_2
), two publishers (one that publishes on topic TOPIC_1
and the other that publishes on both topics) and two subscribers (one subscribed to both topics and one subscribed only to topic TOPIC_1
).
Then, Subscriber 2
will receive events that are published both on TOPIC_1
or on TOPIC_2
, while Subscriber 1
will only received the events that are posted on TOPIC_1
.
Implementation
The EventBroker is a singleton, active object.
EventHandlerBase objects can ask the EventBroker to subscribe to a specific topic: this way a subscriber will receive all the events that are posted on the topics it is subscribed to.
In order to manage this events exchange, the broker maintains a map that associates each topic ID to a vector of EventHandlerBase objects:
map<uint8_t, vector<EventHandlerBase*>> subscribers;
When an event is posted to a topic, it is forwarded to all the subscribers of that topic, according to the vectors stored in the subscribers
map.
Methods
In addition to the Singleton and ActiveObject methods, the EventBroker exposes:
- void post(const Event& ev, uint8_t topic): post an event to a specific topic.
- uint16_t postDelayed<>(const Event& ev, uint8_t topic): post an event to a specific topic after a given delay in milliseconds (the delay can be passed as a template, e.g. postDelayed<100>(...)).
- void removeDelayed(uint16_t id): remove a pending delayed event from the event queue.
- void subscribe(EventHandlerBase* subscriber, uint8_t topic): subscribe the given subscriber to the specified topic.
- void unsubscribe(EventHandlerBase* subscriber, uint8_t topic): unsubscribe the given subscriber to the specified topic. If no topic is passed, the unsubscribe the subscriber from every topic it was subscribed to.
- void clearDelayedEvents(): remove all pending events.
Moreover, the EventBroker module also defines a macro that can be seen as a shortcut to retrieve the broker's instance:
#define sEventBroker Singleton<EventBroker>::getInstance()
You can find the complete implementation of the EventBroker in src/shared/events/EventBroker.h
.
Notice that the current EventBroker implementation does not support posting events that contain a payload. See the events basics page to know how events are structured.
Delayed Events
When a delayed event is posted, it is put in a queue maintained by the EventBroker. The broker's active object thread takes care of removing the delayed events from the queue when their deadline is passed and to actually post them to the required topic.
The reason why the postDelayed<>() method is a template function is that it allows to check that the given delay is grater than a minimum value (EVENT_BROKER_MIN_DELAY
) at compile time, using a static assert.
Only the delayed events are managed and posted by the broker's active object thread. All the other events ("normal" events) are posted and forwarded to the subscribers by the caller thread (i.e. the one that is publishing the event).
Example
This example shows how to define an EventHandler object that subscribes to a topic and received the events that are posted on that topic.
MyEventHandler.h
First of all we need to include the required dependencies. We also need to specify a list of possible events and topics.
#pragma once
#include "events/EventHandler.h"
#include "events/EventBroker.h"
#define sEventBroker Singleton<EventBroker>::getInstance()
namespace Boardcore {
map<uint8_t, vector<EventHandlerBase*>> subscribers;
enum ExampleEvents : uint8_t
{
EV_1 = EV_FIRST_CUSTOM,
EV_2,
EV_3
};
enum ExampleTopics : uint8_t
{
TOPIC_1,
TOPIC_2
};
class MyEventHandler : public EventHandler
{
public:
MyEventHandler()
: EventHandler(), // call parent constructor
last_event(0)
{
// make this object to subscribe to TOPIC_1
sEventBroker.subscribe(this, TOPIC_1);
}
~MyEventHandler()
{
// unsubscribe from all the topics this object was subscribed to
sEventBroker.unsubscribe(this);
}
protected:
void handleEvent(const Event& ev) override
{
switch(ev)
{
case EV_1:
TRACE("Received EV_1 \n");
break;
case EV_2:
TRACE("Received EV_2 \n");
break;
default:
TRACE("Invalid event \n");
}
last_event = ev;
}
private:
uint8_t last_event;
};
}
test-eventbroker.cpp
In the main we need to create an instance of the EventBroker (it is a singleton object) and start it (it is also an active object).
Also, we need to start the custom EventHandler.
Then we can post some events, both on topic TOPIC_1
and TOPIC_2
.
#include <miosix.h>
#include "MyEventHandler.h"
using namespace miosix;
using namespace Boardcore;
int main()
{
EventBroker* broker = &EventBroker::getInstance(); // singleton object
broker->start();
MyEventHandler evh;
if (evh.start())
{
// post an event
broker->post(Event{EV_1}, TOPIC_1);
Thread::sleep(100);
// post another event on a different topic
// this event will not be received by the MyEventHandler
// object, since it is subscribed only to TOPIC_1
broker->post(Event{EV_2}, TOPIC_2);
Thread::sleep(100);
// post an event with a delay of 1 second
const unsigned int DELAY_MS = 1000;
broker->postDelayed<DELAY_MS>(Event{EV_2}, TOPIC_1);
// wait for the delayed event to be posted
// before stopping the active objects
Thread::sleep(2000);
evh.stop(); // it posts an EV_EMPTY to wake up the thread
broker->stop();
}
else {
TRACE("Failed to start MyEventHandler\n");
}
return 0;
}
Output
What we can observe is that the program output will be only something like:
0.04> Received EV_1
1.24> Received EV_2
2.24> Invalid event
The last "Invalid event" is due to the EV_EMPTY
posted by the stop()
method of the EventHandler object.