Adding an afLib C-Based App to the Potenco Build for BeagleBone Green Wireless

Now that you have successfully built Potenco using the instructions in the Linux SDK documentation, you may want to know how to add your own recipe, and create and add your own application. On this page, we will lay out both the instructions for doing so as well as the reasoning behind the instructions, in the sections below.

We are assuming some general familiarity with Yocto and BitBake so will not be defining the terminology used.

About BitBake Layers

BitBake uses the concept of “layers” to control the flow for downloading, compiling, linking, staging, and building the final root file system. We are going to add our own layer, which we will call “app”. You will be using the command bitbake app to create the application binary file. It is important to note that exactly how you do this will depend entirely on the starting Yocto build system that you use for your target processor.

Every manufacturer of hardware who supplies such a build system will typically put things in different places. For the BeagleBone Green Wireless board, you will find the source code in several different locations depending on which part of the system you are trying to build. Afero has also chosen to place all our components in the top-level directory. This is also where we will be putting your application program.

Before we get started, let’s make sure you have a few preliminary things out of the way.

Prerequisites

  • You have completed the instructions for building the BeagleBone Green Wireless based Potenco project, contained in the section Build the Potenco OS Image.
  • You have named the directory in which the Potenco project exists as “potenco” and placed it in your home directory. If you choose to use a different path or directory name, you must adjust the instructions below accordingly.
  • You should have at least 64GB of disk space and 4GB of memory in your Ubuntu system to successfully build the Potenco image.

The Steps, and Why

Step 1. Have Your Potenco Build Image

Again, be sure you have created a Potenco build image per the instructions in Build the Potenco OS Image. This will be the core image that is used on the BeagleBone Green Wireless and the core image to which you will add your own application code.

Step 2. Create af-app Directory

We will need to create a few directories, the first will be af-app. Create this at the root of the Potenco directory:

$ cd potenco
$ mkdir af-app

This is where your application will go, along with your makefile and any header files that you create. Your source as well as your object and final app binary files will be placed here during the build process.

Step 3. Add Source File

Now that we have a place to put your source file, let’s add it. We can call it “test_aflib_edge.c”. The purpose of this program is to:

  • Wait for a change in the Wi-Fi signal strength, then
  • Set attribute ID 2 to the value of 8, and finally
  • Send that new value to the Afero Cloud.

The test_aflib_edge.c file contents look like this:

/**
   Copyright 2019 Afero, Inc.
   This program is a demonstration program that shows you how to capture
   an event from the Afero Cloud to set an edge attribute, and do something with
   the data that is sent to you. It also includes the ability to send a data
   item from the edge device up to the Cloud. This basic functionality is
   the basis of all communications with the Afero Cloud and the device.
   Receiving an attribute is typically used for some kind of control
   on the device itself. And sending an attribute to the Cloud is typically used
   for reporting some kind of operational status from the device back to the
   Cloud and mobile app.
*/
 
// This is a simple unit test 'EDGED' app, simulating the handling of
// edge attributes (in the hub world)
 
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <event2/event.h>
#include <event2/thread.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
 
#include "af_attr_def.h"
#include "af_attr_client.h" // AF_ATTR_MAX_LISTEN_RANGES
#include "af_log.h"
#include "af_rpc.h"
#include "af_ipc_server.h"
#include "aflib.h"
#include "aflib_mcu.h"
 
af_lib_t          *sAf_lib    = NULL;  // currently not used in hub afLib
struct event_base *sEventBase = NULL;  // we definitely need this
 
// ##############
//
// EDGE ATTRIBUTES (or your APP attributes)
//
// EXAMPLE of handle edge/mcu attributes (which should have been defined in the
// loaded Profile on the device):
//
// attribute with ID 1 : led_button, has value on/off or (1/0)
// attribute with ID 2 : represents some kind of state value (uint32)
 
uint8_t   attr_id_1_led_button = 0;  // attributeId = 1
uint32_t  attr_id_2_state      = 0;  // attributeId = 2
 
// END EDGE ATTRIBUTES
// ##############
 
