uBit.messageBus

Overview

The micro:bit has an eventing model that can notify user code when specific things happen on the micro:bit.

For example, the MicroBitAccelerometer will raise events to indicate that the micro:bit has be been shaken, or is in freefall. MicroBitButton will send events on a range of button up, down, click and hold events.

Programmers are also free (in fact, encouraged!) to send their own events whenever they feel it would be useful.

Registering an event handler

The MicroBitMessageBus records which events your program is interested in, and delivers those MicroBitEvents to your program as they occur through a defined event handler.

This is achieved through the MicroBitMessageBus listen function. This lets you attach a callback to a function when a specified event (or events) occur.

You can also control the queuing and threading model used for your callback function on a per event handler basis.

This may sound complex at first, but it is actually very simple. For example, to find out when button A is clicked, write some code like this:

#include "MicroBit.h"

MicroBit    uBit;

void onButtonA(MicroBitEvent)
{
    uBit.display.print("A");
}

int main()
{
    uBit.init();
    uBit.messageBus.listen(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, onButtonA);

    // We don't want to drop out of main!
    while(1)
        uBit.sleep(100);
}

Now, whenever the MICROBIT_BUTTON_EVT_CLICK event is raise by MICROBIT_ID_BUTTON_A, your code inside function onButtonA will be automatically run.

You can call listen as many times as you want to attached functions to each of the events that are useful for your program.

Wildcard Events

Sometimes though, you want to capture all events generated by some component. For example, you might want to know when any changes in a button has happened.

In this case, there is a special event value called MICROBIT_EVT_ANY. If you call listen with this value, then ALL events from the given source component will be delivered to your function.

You can find out which ones by looking at the MicroBitEvent delivered to your function - it contains the source and value variable of the event.

For example, you could write a program like this:

#include "MicroBit.h"

MicroBit    uBit;

void onButtonA(MicroBitEvent e)
{
    if (e.value == MICROBIT_BUTTON_EVT_CLICK)
        uBit.display.scroll("CLICK");

    if (e.value == MICROBIT_BUTTON_EVT_DOWN)
        uBit.display.scroll("DOWN");
}

int main()
{
    uBit.init();
    uBit.messageBus.listen(MICROBIT_ID_BUTTON_A, MICROBIT_EVT_ANY, onButtonA);

    // We don't want to drop out of main!
    while(1)
        uBit.sleep(100);
}

If you REALLY want even more events, there is also a MICROBIT_ID_ANY source, that allows you to attach a function to event generated from any component.

Use this sparingly though, as this could be quite a lot of events!

The following code would attach the onEvent function to receive all the events from the whole runtime:

#include "MicroBit.h"

MicroBit    uBit;

void onEvent(MicroBitEvent)
{
    uBit.display.scroll("SOMETHING HAPPENED!");
}

int main()
{
    uBit.init();
    uBit.messageBus.listen(MICROBIT_ID_ANY, MICROBIT_EVT_ANY, onEvent);

    // We don't want to drop out of main!
    while(1)
        uBit.sleep(100);
}

Defining a Threading Mode

Whenever you register a listener, you may choose the threading mode used with that handler. Every event handler can have its own threading mode, that defines when your handler will be executed, and how it will react to receiving multiple events.

There are four permissible modes for event handlers. These are:

Threading mode Brief Description
MESSAGE_BUS_LISTENER_IMMEDIATE Handler is called directly from the code raising the event. Event handler is not permitted to block.
MESSAGE_BUS_LISTENER_DROP_IF_BUSY Handler is executed through its own fiber. If another event arrives whilst the previous event is still being processed, the new event will be silently dropped.
MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY Handler is executed though its own fiber. If another event arrives, it is queued, and the event handler will immediately be called again once processing is complete. (default)
MESSAGE_BUS_LISTENER_REENTRANT Every event is executed in its own fiber. if another event arrives, it is handled concurrently in its own fiber.

These various modes provide great flexibility in how the runtime can be used to support higher level languages and applications. For example, MESSAGE_BUS_LISTENER_IMMEDIATE is ideal for very simple, lightweight handlers, as this will provide very timely response to events with a low processing overhead. However, it is easy to cause side effects on other parts of the code if it does not return promptly. MESSAGE_BUS_LISTENER_DROP_IF_BUSY provide semantics identical to the Scratch programming language, and can be used to build easy to understand, asynchronous environments.

MESSAGE_BUS_LISTENER_QUEUE_IF_BUSY provides similar semantics, but with tolerance to avoiding loss of high frequency events.

MESSAGE_BUS_LISTENER_REENTRANT provides guaranteed causal ordering and improved concurrency, but at the cost of additional complexity and RAM.

You can define the threading mode you want to use on a per event handler basis as an optional final parameter to the listen function:

#include "MicroBit.h"

MicroBit    uBit;

bool pressed = false;

