A new way to design embedded app' using Luos [Step 2]


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?

Detection consideration

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 modules, 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 module 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 module creation part, as followed:

void alarm_controller_init(void)
{
    app = luos_module_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 module ID changes.
A module 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 modules 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:

void alarm_controller_loop(void)
{
    static short previous_id = -1;
    // ********** hot plug management ************
    // Check if we have done the first init or if module ID has changed
    if (previous_id != id_from_module(app))
    {
        if (id_from_module(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
                    detect_modules(app);
                }
            }
            else
            {
                // 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 module at the end of detection
                previous_id = 0;
            }
        }
        else
        {
            // We detect a change of ID but our ID is not 0 so a detection has just finished.
            // Make modules configurations
            // Try to find an IMU module and set parameters to disable quaternions, and send back Gyro acceleration data.
            imu_report_t report;
            report.gyro = 1;
            report.quat = 0;
            id = id_from_type(IMU_MOD);
            if (id > 0)
            {
                // We found a module, prepare a message with parameters and send it.
                msg_t msg;
                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));
                luos_send(app, &msg);

                // Setup auto-update each 10ms on IMU
                time_luos_t time = time_from_ms(10);
                time_to_msg(&time, &msg);
                // We need to overwrite the command because we don't send a standard TIME command
                msg.header.cmd = UPDATE_PUB;
                luos_send(app, &msg);
            }
            previous_id = id_from_module(app);
        }
        return;
    }
}

Control consideration

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:

volatile control_mode_t control_mode;

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[0];
    return;
}

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:

void rx_alarm_controller_cb(module_t *module, msg_t *msg)
{
    if (msg->header.cmd == GYRO_3D)
    {
        // this is imu information
        if (control_mode.mode_control == PLAY)
        {
            float value[3];
            memcpy(value, msg->data, msg->header.size);
            if ((value[0] > 300) || (value[1] > 300) || (value[2] > 300))
            {
                blink_state = 1;
            }
        }
        return;
    }
    if (msg->header.cmd == CONTROL)
    {
        control_mode.unmap = msg->data[0];
        return;
    }
}

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.

<< Previous (Step 1: Make a basic alarm) | Next (Step 3: Add another app controlling the alarm) >>