// EDGED (or your app): this is what you need to program
//
// This callback is executed any time ASR has information for the MCU
// The name of this event is defined in the aflLib initialization in setup()
void attrEventCallback(const af_lib_event_type_t eventType,
                       const af_lib_error_t error,
                       const uint16_t attributeId,
                       const uint16_t valueLen,
                       const uint8_t* value)
{
    char hexBuf[80];
    bool set_succeeded = 1;
    uint8_t  ret;
 
   
    memset(hexBuf, 0, sizeof(hexBuf));
    if (value != NULL) {
        af_util_convert_data_to_hex_with_name("value", (uint8_t *)value,
                                          valueLen, hexBuf, sizeof(hexBuf));
    }
 
    AFLOG_INFO("attrEventCallback:attrid:%d, %s, len=%d", attributeId, hexBuf, valueLen);
    switch (eventType) {
        // Unsolicited notification when a non-MCU attribute changes state
        case AF_LIB_EVENT_ASR_NOTIFICATION: // non-edge attribute notify
            AFLOG_INFO("NOTIFICATION EVENT: for attr=%d", attributeId);
 
            // TESTING: using the notification of the WIFI_RSSI as trigger to
            //   test af_lib_set_attribute_xx call
            //
            // we are interested in attribute wifi rssi (65005)
            if (attributeId == AF_ATTR_WIFISTAD_WIFI_RSSI) {
               // TESTING: send attributeId =2, with value=8 up
               //   (simulating an edge attribute is changed and need to do a af_lib_set_xx)
               AFLOG_INFO("EDGED: set_attribute_32() for id=2, value=8");
               attr_id_2_state = 8;
               ret = af_lib_set_attribute_32(sAf_lib, 2,
                                             attr_id_2_state, AF_LIB_SET_REASON_LOCAL_CHANGE);
               if (ret != AF_SUCCESS) {
                   AFLOG_ERR("af_lib_set_attribute: failed set for the test attributeId=2");
               }
            }
            break;
 
 
        case AF_LIB_EVENT_ASR_SET_RESPONSE:
            AFLOG_INFO("ASR_SET_RESPONSE EVENT:");
            break;
 
 
        case AF_LIB_EVENT_MCU_SET_REQUEST: // edge attribute set request
            AFLOG_INFO("MCU_SET_REQUEST EVENT: for attr=%d", attributeId);
            // in real world scenario:
            // you would know which attribute by switch on (attributeId) and handle the attributes
            // EDGED/Your app needs to handle
            if (attributeId == 1) {
                attr_id_1_led_button = *value;
                af_lib_send_set_response(sAf_lib, attributeId, set_succeeded, 1, (const uint8_t *)&attr_id_1_led_button);
            }
            else if (attributeId == 2) {
                attr_id_2_state = *value;
                AFLOG_INFO("EDGED: new value=%d", attr_id_2_state);
                af_lib_send_set_response(sAf_lib, attributeId, set_succeeded, 1, (const uint8_t *)&attr_id_2_state);
            }
            else {
                // Don't know anything about these attributes, reject
                set_succeeded = 0;  // failed the set
                af_lib_send_set_response(sAf_lib, attributeId, set_succeeded, valueLen, value);
            }
            break;
 
 
        case AF_LIB_EVENT_MCU_DEFAULT_NOTIFICATION: // edge attribute changed
            AFLOG_INFO("EDGE ATTR changed: for attr=%d", attributeId);
            // Your code to handle whatever needs to be done if you are interested in a particular attribute
            break;
 
        case AF_LIB_EVENT_ASR_GET_REQUEST: {
            AFLOG_INFO("EDGE ATTR get_reqeust: for attr=%d", attributeId);
            // attribute_store asks for the current value of attribute
            // (belong to edged or mcu).  Responding with attribute and its value
            // Note 1: af_lib_set_attribute_xx, where xx depends on the type of attributes
 
            // Note the following in the call: AF_LIB_SET_REASON_GET_RESPONSE to indicates
            //  it is a reply for the get_request
            if (attributeId == 1) {
                ret = af_lib_set_attribute_8(sAf_lib, attributeId,
                                             attr_id_1_led_button, AF_LIB_SET_REASON_GET_RESPONSE);
            }
            else if (attributeId == 2) {
                AFLOG_INFO("af_lib_set_attriubte_32, with AF_LIB_SET_REASON_GET_RESPONSE");
                ret = af_lib_set_attribute_32(sAf_lib, attributeId,
                                             attr_id_2_state, AF_LIB_SET_REASON_GET_RESPONSE);
            }
            else {
                ret = AF_ERROR_NOT_SUPPORTED;
            }
 
            if (ret != AF_SUCCESS) {
                AFLOG_ERR("af_lib_set_attribute: failed for request id:%d", attributeId);
            }
            } // AF_LIB_EVENT_ASR_GET_REQUEST
            break;
 
 
        default:
           AFLOG_INFO("EVENT: %d, received but not handled", eventType);
           break;
 
    } // end switch
}
 
