Intro : The purpose of this tutorial
Step 1: Make a basic alarm
Step 2: Make our alarm adaptable and hot-pluggable
Step 3: Add another app controlling the alarm
Step 4: Make our alarm evolve using a truck horn and more
Step 5: Connect our system to the cloud
Make our alarm adaptable and hot-pluggable
We have now our basic alarm working, but it is not really usable as a bike alarm yet.
We will have to add some functionalities and make this app usable by any other app that could come complete our alarm.
By following this tutorial and applying these pieces of advice to all of your apps, you will be able to easily reuse all your developments out-of-the box.
To check if your app is already easy to be used with other ones, you have to ask yourself two questions:
- Is my app ready to accept another one to run detections?
- Is my app controllable by any other app in a standard way?
In the basic version of the alarm app, when we initialized the function we made the app run a detection after 1500ms.
But what if some other app on the network has made one just before? Or if a new app also running a detection is added to the network?
- If another app runs a detection before yours, there is no need to run one. The other app creates a routing table and sent it with your app, so your will be able to directly use it in order to setup any needed containers, and work as expected.
- If another app runs a detection after yours, you will have to catch the generated routing table in order to be prepared to any needs of reconfiguration. This can happen if a new app is hot-plugged in the network and need to be included into the routing table.
- If another app runs a detection at the exact same time than yours, your resulting routing table may be corrupted. Chances are low for this to occur but there is no way to prevent it. Make sure that the apps allowed to run detection don’t try to start it at the same time after power-up, and you should be fine.
To be prepared for those situations, we will have to make a smart detection based on our container ID.
To make it smarter, we will have to move the detection from the app initialization into the loop function.
On the init function we only keep the app container creation part, as followed:
app = luos_container_create(rx_alarm_controller_cb, ALARM_CONTROLLER_APP, "alarm_control", STRINGIFY(VERSION));
Now we have to detect into the loop whether we need to run a detection or not, based on the possible container ID changes.
A container that is not included in a routing table have the ID 0. We can use this information to check if a new ID is needed or if another detection is running, and monitor our ID’s evolution in order to be ready to reconfigure containers if it’s necessary.
In our case, we have to take care to re-setup the IMU after each detection, because auto-update configurations (the part that makes the IMU to send us data each 10ms) are erased by detections, to prevent any error due to ID change. So to keep our IMU updating anyway, we have to reconfigure it after any detection.
You can add this code into your loop to manage it:
staticshort previous_id = -1;
// ********** hot plug management ************
// Check if we have done the first init or if container ID has changed
if (previous_id != id_from_container(app))
if (id_from_container(app) == 0)
// We don't have any ID, meaning either no detection has occurred, or a detection is currently occurring.
if (previous_id == -1)
// This is the really first init, we have to make it.
// Be sure that the network is powered up 1500 ms before starting a detection
if (HAL_GetTick() > 1500)
// No detection occurred, do the detection
// Another app is making a detection.
// Do not launch a detection to prevent a detection collision, that would potentially results in a corrupted routing table.
// Reset the init state to be ready to setup container at the end of detection
previous_id = 0;
// We detect a change of ID but our ID is not 0 so a detection has just finished.
// Make containers configurations
// Try to find an IMU container and set parameters to disable quaternions, and send back Gyro acceleration data.
report.gyro = 1;
report.quat = 0;
id = id_from_type(IMU_MOD);
if (id > 0)
// We found a container, prepare a message with parameters and send it.
msg.header.cmd = PARAMETERS;
msg.header.size = sizeof(imu_report_t);
msg.header.target = id;
msg.header.target_mode = IDACK;
memcpy(msg.data, &report, sizeof(imu_report_t));
// Setup auto-update each 10ms on IMU
time_luos_t time = time_from_ms(10);
// We need to overwrite the command because we don't send a standard TIME command
msg.header.cmd = UPDATE_PUB;
previous_id = id_from_container(app);
Each application can have different control consideration. You have to try to be prepared of any usage of your app by preparing messages reception that can be useful. This is the API part of your app, so try to think it clean and flexible by using Luos Object Dictionary (OD) as much as possible.
There is one thing each app should have: the capacity to be stopped by any other.
In our case, this is critical because, for now, the alarm can’t be shut down: the only way is to power it down. We have to provide a way for other apps to stop it!
Luos has a standard structure and command dedicated to this, called control_mode.
This command works like play, pause, stop, and record commands of your music player. This kind of command can be used on drivers to control data stream such as trajectories on motors for examples, or they can be used to control app’s running state.
Each app should have a global variable representing the running state of the app. To manage it in our alarm app, I add this global:
Then I put it on PLAY by default on the init:
control_mode.mode_control = PLAY;
And I add the possibility to receive a control mode in my rx_alarm_controller_cb:
if (msg->header.cmd == CONTROL)
control_mode.unmap = msg->data;
Now I can use this control_mode to make my app run or not.
For this alarm app, I use it as a condition to execute the non-blocking blink on the loop and for the IMU data reception.
Finally, my complete callback reception looks like that:
voidrx_alarm_controller_cb(container_t *container, msg_t *msg)
if (msg->header.cmd == GYRO_3D)
// this is imu information
if (control_mode.mode_control == PLAY)
memcpy(value, msg->data, msg->header.size);
if ((value > 300) || (value > 300) || (value > 300))
blink_state = 1;
if (msg->header.cmd == CONTROL)
control_mode.unmap = msg->data;
Nice! Our app is now clean, and ready to properly work with any other app!
If you have any question or comment about this step of the tutorial, feel free to reply to this post.