void onButtonA(MicroBitEvent)
{
    pressed = true;
}

int main()
{
    uBit.init();
    uBit.messageBus.listen(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, onButtonA, MESSAGE_BUS_LISTENER_IMMEDIATE);

    // We don't want to drop out of main!
    while(1)
    {
        if(pressed)
            uBit.display.scroll("Pressed!");
        uBit.sleep(100);
    }
}

C++ Event Handlers

It is also possible to write event handlers as C++ member functions. If you don't know what this means, then don't worry, as that also means you won't need it. :-)

For those programmers who do like to write C++, you can use a variation of the listen function to register your member function event handler.

This takes the same form as the examples above, but with an additional parameter to specify the object to call the method on. You are also required to specify your event handler using legal C++ syntax.

For example, you can write code like this to register an event handler in your own class:

MyCoolObject::onButtonPressed(MicroBitEvent e)
{
    uBit.display.print("A");
}

MyCoolObject::MyCoolObject()
{
    uBit.messageBus.listen(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, this, &MyCoolObject::onButtonPressed);
}

Again, it is also possible to a threading mode as an optional final parameter:

MyCoolObject::onButtonPressed(MicroBitEvent e)
{
    uBit.display.print("A");
}

MyCoolObject::MyCoolObject()
{
    uBit.messageBus.listen(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, this, &MyCoolObject::onButtonPressed, MESSAGE_BUS_LISTENER_IMMEDIATE);
}

Removing Event Handlers

Event handlers can be dynamically removed from the message bus as well as added. To do this, use the ignore function. This takes precisely the same parameters as the listen function, except that the threading mode argument is never used.

For example, to remove the event handlers shown above:

uBit.messageBus.ignore(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, onButtonA);
uBit.messageBus.ignore(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, this, &MyCoolObject::onButtonPressed);

Message Bus ID

Constant Value
MICROBIT_ID_MESSAGE_BUS_LISTENER 1021

The message bus will send a MICROBIT_ID_MESSAGE_BUS_LISTENER event whenever a new listener is added to the message bus.

This event allows other parts of the system to detect when interactions are taking place with a component. This is primarily used as a power management mechanism - allowing on demand activation of hardware when necessary.

Message Bus Events

Constant Value
Message Bus ID of listener 1-65535

API

Constructor


MicroBitMessageBus()

Description

Default constructor.

Adds itself as a fiber component, and also configures itself to be the default EventModel if defaultEventBus is NULL.

send


int
send
(
MicroBitEvent
evt)

Description

Queues the given event to be sent to all registered recipients.

Parameters

MicroBitEvent
evt - The event to send.

Example
 MicroBitMessageBus bus; 

 // Creates and sends the MicroBitEvent using bus. 
 MicrobitEvent evt(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK); 

 // Creates the MicrobitEvent, but delays the sending of that event. 
 MicrobitEvent evt1(MICROBIT_ID_BUTTON_A, MICROBIT_BUTTON_EVT_CLICK, CREATE_ONLY); 

 bus.send(evt1); 

 // This has the same effect! 
 evt1.fire() 

process


int
process
(
MicroBitEvent &
evt)

Description

Internal function, used to deliver the given event to all relevant recipients. Normally, this is called once an event has been removed from the event queue.

Parameters

MicroBitEvent &
evt - The event to send.

Returns

1 if all matching listeners were processed, 0 if further processing is required.

Note

It is recommended that all external code uses the send() function instead of this function, or the constructors provided by MicrobitEvent.


int
process
(
MicroBitEvent &
evt,
bool
urgent)

Description

Internal function, used to deliver the given event to all relevant recipients. Normally, this is called once an event has been removed from the event queue.

Parameters

MicroBitEvent &
evt - The event to send.

bool
urgent - The type of listeners to process (optional). If set to true, only listeners defined as urgent and non-blocking will be processed otherwise, all other (standard) listeners will be processed. Defaults to false.

Returns

1 if all matching listeners were processed, 0 if further processing is required.

Note

It is recommended that all external code uses the send() function instead of this function, or the constructors provided by MicrobitEvent.

elementAt


MicroBitListener
elementAt
(
int
n)

Description

Returns the microBitListener with the given position in our list.

Parameters

int
n - The position in the list to return.

Returns

the MicroBitListener at postion n in the list, or NULL if the position is invalid.

add


int
add
(
MicroBitListener *
newListener)

Description

Add the given MicroBitListener to the list of event handlers, unconditionally.

Parameters

MicroBitListener *
newListener

Returns

MICROBIT_OK if the listener is valid, MICROBIT_INVALID_PARAMETER otherwise.

remove


int
remove
(
MicroBitListener *
newListener)

Description

Remove the given MicroBitListener from the list of event handlers.

Parameters

MicroBitListener *
newListener

Returns

MICROBIT_OK if the listener is valid, MICROBIT_INVALID_PARAMETER otherwise.