/* This is the test program to verify afLib implementation */
int main(int argc, char *argv[])
{
    int retVal = AF_SUCCESS;
 
   /* enable pthreads */
    evthread_use_pthreads();
 
    /* get an event_base */
    sEventBase = event_base_new();
    /* And make sure we actually got one! */
    if (sEventBase == NULL) {
        /* var/log/messages entry to show where we are at in this app */
        AFLOG_ERR("main_event_base_new::can't allocate event base");
        retVal = -1;
        return (retVal);
    }
 
    AFLOG_INFO("EDGE: start");
 
    /* The event we want to register is the afero library getting data. */
    retVal = af_lib_set_event_base(sEventBase);
    if (retVal != AF_SUCCESS) {
        AFLOG_ERR("main_set_event_base::set event base failed");
        goto err_exit;
    }
    AFLOG_INFO("EDGE: call af_lib_create_with_unified_callback");
    /*
       Now create the event and point to our callback function when that event
       occurs. Note that this function af_lib_create_with_unified_callback will
       automatically subscribe you to attributes 1 - 1023, and several Wi-Fi, WAN, and Ethernet
       attributes as well as the Profile change attribute.
    */
    sAf_lib = af_lib_create_with_unified_callback(attrEventCallback, NULL);
    if (sAf_lib == NULL) {
        AFLOG_ERR("main_event_base_new::can't allocate event base");
        retVal = -1;
        goto err_exit;
    }
    AFLOG_INFO("EDGE: dispatching event base");
    /* start it up! This will not return until
       either there are no more events or the loop exit
       or kill functions are called.
    */
    event_base_dispatch(sEventBase); 
err_exit:
    // done, let's close/clean up
    AFLOG_INFO("EDGED:  shutdown");
    af_lib_shutdown();
    return (retVal);
}

Now that we have the code ready, we need a way to compile it. This is where things get interesting with Yocto and BitBake. The makefile we create cannot be run directly. We can only invoke it through BitBake. This is because BitBake will provide several paths that are necessary to find all the needed library elements. So before we can compile and test, we need to create the makefile and add the BitBake layer that will then invoke the makefile with the correct system paths.

Step 4. Create the makefile

Start by opening your favorite text editor and create the file “makefile” in the af-app directory, with contents as follows:

#  Copyright (c) 2019 Afero, Inc. All rights reserved.
APP_LIBS_NEEDED :=   -lpthread -levent_pthreads -levent -laf_util -laf_edge -laf_ipc -laf_attr
default: all
all: app
app: test_aflib_edge.c
            $(CC) $(CFLAGS)  -L $(APP_LIBS_NEEDED) -L $(APP_LIBS_NEEDED) -o app test_aflib_edge.c

Now that we have the makefile, we’re ready to create the BitBake layer.

Step 5. Create the BitBake Layer

There are BitBake commands that create a BitBake layer for you; however, we will do this manually.

The Afero stack keeps its layers in the ~/potenco/sources/meta-afero directory. Our app will live in its own layer and will be named “meta-app”. Create it at the same directory level as meta-afero and the other layers; that is, in the directory ~/potenco/sources:

$ cd ~/potenco/sources
$ mkdir meta-app

Now we need two subdirectories in meta-app: conf and recipes-app. Type the following:

$ cd meta-app
$ mkdir conf
$ mkdir recipes-app

Step 6. Create the BitBake Recipe for the App

The recipe provides the structure we need to add our recipes and some BitBake configuration. Let’s start with the BitBake recipe for the app:

$ cd recipes-app

In this recipes-app directory, we will create our recipe. The name is not important, but usually reflects the application being built and always ends with .bb. We will call ours “app_1.0.bb”. Using your favorite editor, create the file and add the following contents to it:

# Copyright (C) 2019 Afero, Inc. All rights reserved
# human friendly description
DESCRIPTION = "Afero example app"
SECTION = "examples"
# A few things that this recipe will depend on .
DEPENDS = "libevent af-util af-ipc attrd af-edge"
# License is unimportant here for us as is the checksum.
LICENSE = "CLOSED"
LIC_FILES_CHKSUM = ""
# Bring in a few tools for the build, such as being able to define where we want the build to occur and the systemd libraries.
inherit externalsrc systemd 
# point to where our source code is.
EXTERNALSRC = "${TOPDIR}/../af-app"
 
PARALLEL_MAKE = ""
#this is what we want it to do to compile the code, cd into the source directory and run the makefile found there. 
do_compile(){
            cd ${S}
            oe_runmake
}
# For installing, we will copy the app created to the /usr/bin/staging directory for this layer and set its permissions to rwxr-x-r-x after first making sure the target directory exists.
do_install_append() {
    install -d ${D}/usr/bin
    install -m 755 ${EXTERNALSRC}/app ${D}/usr/bin
}

Step 7. Create the layer.conf File

Now that we have the BitBake recipe, we need to create a configuration file that informs BitBake what to do with that file, the path, where the recipes are located, and what they are called.

The .conf file also tells BitBake what the target is called; in this case it’s called “app”. You can also assign a priority. We are using the same priority as all the other Afero BitBake recipes, which is 6.

So let’s get out the editor again and create the layer.conf file that BitBake searches for. It belongs in the meta-app/conf directory and contains the following:

# We have a conf and classes directory, add to BBPATH
BBPATH := "${BBPATH}:${LAYERDIR}"
# We have a recipes directory, add to BBFILES
BBFILES := "${BBFILES} ${LAYERDIR}/recipes-*/*.bb \
        ${LAYERDIR}/recipes-*/*.bbappend"
BBFILE_COLLECTIONS += "app"
BBFILE_PATTERN_app := "^${LAYERDIR}/"
BBFILE_PRIORITY_app = "6"

Step 8. Modify bblayers.conf File

Okay! Now that we have the BitBake recipe and the layer.conf file created, we need to tell BitBake to look for it. There is a single file that tells BitBake about all the layers. It’s called “bblayers.conf” and is always located in the conf directory under the build directory; i.e., the directory in which you invoke BitBake.

In our tree it is located in ~/potenco/build/conf.Take a look at the contents of bblayers.conf and you will see a listing of directories. This listing was created when you first created the build directory during the configuration phase of the Potenco build. Example contents are shown below:

# This template file was created by taking the oe-core/meta/conf/bblayers.conf
# file and removing the BBLAYERS section at the end.
 
# LAYER_CONF_VERSION is increased each time build/conf/bblayers.conf
# changes incompatibly
LCONF_VERSION = "7"
 
BBPATH = "${TOPDIR}"
BBFILES ?= ""
 
# Layers configured by oe-core-setup script
BBLAYERS += " \
           <your_full_path>/potenco/sources/meta-processor-sdk \
           <your_full_path>/potenco/sources/meta-arago/meta-arago-distro \
           <your_full_path>/potenco/sources/meta-arago/meta-arago-extras \
           <your_full_path>/potenco/sources/meta-qt5 \
           <your_full_path>/potenco/sources/meta-openembedded/meta-networking \
           <your_full_path>/potenco/sources/meta-openembedded/meta-ruby \
           <your_full_path>/potenco/sources/meta-openembedded/meta-python \
           <your_full_path>/potenco/sources/meta-openembedded/meta-oe \
           <your_full_path>/potenco/sources/meta-ti \
           <your_full_path>/potenco/sources/meta-linaro/meta-linaro-toolchain \
           <your_full_path>/potenco/sources/oe-core/meta \
           <your_full_path>/potenco/sources/meta-afero \
"

We must modify this file so it includes the directory that is the top of the meta-app layer we added. It’s easily created by copying the last line and then changing meta-afero to meta-app as shown in the bblayers.conf file below:

# This template file was created by taking the oe-core/meta/conf/bblayers.conf
# file and removing the BBLAYERS section at the end.
 
# LAYER_CONF_VERSION is increased each time build/conf/bblayers.conf
# changes incompatibly
LCONF_VERSION = "7"
 
BBPATH = "${TOPDIR}"
BBFILES ?= ""
# Layers configured by oe-core-setup script
BBLAYERS += " \
           <your_full_path>/potenco/sources/meta-processor-sdk \
           <your_full_path>/potenco/sources/meta-arago/meta-arago-distro \
           <your_full_path>/potenco/sources/meta-arago/meta-arago-extras \
           <your_full_path>/potenco/sources/meta-qt5 \
           <your_full_path>/potenco/sources/meta-openembedded/meta-networking \
           <your_full_path>/potenco/sources/meta-openembedded/meta-ruby \
           <your_full_path>/potenco/sources/meta-openembedded/meta-python \
           <your_full_path>/potenco/sources/meta-openembedded/meta-oe \
           <your_full_path>/potenco/sources/meta-ti \
           <your_full_path>/potenco/sources/meta-linaro/meta-linaro-toolchain \
           <your_full_path>/potenco/sources/oe-core/meta \
           <your_full_path>/potenco/sources/meta-afero \
           <your_full_path>/potenco/sources/meta-app \
"

Step 9. Build Application

We can now build the application; it will automatically be deposited in the staging directory. Type the following:

$ cd ~/potenco/build
$ bitbake app

Step 10. Copy App to Target Machine or Create SD Card Image

At this point you can take the app and use scp to copy it to the target machine to run; alternatively, you can create a full SD card image.

To create a full SD card image, we need to tell the core-image-minimal BitBake recipe that there is one more thing to include in the final image. This is not done automatically for a new layer and means that you have to edit the BitBake recipe to add it. This recipe is located in ./sources/meta-afero/recipes-core/images/ and is called “core-image-minimal.bb”.

  1. Take a look at the core-image-minimal.bb file. You find the following section named IMAGE_INSTALL that lists a large number of items:
    IMAGE_INSTALL = " \
        packagegroup-core-boot \
        kernel-modules \
        bash \
        udev-extraconf \
        base-files \
        module-init-tools \
        mtd-utils \
        mtd-utils-ubifs \
        curl \
        thermal-init \
        dbus \
        expat \
        glib-2.0 \
        libxml2 \
        libpcre \
        iptables \
        arago-gpl-notice \
        util-linux-fsck \
        i2c-tools \
        usbutils \
        zlib \
        libevent \
        json-c \
        libstdc++ \
        pru-icss \
        wl18xx-target-scripts \
        wpa-supplicant-wl18xx \
        bt-firmware \
        wl18xx-firmware \
        wlconf \
        iw \
        crda \
        backports \
        af-ipc \
        af-util \
        attrd \
        af-conn \
        af-sec \
        af-edge \
        otamgr \
        hubby \
        af-extras \
        dropbear \
        lrzsz \
    "
  2. As you can see, it lists a large number of elements that are to be included in the final rootfs. Just add “app \” to this list, like in the following version. Place the addition near the bottom, as shown:
    IMAGE_INSTALL = " \
        packagegroup-core-boot \
        kernel-modules \
        bash \
        udev-extraconf \
        base-files \
        module-init-tools \
        mtd-utils \
        mtd-utils-ubifs \
        curl \
        thermal-init \
        dbus \
        expat \
        glib-2.0 \
        libxml2 \
        libpcre \
        iptables \
        arago-gpl-notice \
        util-linux-fsck \
        i2c-tools \
        usbutils \
        zlib \
        libevent \
        json-c \
        libstdc++ \
        pru-icss \
        wl18xx-target-scripts \
        wpa-supplicant-wl18xx \
        bt-firmware \
        wl18xx-firmware \
        wlconf \
        iw \
        crda \
        backports \
        af-ipc \
        af-util \
        attrd \
        af-conn \
        af-sec \
        af-edge \
        otamgr \
        hubby \
        af-extras \
        app \
        dropbear \
        lrzsz \
    "
  3. Save the file. Now, after you use the command bitbake core-image-minimal the app will be deposited in /usr/bin in the root file system.
  4. You can take the resulting afimg file, flash it on to a micro SD card, insert it into the BBGW board, and boot up the system in the usual way by holding down the User button while the device is reset then continue to hold it until the blue LEDs start to flash, usually more than 10 seconds.

Now we will create the Potenco device Profile and publish it to the device.

Step 11. Create and Publish Device Profile

After the system boots, you can type app at the command prompt and the application will run. However, it won’t do anything until you have pushed a new profile to your Potenco device that includes two new attributes. Use the Afero Profile Editor to create a new Profile that defines these attributes. You can read details on using the Profile Editor in the Profile Editor User Guide, but the specific instructions follow:

  1. Open the latest version of the Afero Profile Editor and sign in with your Afero developer account. (Download for either Windows or macOS.)
  2. On the Startup window, select New. On the New Project dialog, complete the fields as follows:
    • Module Type: Potenco
    • Device Type Name: MyApp (or any name you prefer)
    • Project Folder: Default location is fine
    • Create Subfolder using Device Type Name: Leave selected

    Select the Create button.

  3. On the Define the Device Type window that appears, add a Description if you wish then select the Save button in the upper-right.
  4. Select Attributes in the left-hand Navigation pane. On the Define the MCU Attributes window, complete the fields as follows:
    • Supported Network Interfaces: Select Wi-Fi and WAN
    • Device Configuration, Receive UTC Time: Leave deselected
    • Device Attribute - Create two MCU attributes using the +Device Attribute button:
      • MCU attribute ID 1 - Name: int8, Size: SINT8, Default Value: 0, Operations: Read and Write
      • MCU attribute ID 2 - Name: int32, Size: SINT32, Default Value: 0, Operations: Read and Write

    Select the Save button in the upper-right.

  5. Select UI Controls in the left-hand Navigation pane. On the Define the UI Controls window, define two Value controls that will simply store and show the values to the app:

    1. First, define two attribute options using the +Attribute Option button in the right-hand pane:
      • int8 - Label: int8, leave remaining fields as shown
      • int32 - Label: int32, leave remaining fields as shown
    2. Next, define two controls using the +Control button:
      • Value control 1 - Attribute Option: int8, Control Type: Value, leave remaining fields as shown
      • Value control 2 - Attribute Option: int32, Control Type: Value, leave remaining fields as shown

    Select the Save button in the upper-right.

  6. Select UI Control Groups in the left-hand Navigation pane. On the Define the UI Control Groups window:
    1. Select the + button to create a new group.
    2. Name your control group as you wish by first selecting “New Group” then editing the name.
    3. Drag both Value controls from the right-hand pane into the group box.

    Select the Save button in the upper-right.

  7. Select Publish in the left-hand Navigation pane. On the Publish window:
    1. Select the checkbox adjacent to your Potenco device name.
    2. Select the Publish button. Your Profile will be published to your Potenco; monitor progress by watching the Device Activity pane.
    3. Once published, you can close the Afero Profile Editor.

Step 12. Open the App on the Device

On the Potenco device, invoke the app by typing app at the command prompt. It will look like the program is hanging, but it is waiting for the Wi-Fi signal strength to change. When it changes, an event is generated that the code will see and respond to by setting attribute 2 value to 8. To make this happen, you can simply cup your hands over the Wi-Fi antenna. What you will see is the app suddenly showing a value of 8 for the attribute ID 2 value.