AnyCloud WiFi Template + WiFi Helper Library (Part 2): Enable the WiFi Network

Summary

Instructions on using the AnyCloud Wireless Connection manager to enable WiFi.  This article is Part 2 of a series that will build a new IoT Expert template project for WiFi.

Article
(Part 1) Create Basic Project & Add Cypress Logging Functionality
(Part 2) Create New Thread to manage WiFi using the Wireless Connection Manager
(Part 3) Create a New Middleware Library with WiFi helper functions
(Part 4) Add WiFi Scan
Add WiFi Connect
Add WiFi Disconnect
Add WiFi Ping
Add Gethostbyname
Add MDNS
Add Status
Add StartAP
Make a new template project (update manifest)

Story

In the last article I got things going by starting from the old template, fixing up the Visual Studio Code configuration, adding the new Cypress Logging functionality and then testing everything.

In this article I will

  1. Create new task to manage WiFi
  2. Add Wireless Connection Manager to the project
  3. Create wifi_task.h and wifi_task.c
  4. Update usrcmd.c to send commands to the WiFi task

Create New Task to Manage WiFi

I am going to start by creating new a new task (called wifi_task) that will be responsible for managing the WiFi connection.  In Visual Studio Code you can create a new file by pressing the little document with the + on it.  You will need a file “wifi_task.h” and one “wifi_task.c”

Once you have wifi_task.h you will need to add the function prototype for the wifi_task.  In addition add a “guard”.  I like to use “#pragma once”

Here is copyable code.

#pragma once
void wifi_task(void *arg);

In wifi_task.c Ill start with a simple blinking LED function.  Well actually it will do a print instead of a blink.  Here it is:

#include "wifi_task.h"
#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>

void wifi_task(void *arg)
{
    while(1)
    {
        vTaskDelay(1000);
        printf("blink\n");

    }
}

Now that I have a wifi_task (which doesn’t do much) lets update main.c.  First include the wifi_task.h

#include "wifi_task.h"

Then create the task.  Notice that I start with a pretty big stack.

xTaskCreate(wifi_task,   "WiFi"       , configMINIMAL_STACK_SIZE*20,0 /* args */ ,0 /* priority */, 0);

When you run this, you will have the “blink” interleaved with the blink from the last article.

Add Wireless Connection Manager

You next step is to add the wireless connection manager.  Start the library browser by running “make modlibs”.  Then click on the wifi-connection-manager”.  Notice that when you do that, it will bring in a bunch of other libraries as well.  These are all libraries that it (the WiFi-Connection-Manager) is depend on.

If you look in the wireless connection manager documentation you will find this nice note.  It says that the WCM uses the Cypress logging functionality and you can turn it on with a #define.  That’s cool.  So now I edit the Makefile and add the define.

The the documentation also says that this library depends on the “Wi-Fi Middleware Core”

If you go to the Wi-Fi Middleware core library documentation you will see instructions that say that you need to

  1. Enable & Configure LWIP
  2. Enable & Configure MBEDTLS
  3. Enable & Configure the Cypress RTOS Abstraction

In order to do that you will need to two things

  1. Copy the configuration files into your project
  2. Setup some options in the Makefile

Start by copying the file.  They give you default configurations in mtb_share/wifi-mw-core/version/configs.  You will want to copy those files into your project.  This can be done in the Visual Studio Code interface using ctrl-c and ctrl-v

Notice that I now have two FreeRTOSConfig files.  So, delete the original file and rename the copied file.

Now your project should look like this:

The next step is to fix the Makefile by adding some defines.

 

DEFINES=CY_RETARGET_IO_CONVERT_LF_TO_CRLF
DEFINES+=CYBSP_WIFI_CAPABLE CY_RTOS_AWARE
DEFINES+=MBEDTLS_USER_CONFIG_FILE='"mbedtls_user_config.h"'
DEFINES+=ENABLE_WIFI_MIDDLEWARE_LOGS

The add the required components

COMPONENTS=FREERTOS LWIP MBEDTLS PSOC6HAL

Update wifi_task.c

My wifi task is going to work by

  1. Sitting on Queue waiting for messages of “wifi_cmd_t”
  2. When those messages come in, execute the right command.

Start by adding some includes to the wifi_task.c

#include <stdio.h>
#include "wifi_task.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"

#include "cy_wcm.h"

Then define the legal commands.  I will add a bunch of more commands in the future articles.  But for this article there will be only one command.  Enable.  The command message is

  1. The command
  2. Three args of unknown type

In addition you will require

  1. The command queue
  2. An initialize state variable
  3. A way to keep track of what mode you are in (AP, STA or APSTA)
typedef enum {
    WIFI_CMD_ENABLE,

} wifi_cmd_t;

typedef struct {
    wifi_cmd_t cmd;
    void *arg0;
    void *arg1;
    void *arg2;
} wifi_cmdMsg_t;


static QueueHandle_t wifi_cmdQueue;
static bool wifi_initialized=false;
static cy_wcm_interface_t wifi_network_mode;

The first “command” that I will create is the enable.  This will

  1. Setup the interface
  2. Initialize the WiFi.  The simple init command actually does a bunch of stuff, including powering on the wifi chip, downloading the firmware into it, setting up all of the tasks in the RTOS, enabling the LWIP and MBEDTLS
static void wifi_enable(cy_wcm_interface_t interface)
{
    cy_rslt_t result;
    cy_wcm_config_t config = {.interface = interface}; 
    result = cy_wcm_init(&config); // Initialize the connection manager
    CY_ASSERT(result == CY_RSLT_SUCCESS);

    result = cy_wcm_register_event_callback(wifi_network_event_cb);
    CY_ASSERT(result == CY_RSLT_SUCCESS);

    wifi_network_mode = interface;
    wifi_initialized = true;
    
    printf("\nWi-Fi Connection Manager initialized\n");
    
}

In the previous block of code notice that I register a callback.  The callback looks like a switch that prints out messages based on the event type.  Notice that there are three lines which are commented out – which we will fix in the next article.

static void wifi_network_event_cb(cy_wcm_event_t event, cy_wcm_event_data_t *event_data)
{
    cy_wcm_ip_address_t ip_addr;

    switch(event)
    {
        case CY_WCM_EVENT_CONNECTING:            /**< STA connecting to an AP.         */
            printf("Connecting to AP ... \n");
        break;
        case CY_WCM_EVENT_CONNECTED:             /**< STA connected to the AP.         */
            printf("Connected to AP and network is up !! \n");
        break;
        case CY_WCM_EVENT_CONNECT_FAILED:        /**< STA connection to the AP failed. */
            printf("Connection to AP Failed ! \n");
        break;
        case CY_WCM_EVENT_RECONNECTED:          /**< STA reconnected to the AP.       */
            printf("Network is up again! \n");
        break;
        case CY_WCM_EVENT_DISCONNECTED:         /**< STA disconnected from the AP.    */
            printf("Network is down! \n");
        break;
        case CY_WCM_EVENT_IP_CHANGED:           /**< IP address change event. This event is notified after connection, re-connection, and IP address change due to DHCP renewal. */
                cy_wcm_get_ip_addr(wifi_network_mode, &ip_addr, 1);
   //             printf("Station IP Address Changed: %s\n",wifi_ntoa(&ip_addr));
        break;
        case CY_WCM_EVENT_STA_JOINED_SOFTAP:    /**< An STA device connected to SoftAP. */
 //           printf("STA Joined: %s\n",wifi_mac_to_string(event_data->sta_mac));
        break;
        case CY_WCM_EVENT_STA_LEFT_SOFTAP:      /**< An STA device disconnected from SoftAP. */
//            printf("STA Left: %s\n",wifi_mac_to_string(event_data->sta_mac));
        break;
    }

}

Now I want to update the main loop of the WiFI task.  It is just an infinite loop that processes command messages (from other tasks).

void wifi_task(void *arg)
{
    wifi_cmdQueue = xQueueCreate(10,sizeof(wifi_cmdMsg_t));

    wifi_cmdMsg_t msg;

    while(1)
    {
        xQueueReceive(wifi_cmdQueue,&msg,portMAX_DELAY);
        switch(msg.cmd)
        {
            case WIFI_CMD_ENABLE:
                printf("Received wifi enable message\n");
                wifi_enable((cy_wcm_interface_t)msg.arg0);
            break;

        }
    }
}

In the other tasks in the system you “COULD” create a message and submit it to the queue.  I always think that it is easier if you create a function which can be called in the other threads.  Here is the wifi_enable function.  This function takes a char * of either “STA”, “AP”, or “APSTA” and then submits the right message to the queue.

bool wifi_cmd_enable(char *interface)
{
    wifi_cmdMsg_t msg;
    msg.cmd = WIFI_CMD_ENABLE;
    msg.arg0 = (void *)CY_WCM_INTERFACE_TYPE_STA;

    if(strcmp(interface,"STA") == 0)
        msg.arg0 = (void *)CY_WCM_INTERFACE_TYPE_STA;

    else if(strcmp(interface,"AP") == 0)
        msg.arg0 = (void *)CY_WCM_INTERFACE_TYPE_AP;

    else if(strcmp(interface,"APSTA") == 0)
        msg.arg0 = (void *)CY_WCM_INTERFACE_TYPE_AP_STA;
    
    else
    {
        printf("Legal options are STA, AP, APSTA\n");
        return false;
    }

    xQueueSend(wifi_cmdQueue,&msg,0);
    return true;
}

Once I have the nice function for the other tasks, I add it to the public interface in wifi_task.h

#pragma once
#include <stdbool.h>
void wifi_task(void *arg);
bool wifi_cmd_enable(char *interface);

Add a new user command “net”

Now that I have the wifi_task setup I want to add a “net” command to the command line shell.  Start by adding the include.

#include "wifi_task.h"

Then create a function prototype for a new command.

static int usrcmd_net(int argc, char **argv);

Add the command to the list of commands that the shell knows.

static const cmd_table_t cmdlist[] = {
    { "help", "This is a description text string for help command.", usrcmd_help },
    { "info", "This is a description text string for info command.", usrcmd_info },
    { "clear", "Clear the screen", usrcmd_clear },
    { "pargs","print the list of arguments", usrcmd_pargs},
#ifdef configUSE_TRACE_FACILITY 
#if configUSE_STATS_FORMATTING_FUNCTIONS ==1
    { "tasks","print the list of RTOS Tasks", usrcmd_list},
#endif
#endif
    { "net","net [help,enable]",usrcmd_net},
};

Then create the net command.  I want a BUNCH of net commands.  They will include help, enable, connect, disconnect, …. but for now we will start with enable.  This function just calls the wifi_enable command that we added to the wifi_task.h interface.

static int usrcmd_net(int argc, char **argv)
{
    if(argc == 1 || strcmp("help",argv[1]) == 0)
    {
        printf("net [help,enable,connect,disconnect,mdns,scan,ping,lookup]\n");

        printf("%-35s %s\n","net enable","Enable the WiFi Driver & load the WiFi Firmware");

        return 0;
    }

    if(strcmp("enable",argv[1])==0)
    {
            if(argc == 2)
                wifi_cmd_enable("STA");
            else
                wifi_cmd_enable(argv[2]);        
            return 0;
    }

    return 0;
}

Test

Program and test.  Now the “net enable” works.  Notice that it gives you the output about the wifi firmware being loaded into the chip.  Then it tells you that the chip is enabled and the connection manager is rolling.

In the next article I will create a new library of helper functions for wifi.

Tilt Hydrometer (Part 1) Overview & Out-of-Box

Summary

A discussion of a new series of articles about using the PSoC 6 + 43XXXX Wifi/Bluetooth combo chips to implement a data collection system for the Tilt2 Hydrometer.  Even if you aren’t specifically interested in hydrometers, this is a general purpose discussion of the design of an IoT device.

Story

In the middle of the Covid lockdown my 21-year-old daughter suggested that we start brewing beer.  This was always something that I have been interested in so I said “Sure!”.  What does this have to do with IoT you might ask?  I am an engineer and I love data.  Two of the key pieces of data that you are interested in while fermenting beer are:

  • The gravity of the beer
  • The temperature of the beer

If you don’t know anything about brewing beer, it is simple (people have been doing it a long time… even with no IoT)

  1. Start with grain
  2. Mill the grain
  3. Heat the grain with water to turn it into sugar water (called wort)
  4. Add yeast
  5. Wait while the yeast converts the sugar in the wort into alcohol and carbon dioxide
  6. Bottle the beer (or keg)
  7. Drink

Back to the metrics.  The “specific gravity” or just “gravity” is just the ratio of the density of your solution to plain water.  This is an indication of sugar in the wort solution.  At the start of the fermentation you will have “lots” of sugar, and no alcohol.  By the end you will have “lots” of alcohol and not much sugar.  You can tell how things are going by monitoring the gravity of the beer, which is a proxy metric for how much sugar has been converted to alcohol.

There are two common ways to measure the gravity:

  • A float hydrometer – sugar water is denser then water, so a “float” will float lower in the solution as the sugar gets converted to alcohol.
  • A refractometer – the index of refraction of the solution changes as the sugar concentration changes (this is an amazing old-school technology

As I was learning about this whole process I found the tilt hydrometer.  This device has

As the gravity of the beer changes, the device floats at a different angle (because it floats lower/higher).  They use the accelerometer to measure the apparent angle of gravity to calculate the angle of the device.  This angle is then used to calculate the density of the solution it is floating in.  They then broadcast the calculated gravity and temperature in Apple Bluetooth iBeacon format.

When I saw this, I thought “perfect” I know what to do with that.  I should build a device that can collect all of the data, display it, save it to an SPI flash and put it into the cloud.  It should look something like this: (each Tilt is identified by 1 of 8 colors… pink in this case).

Yes, I know they have an iPhone app, but I want to build a single device that sits in my brewery all of the time.  And yes I know they have a Raspberry Pi app, but that isn’t the point.

My device will have the following characteristics:

A Display with:

  • A Splash Screen
  • A Table of all Tilts, Gravity and Temperature
  • Single: One screen per tilt with the specific data including debugging
  • Single: A graph of the active data for one specific tilt
  • Single: A table of all of the recordings from that specific tilt
  • The WiFi Status
  • The Bluetooth Status

Bluetooth System that can:

  • Record tilt data as broadcast in iBeacon advertising packets
  • Repeat tilt data (maybe)
  • Introducer WiFi (probably)

CapSense button GUI to:

  • Next Screen
  • Auto Mode
  • Reset current
  • Dump recorded data to the SD Card

Command Line

  • A UART based command line to debug & learn

USB

  • Mass Storage to see files
  • USB <-> UART Bridge

Power Supply via USB Port

  • Plug in Type-C using Cypress BCR

WiFi

  • MQTT Publish to AWS
  • NTP – to find the time
  • Local webserver
  • MDNS

RTC

  • Keep Track of current Time

SPI NOR Flash

  • Record the data

SD CARD

  • Dump the fixed SPI Flash  recordings to a removable SD CARD & remove data from the SPI Flash

Here is another picture of what I am thinking (well actually what I implemented for this series of articles)

Un-boxing

To get this show on the road, I ordered three tilts and two repeaters from Baron Brew Equipment.

It included a neat little quick start picture showing how to get going.

Then the box of goodies.

There are 8-possible tilts, Red, Green, Orange, Blue, Black, Yellow, Purpose and Pink (each Tilt his “hardcoded” to identify itself as a specific color)

Tilt Hydrometer

Here is a picture of the “blue” one (notice I put the wrong box in the picture)

The tilt comes in a plastic tube.  Which has a label to remind you to take it out of the tube (my experience is that you should be embarrassed to have to read most warning labels 🙂 )

It is about 100mm long (about 4 inches).  The bluetooth module is at the top, U3 is the temperature sensor and U2 (which is under the black 3-d printed plastic) is the accelerometer.

Repeater

If you put a Bluetooth device floating in a bunch of beer, surrounded by a metal fermentation container, you will not be able to hear the Bluetooth signal.  To solve this problem the Tilt people made a repeater which can rest on the top of the fermenter.  It listens for the weak signal, then rebroadcasts with a higher gain antenna.

Here is a picture of the repeater.  Notice that it uses the BMD-301 which has an external SMA antenna.

It also comes in a nice plastic tube.

The repeater can only re-broadcast one color at a time.  The button to switches between the 8 colors and off.

Each time you press the button the 3-color LED lights up with the color that represents which tilt color that it is repeating. Red->Green->… Pink->Off

It also has a huge rechargeable battery.

The Plan

Here is a list of the articles that I plan to write

This series is broken up into the following 12 articles with a few additional possible articles. 

Tilt Hydrometer (Part 1) Overview & Out-of-Box

Tilt Hydrometer (Part 2) Architecture

Tilt Hydrometer (Part 3) Advertising Scanner

Tilt Hydrometer (Part 4) Advertising Packet Error?

Tilt Hydrometer (Part 5) Tilt Simulator & Multi Advertising iBeacons

Tilt Hydrometer (Part 6) Tilt Simulator an Upgrade

Tilt Hydrometer (Part 7) Advertising Database

Tilt Hydrometer (Part 8) Read the Database

Tilt Hydrometer (Part 9) An LCD Display

Tilt Hydrometer (Part 10) The Display State Machine

Tilt Hydrometer (Part 11) Draw the Display Screens

Tilt Hydrometer (Part 12) CapSense

Tilt Hydrometer: LittleFS & SPI Flash (Part ?)

Tilt Hydrometer: WiFi Introducer (Part ?)

Tilt Hydrometer: WebServer (Part ?)

Tilt Hydrometer: Amazon MQTT (Part ?)

Tilt Hydrometer: Printed Circuit Board (Part ?)

You can get the source code from git@github.com:iotexpert/Tilt2.git  This repository has tags for each of the articles which can be accessed with "git checkout part12"  You can find the Tilt Simulator at  git@github.com:iotexpert/TiltSimulator.git.

 

AnyCloud Bluetooth Advertising Scanner (Part 10)

Summary

We have finally reached the end of the AnyCloud Bluetooth Advertising Scanner.  In this article I will add the ability to sort the database.  In addition I will add the ability to purge a device.  And finally, truly finally, a bit of commentary.

Story

I originally built this program to help me learn about the AnyCloud Bluetooth SDK.  Well, originally I built this functionality to try to find and talk to a specific device (in an upcoming series).  The problem is that there are so many devices at my house that are blasting out so much data it is hard to see what I am looking for.  What I realized would help is add the ability to sort the devices from newest to oldest.  In addition I noticed that occasionally my database would fill up… and it would be nice to purge out old entries.  So that is what we are going to do.

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Fix the Database Data Structure

You might remember that the database was built as an array of structures.  This mean that any moving around of the data would be a require a replacement of the whole structure.

static adb_adv_t adb_database[ADB_MAX_SIZE];

To fix this problem I moved the database to a an array of pointers.

static adb_adv_t *adb_database[ADB_MAX_SIZE];

To support this, when I see a new device I malloc a block of memory to hold the actual structure.

    // If it is NOT found && you have room
    if(entry == -1)
    {
        adb_database[adb_db_count] = malloc(sizeof(adb_adv_t));

Then I had to fix all of the references to the structure.  And there were a bunch (actually 43 of them).  But the replacement was pretty simple

adb_database[…].xxx is replaced by adb_database[…]-> …. here are the three different cases

case 1: adb_database[adb_db_count].

case 2: adb_database[entry].

case 1: adb_database[i].

That was actually way less painful that I thought it was going to be.  Probably what would actually be best is a library of these data structures with an API that would not have changed when the key changed, but that I suppose, is for another day.

Add Two New Commands

Now I add the sort and purge commands to my command list.

typedef enum {
    ADB_ADD,
    ADB_PRINT_RAW,
    ADB_PRINT_DECODE,
    ADB_WATCH,
    ADB_ERASE,
    ADB_RECORD,
    ADB_FILTER,
    ADB_SORT,
    ADB_PURGE,
} adb_cmd_t;

Create the Sort Functionality

To sort, I will use the c-standard library function qsort.  It requires a function that compares two entries a/b and returns

  1. a negative number of a<b
  2. 0 if a=b
  3. a positive number if a>b

Here is the function.  Hey Hassane you like those pointers?

static int adb_sort_cmpfunc(const void * a, const void * b) 
{
    adb_adv_t *p1 = *((adb_adv_t **)a);
    adb_adv_t *p2 = *((adb_adv_t **)b);
        
    return p2->lastSeen - p1->lastSeen;
}

The sort is actually really simple now.  Just a call to sort (then I decided to print out the table)

                case ADB_SORT:
                    qsort(adb_database, adb_db_count, sizeof(adb_adv_t *), adb_sort_cmpfunc);
                    adb_db_print(ADB_PRINT_METHOD_BYTES,true,-1);
                break;

Now instead of this….

I get this…

Create the Purge Functionality

The purge function needs to do two things

  1. Free all of the memory from an entry
  2. Move the pointers so that the “purged” entry is gone.

First I erase all of the data in the linked list with the adb_eraseEntry function.

Then I free the head of the list

Then I free the actual structure

Then I move all of the pointers to squeeze the able.

static void adb_purgeEntry(int entry)
{

    adb_eraseEntry(entry);
    free(adb_database[entry]->list);
    free(adb_database[entry]->result);
    free(adb_database[entry]);
    adb_db_count -= 1;
    for(int i=entry;i<adb_db_count;i++)
    {
        adb_database[i] = adb_database[i+1];
    }
}

And you need to add the actual command.

                case ADB_PURGE:
                    if((int)msg.data0<0 || (int)msg.data0>=adb_db_count)
                    {
                        printf("Purge error %d\n",(int)msg.data0);
                        break;
                    }   
                    adb_purgeEntry((int)msg.data0);
                break;

The End & Commentary

I would like to add and maybe will one day:

  1. A connect function with a GATT browser
  2. A smarter way to deal with the fact that device change addresses

Finally a couple of comments about this

  1. You might notice that I don’t check very many possible errors.  I do this in the interest of simpler to read code.  This is a tradeoff that I make for “teaching” code.  I hope that you understand that if you want to do something like this in a real product that you need to be much more careful.
  2. I don’t have unit testing.  This falls into the same category as the error checking.  Really this is a bad idea as code without unit testing is obsolete the second it comes out of your fingers.  But, it is easier to read.
  3. I don’t have many comments.  This is something that my colleagues bitch about all of the time with me.  And I know that it must be a personality defect.
  4. I use malloc/free all over the place.  This is a religious war.  You can make a static allocation scheme, but it would be really complicated in this case.  I personally think that the tradeoff of using a battle worn and tested malloc/free is totally worthwhile against the complexity of custom static memory allocation schemes.

AnyCloud Bluetooth Advertising Scanner (Part 9)

Summary

In this series of articles I am building a Bluetooth Low Energy Scanner using the Cypress/Infineon AnyCloud SDK running on a PSoC 6 and CYW43xxx.  In Part 9, I will fix a memory leak, add packet age, and improve the printing.

Story

You might be starting to wonder if this series is ever going to end.  Well, this article and one more.  That is it.

This morning as I was looking at the serial console window I noticed that I had hit the limit of device in the buffer,  OK.  But that it had also crashed, gone, bye bye, so long … the long dark road.  That needs fixing.

I also was curious when I looked at the output, how long ago I had seen the packets/devices.  So I decided that having “age” in the database made sense.

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Fix the Memory Leak

I noticed that a while after I started getting the message “ADV Table Max Size” that things would crash.  But Why?  The answer is a memory leak – go figure. I originally thought when I get a new device I would just overwrite the last entry in the table.  But, when I overwrote the adb_database[adb_db_count]. record with a new scan_result and a new list, I left memory that was previously allocated, here is the code:

    if(entry == -1)
    {
        adb_database[adb_db_count].result = scan_result;
        adb_database[adb_db_count].listCount = 1;
        adb_database[adb_db_count].record = false;
        adb_database[adb_db_count].filter = true;
        adb_database[adb_db_count].numSeen = 1;

        adb_adv_data_t *current = malloc(sizeof(adb_adv_data_t));
        current->next = 0;
        current->data = data;
        current->count = 1;

        adb_database[adb_db_count].list = current;

        adb_db_count = adb_db_count + 1;
        if(adb_db_count == ADB_MAX_SIZE)
        {
            printf("ADV Table Max Size\n");
            adb_db_count = adb_db_count - 1;
        }
        else
        {    
            adb_db_print(ADB_PRINT_METHOD_BYTES,false,adb_db_count-1);
        }
    }

A cheap fix is to just stop making new entries when the database runs out of room.

    // If there is a new entry and you ran out of space
    if(entry == -1 && adb_db_count >= ADB_MAX_SIZE)
    {
        free(scan_result);
        free(data);
        return;
    }

Add Age

As I mentioned earlier I wanted to keep track of

  1. The last time I had seen a device
  2. When I saw that specific advertising packet

The FreeRTOS has a free running millisecond counter that starts a 0 and counts up to 2^32.  A really cheap way to keep track of time is just to use this counter. To do this the first step is add the time to the database.  Both in the device record and the packet record.

typedef struct {
    uint8_t *data;
    int count;
    TickType_t lastSeen;
    struct adb_adv_data_t *next;
} adb_adv_data_t;

typedef struct {
    wiced_bt_ble_scan_results_t *result;
    bool record;
    bool filter;
    int numSeen;
    int listCount;
    TickType_t lastSeen;
    adb_adv_data_t *list;
} adb_adv_t ;

Update the Printing

I am going to make the output look like this with a new column representing the seconds since I heard the packet.  In the picture below you can see that I heard 00 at 0.0 seconds ago…  Then you can see that I had a recording of device 5 where I have a bunch of packets that I heard back into time.

To do this I just add a time calculation like this:

static void adb_db_printEntry(adb_print_method_t method, int entry, adb_adv_data_t *adv_data)
{
    float time = ((float)xTaskGetTickCount() - (float)(adv_data->lastSeen))/1000;

    printf("%c%c%02d %05d %03d %6.1f ",adb_database[entry].watch?'W':' ',
    adb_database[entry].filter?'F':' ',
    entry,adb_database[entry].numSeen,adb_database[entry].listCount,
    time);

Fix up the Add

The next thing that I need to do is make the “add” function add the time.  The problem is that this function has gotten totally totally out of control.  It turns out that there are x different possibilities

  1. Ignore the packet (because the table is full)
  2. Add a new device & packet
  3. Update the head of the list with a new packet
  4. Insert a new packet at the head of the list
  5. If you are filtering update a duplicated packet count

The code for these branches all looked somewhat similar.  But, which branch to take depended on

  1. If you were “watching” that device
  2. If you were “filtering” that device
  3. If you were “recording”
  4. If you had seen that packet before (aka it was found)

I ended up making a truth table:

Watch Filter Recording Found Action
0 0 0 0 update the head
0 0 0 1 update the head
0 0 1 0 update the head
0 0 1 1 update the head
0 1 0 0 update the head
0 1 0 1 update the head
0 1 1 0 update the head
0 1 1 1 update the head
1 0 0 0 update the head
1 0 0 1 update the head
1 0 1 0 insert at the head
1 0 1 1 insert at the head
1 1 0 0 update the head
1 1 0 1 update the found
1 1 1 0 insert at the head
1 1 1 1 update the found

The case where you

  1. Had no room
  2. Saw a new device

Look like this:

static void adb_db_add(wiced_bt_ble_scan_results_t *scan_result,uint8_t *data)
{

    TickType_t timeSeen = xTaskGetTickCount();

    int entry = adb_db_find(&scan_result->remote_bd_addr);

    // If there is a new entry and you ran out of space
    if(entry == -1 && adb_db_count >= ADB_MAX_SIZE)
    {
        free(scan_result);
        free(data);
        return;
    }
    
    // If it is NOT found && you have room
    if(entry == -1)
    {
        adb_database[adb_db_count] = malloc(sizeof(adb_adv_t));
        adb_database[adb_db_count]->result = scan_result;
        adb_database[adb_db_count]->listCount = 1;
        adb_database[adb_db_count]->watch = false;
        adb_database[adb_db_count]->filter = true;
        adb_database[adb_db_count]->numSeen = 1;
        adb_database[adb_db_count]->lastSeen = timeSeen;

        adb_adv_data_t *current = malloc(sizeof(adb_adv_data_t));
        current->next = 0;
        current->data = data;
        current->numSeen = 1;
        current->lastSeen = timeSeen;

        adb_database[adb_db_count]->list = current;

        adb_db_count = adb_db_count + 1;    
        adb_db_print(ADB_PRINT_METHOD_BYTES,false,adb_db_count-1);

        return; 
    }

At this point in the code you know that you have seen this device before.  If you are filtering you should look in the linked list to see if you can find the specific packet (lines 1-15).

If you look at the truth table above you will see three cases where you should insert at the head of this list.  Those cases are identified with the sprawling if on lines 17-21).  Once you identify that scenario you do the needful.

    adb_adv_data_t *updateItem=0; 

    if(adb_database[entry].filter) // if filtering is on.
    {
        int len = btutil_adv_len(data); // ARH maybe a bug here
        
        for(adb_adv_data_t *list = adb_database[entry].list;list;list = (adb_adv_data_t *)list->next)
        {
            if(memcmp(list->data,data,len) == 0) // Found the data
            {
                updateItem = list;
                break;
            }
        }
    }

    // insert at the head
    if( (adb_database[entry].watch && !adb_database[entry].filter && adb_recording && !updateItem) ||
        (adb_database[entry].watch && !adb_database[entry].filter && adb_recording && updateItem) ||
        (adb_database[entry].watch && adb_database[entry].filter && adb_recording && !updateItem)
    )
    {
        adb_adv_data_t *updateItem = malloc(sizeof(adb_adv_data_t)); // make new data
        updateItem->next = (struct adb_adv_data_t *)adb_database[entry].list;
        updateItem->numSeen = 1;
        updateItem->data = data;
        updateItem->lastSeen = timeSeen;

        adb_database[entry].list = updateItem;
        adb_database[entry].numSeen += 1;
        adb_database[entry].lastSeen = timeSeen;
        adb_database[entry].listCount += 1;
        free(scan_result);
        
        adb_db_print(ADB_PRINT_METHOD_BYTES,false,entry);


        adb_recording_count += 1;
        if(adb_recording_count == ADB_RECORD_MAX)
        {
            adb_recording = false;
            printf("Recording buffer full\n");
        }
        return;
    }

The final case happens when you are just going to update a found packet.

    if(updateItem == 0)
        updateItem = adb_database[entry].list;


    adb_database[entry].numSeen += 1;
    adb_database[entry].lastSeen = timeSeen;

    updateItem->lastSeen = timeSeen;

    int len = btutil_adv_len(data); // ARH maybe a bug here
    if(memcmp(updateItem->data,data,len) == 0)
    {
        updateItem->numSeen += 1;
    }
    else
    {
        updateItem->numSeen = 1;   
    }

    free(updateItem->data);
    updateItem->data = data;
    free(scan_result);

}

In the next article I will add

  1. Sort
  2. Purge

Then I will call it  a day.

AnyCloud Bluetooth Advertising Scanner (Part 8)

Summary

In this series of articles I am building a Bluetooth Low Energy Scanner using the Cypress/Infineon AnyCloud SDK running on a PSoC 6 and CYW43xxx.  In Part 8 I will turn on the ability to filter duplicate advertising packets.

Story

In the previous article I added the ability to record advertising packets.  The problem is, of course, that many devices are blasting out advertising packets, which will quickly overwhelm you.  I suppose more importantly it will overwhelm the packet buffer.  Most of the device are just advertising their presence, so they send the same data over and over.  Some devices alternate between a small number of different advertising packets, e.g. an iBeacon then and Eddystone beacon.

The way that the filter will work is that I will update the “add” function to search through all of the packets that device has launched, then if I have seen the packet before (Ill use a memcmp) then I will just keep a count of how many times I have see that packet.

The other thing that needs to happen is for me to add a “filter” command so that I can turn on packet filtering on a device by device basis.

And I need to fix the printing to use the new filtered packet database.

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Update the Data Structures

In order to do the filters, and keep track of the data I will add

  1. A “count” field to the packet data structure
  2. A “filter” boolean field to the device data structure.
typedef struct {
    uint8_t *data;
    int count;
    struct adb_adv_data_t *next;
} adb_adv_data_t;

typedef struct {
    wiced_bt_ble_scan_results_t *result;
    bool record;
    bool filter;
    int numSeen;
    int listCount;
    adb_adv_data_t *list;
} adb_adv_t ;

Update the Add Function

In the function “static void adb_db_add(wiced_bt_ble_scan_results_t *scan_result,uint8_t *data)” which is called every time I see a new advertising packet I will need to handle four different cases:

  1. The first time you see a device
  2. A device you have seen AND you are recording AND have the filtering turned on
  3. A device you are recording but not filtering
  4. A device you have seen, but are not recording or filter.

In the first case, a device you haven’t seen before, you need to

  1. Automatically turn on the filtering
  2. Initialize the counts to 1
  3. Do the other initialization (as before)
    // If it is NOT found
    if(entry == -1)
    {
        adb_database[adb_db_count].result = scan_result;
        adb_database[adb_db_count].listCount = 1;
        adb_database[adb_db_count].record = false;
        adb_database[adb_db_count].filter = true;
        adb_database[adb_db_count].numSeen = 1;

        adb_adv_data_t *current = malloc(sizeof(adb_adv_data_t));
        current->next = 0;
        current->data = data;
        current->count = 1;

In the case where you have

  1. See the device before
  2. You have room in the record buffer
  3. And you are in record mode

You will then decide if you are filtering.

Then you will iterate through all of the packets and compare the data to the data you just received.  If there is a match then you update the count, free the duplicate data and return.

    else if(adb_database[entry].record && adb_recording_count<ADB_RECORD_MAX && adb_recording)
    {
        adb_database[entry].numSeen += 1;

        if(adb_database[entry].filter) // if filtering is on.
        {
            int len = btutil_adv_len(data); 
            for(adb_adv_data_t *list = adb_database[entry].list;list;list = (adb_adv_data_t *)list->next)
            {
                if(memcmp(list->data,data,len) == 0) // Found the data
                {
                    list->count += 1;
                    printf("Count = %d\n",list->count);
                    free(data);
                    free(scan_result);
                    return;
                }
            }
        }

If you have not see the data before, then you need to add it to the linked list.

        adb_adv_data_t *current = malloc(sizeof(adb_adv_data_t));
        current->next = (struct adb_adv_data_t *)adb_database[entry].list;
        current->data = data;
        current->count = 1;

If you are not recording and not filtering, just increment counts.

    else
    {
        adb_database[entry].numSeen += 1;
        adb_database[entry].list->count += 1;

Add a “filter” Command

I want the ability for a user to “filter all” or “filter clear” or “filter #” – just like we did with watch.  So, add the #defines and new function to advDatabase.h

#define ADB_FILTER_ALL -1
#define ADB_FILTER_CLEAR -2
void adb_filter(int entry);

Then add the new filter command in advDatabase.c

typedef enum {
    ADB_ADD,
    ADB_PRINT_RAW,
    ADB_PRINT_DECODE,
    ADB_WATCH,
    ADB_ERASE,
    ADB_RECORD,
    ADB_FILTER,
} adb_cmd_t;

I will use the adb_queueCmd function that I created in the last article.

inline void adb_filter(int entry) { adb_queueCmd(ADB_FILTER,(void*)entry,(void *)0); }

The filter command has three cases

  1. All – turn the bool for all on
  2. Clear – turn the bool for all off
  3. Just a specific number – toggle that specific number
static void adb_db_filter(int entry)
{
    if(entry == ADB_FILTER_ALL)
    {
        for(int i=0;i<adb_db_count;i++)
        {
            adb_database[i].filter = true;
        }
        return;
    }

    if(entry == ADB_FILTER_CLEAR)
    {
        for(int i=0;i<adb_db_count;i++)
        {
            adb_database[i].filter = false;
        }
        return;
    }

    if(entry > adb_db_count-1 || entry < ADB_WATCH_CLEAR)
    {
        printf("Record doesnt exist: %d\n",entry);
        return;      
    }
    adb_database[entry].filter = !adb_database[entry].filter; 

}

And you need to fix up the main command processor

                case ADB_FILTER:
                    adb_db_filter((int)msg.data0);
                break;

Finally add the command to the usrcmd.c

static int usrcmd_filter(int argc, char **argv)
{

    if(argc == 2 && !strcmp(argv[1],"all"))
    {
        adb_filter(ADB_FILTER_ALL); // all
        return 0;
    }


    if(argc == 2 && !strcmp(argv[1],"clear"))
    {
        adb_filter(ADB_FILTER_CLEAR);
        return 0;
    }

    if(argc == 2)
    {
        int i;
        sscanf(argv[1],"%d",&i);
        adb_filter(i);
        return 0;
    }

    return 0;
}

Fix the Printing

Now we nee to fix the printing.  I want to add an indicate of the filtering to the output.  Remember from the previous article I indicated “Watch” with a “*”.  When I looked at it, I decided that I should indicate filter with an “F” and watch with a “W”.  So I fix that.

        printf("%c%c%02d %05d %03d MAC: ",adb_database[i].record?'W':' ',
            adb_database[i].filter?'F':' ',
            i,adb_database[i].numSeen,adb_database[i].listCount);
        btutil_printBDaddress(adb_database[i].result->remote_bd_addr);
        switch(method)
        {

Then I test the “filter” command

Fix the Printing Part (2)

As I noodled on how to change the printing I decide that it would be nice to sometimes print out only one packet e.g. no history and sometimes print them all out e.g. history.  So, I add a new parameter to the function called “history”

static void adb_db_print(adb_print_method_t method,bool history,int entry)

As I looked at the printing code, I decided that it would be better to have a new function to print only one entry.  I suppose that I could have left the code inline, but I thought that intent was clearer.

static void adb_db_printEntry(adb_print_method_t method, int entry, adb_adv_data_t *adv_data)
{
    printf("%c%c%02d %05d %03d MAC: ",adb_database[entry].record?'W':' ',
    adb_database[entry].filter?'F':' ',
    entry,adb_database[entry].numSeen,adb_database[entry].listCount);
    btutil_printBDaddress(adb_database[entry].result->remote_bd_addr);

    switch(method)
    {
    
    case ADB_PRINT_METHOD_BYTES:
        printf(" Data: ");
        btutil_adv_printPacketBytes(adv_data->data);
    break;

    case ADB_PRINT_METHOD_DECODE:
        printf("\n");
        btutil_adv_printPacketDecode(adv_data->data);
    break;
    } 
    printf("\n");

}

With the new function in place I now need to update the print function to call the new entry function.  Printing the history is just a matter of iterating through the linked list.

static void adb_db_print(adb_print_method_t method,bool history,int entry)
{
    int start,end;
 
    if(entry < 0)
    {
        start = 0;
        end = adb_db_count;
    }
    else
    {
        start = entry;
        end = entry+1;
    }

    if(end>adb_db_count)
        end = adb_db_count; 

    for(int i=start;i<end;i++)
    {
        if(history) // then iterate through the linked list print all of the packets
        {
            for(adb_adv_data_t *list = adb_database[i].list;list;list = (adb_adv_data_t *)list->next)
            {
                adb_db_printEntry(method,i,list);    
            }
        }
        else // Just print the first packet in the list
            adb_db_printEntry(method,i,adb_database[i].list);
    }
}

Now, I need to update all of the calls to adb_db_print to have the new history parameter.  First, I made the decision that when you “print” from the command line that you are interested in the history.

                case ADB_PRINT_RAW:
                    adb_db_print(ADB_PRINT_METHOD_BYTES,true,(int)msg.data0);
                break;
                case ADB_PRINT_DECODE:
                    adb_db_print(ADB_PRINT_METHOD_DECODE,true,(int)msg.data0);
                break;

But when you are printing out the packet for a new device don’t print out the history

            adb_db_print(ADB_PRINT_METHOD_BYTES,false,adb_db_count-1);

Program and Test

After I program my development kit, I start by typing “watch all”.  Very quickly, at my house, you can see that a bunch of devices are discovered.  You can see that all of these have a “W” (meaning that I am watching them) and an “F” meaning they are filtering out duplicates.  Then I type “record” to turn on recording. After a minute I turn off recording then do the print you can see below.

A couple of things to notice.

  1. Device #4 (which I highlighted) appears to be sending out a pattern of alternating packets.  See that I have heard 3335 packets yet there are only two in the buffer
  2. You can see device 11 seems to be sending out 16 different packets.  Why?  I don’t know.

But we can “decode 11” to try to figure it out.  You can see that it is advertising manufactures specific data with Apple’s UUID which I happen to know is 0x004C.  But why?  I don’t know.

I really want to move onto a new series of articles… but there are two functions which I will add to the program.  Stay tuned for what they do.

AnyCloud Bluetooth Advertising Scanner (Part 7)

Summary

In this series of articles I am building a Bluetooth Low Energy Scanner using the Cypress/Infineon AnyCloud SDK running on a PSoC 6 and CYW43xxx.  In Part 7 I will add the ability to record BLE ADV packets.

Story

If you have been reading along, at this point we have built a BLE scanner that can see Bluetooth devices that are advertising.  My scanner has a command line and you can print out the most recent data.  Even better, we built a decoder that allows you to better understand the data.

Now I want to add the ability to record more than one advertising packet per device.  To that end I will add three commands:

  • watch – Mark a device as one that needs to have the advertising data recorded.  You can type “watch 12” or you can say “watch all” or you can say “watch clear”
  • record – Turn on recording of “watched” devices.  When you type record it will toggle the recording state between On and Off.
  • erase – clear the record buffer of all but the most recent packet.

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Update the advDatabase Interface

The first thing I realized as I went to add a new command was that I was typing the exact same code over and over for the public interface.  The code looked like this:

void adb_watch(int entry)
{
    adb_cmdMsg_t msg;
    msg.cmd = ADB_WATCH;
    msg.data0 = (void *)entry;
    xQueueSend(adb_cmdQueue,&msg,0); // If the queue is full... oh well

}

So I created this:

static void adb_queueCmd(adb_cmd_t cmd,void *data0, void *data1)
{
    adb_cmdMsg_t msg;
    msg.cmd = cmd;
    msg.data0 = data0;
    msg.data1 = data1;
    xQueueSend(adb_cmdQueue,&msg,0); // If you loose an adv packet it is OK...
}

Then did this to eliminate the duplication.

inline void adb_addAdv(wiced_bt_ble_scan_results_t *scan_result,void *data) { adb_queueCmd(ADB_ADD,(void *)scan_result,(void *)data);}
inline void adb_print(int entry) { adb_queueCmd(ADB_PRINT_RAW,(void *)entry,(void *)0); }
inline void adb_decode(int entry) { adb_queueCmd(ADB_PRINT_DECODE,(void*)entry,(void *)0); }

Redo the Database

If you recall from previous posts, my advertising database was just

  1. An Array of structures
  2. Each structure contained the mac address and…
  3. A pointer to a malloc’d copy of the advertising data
typedef struct {
    wiced_bt_ble_scan_results_t *result;
    uint8_t *data;
} adb_adv_t ;

#define ADB_MAX_SIZE (40)
adb_adv_t adb_database[ADB_MAX_SIZE];

Here is a picture of the datastructure

 

Now what I want to do is make the “data” pointer to be a pointer to a linked list of data.  Here is the new definition.

typedef struct {
    uint8_t *data;
    struct adb_adv_data_t *next;
} adb_adv_data_t;

typedef struct {
    wiced_bt_ble_scan_results_t *result;
    int listCount;
    bool record;
    int numSeen;
    adb_adv_data_t *list;
} adb_adv_t ;

The new data structure looks like this

I wanted to limit the number advertising packets that can be stored so I don’t run out of memory.  I am not actually sure how many can be stored, but I suppose a bunch as the chip has 1MB of RAM.  But, I pick 100 which seems like enough to start out with.  I create two variables

  1. A counter for the number of advertising packets that are currently saved
  2. A recording state (are you saving or not)
#define ADB_RECORD_MAX (100)
static int adb_recording_count = 0;
static bool adb_recording = false;

Update the Printing

You probably noticed that I declared two new members of the adb_adv_t structure, specifically numSeen and listCount, which I would like to print out.   I also wanted a visual indication that I am “watching” a device.  The columns are now:

  1. A “*” to indicate that a device is being watched
  2. The device #
  3. The number of packets that I have seen in total from that device
  4. The number of recorded packets for that device
  5. The MAC address
  6. The raw bytes

To do implement this a simple change is made to the print function:

    for(int i=start;i<end;i++)
    {
    
        printf("%s%02d %05d %03d MAC: ",adb_database[i].record?"*":" ",i,adb_database[i].numSeen,adb_database[i].listCount);
        btutil_printBDaddress(adb_database[i].result->remote_bd_addr);
        switch(method)
        {
        
        case ADB_PRINT_METHOD_BYTES:
            printf(" Data: ");
            btutil_adv_printPacketBytes(adb_database[i].list->data);
        break;

        case ADB_PRINT_METHOD_DECODE:
            printf("\n");
            btutil_adv_printPacketDecode(adb_database[i].list->data);
        break;
        } 
        printf("\n");
    }

Update the Add

Now that we have all of the infrastructure in place we need to update the function that saves advertising data.  When an advertising packet comes in you have three situations to consider:

  1. You have never seen the device before
  2. You have seen the device before and you are “watching” it
  3. You have see the device but you are not watching it

In the case where you have never seen the device you need to

  1. Save the scan result
  2. Set the listCount to 1 (you only have one datapoint)
  3. Turn off recording (start with the recording off)
  4. Set the total numSeen to 1 as this is the first packet you have seen
  5. Allocate some memory for the advertising linked list structure
  6. Terminate the linked list
  7. Save the advertising data
  8. Increment the database count (up to the max)
  9. Print out the packet you just saw
    if(entry == -1)
    {
        adb_database[adb_db_count].result = scan_result;
        adb_database[adb_db_count].listCount = 1;
        adb_database[adb_db_count].record = false;
        adb_database[adb_db_count].numSeen = 1;

        adb_adv_data_t *current = malloc(sizeof(adb_adv_data_t));
        current->next = 0;
        current->data = data;

        adb_database[adb_db_count].list = current;

        adb_db_count = adb_db_count + 1;
        if(adb_db_count == ADB_MAX_SIZE)
        {
            printf("ADV Table Max Size\n");
            adb_db_count = adb_db_count - 1;
        }
        else
        {    
            adb_db_print(ADB_PRINT_METHOD_BYTES,adb_db_count-1);
        }
    }

In the case where you have

  1. Seen the device before
  2. You are recording that device
  3. There is room left in the recording buffer

Then you will

  1. Increment number seen
  2. Create memory for the new entry in the linked list
  3. Attach the tail of the linked list to your new entry (you will insert at the front of the list)
  4. Save the data
  5. Increment the number of saved entries
  6. Insert your new packet at the head of the list
  7. Printout the packet
  8. Increment the record count (the total number of packets in the recording buffer)
  9. Then potentially stop recording if you have gotten to the max size.
    else if(adb_database[entry].record && adb_recording_count<ADB_RECORD_MAX && adb_recording)
    {
        adb_database[entry].numSeen += 1;

        adb_adv_data_t *current = malloc(sizeof(adb_adv_data_t));
        current->next = (struct adb_adv_data_t *)adb_database[entry].list;
        current->data = data;
        adb_database[entry].listCount += 1;
        adb_database[entry].list = current;

        adb_db_print(ADB_PRINT_METHOD_BYTES,entry);

        adb_recording_count += 1;
        if(adb_recording_count == ADB_RECORD_MAX)
        {
            adb_recording = false;
            printf("Recording buffer full\n");
        }
    }

In the case where you have seen the device before, but you are not recoding then you will

  1. Update the numSeen
  2. Erase the old packet data
  3. Save the new packet
  4. Erase the “result” (you already have it saved)
    else
    {
        adb_database[entry].numSeen += 1;
        free(adb_database[entry].list->data);
        adb_database[entry].list->data = data;
        free(scan_result);
    }

Add a Watch Command

The watch function is pretty simple.  It just needs to either mark the “record” boolean as true or false.  When I decided to implement this function I decided to make positive numbers be the entry in the table.  But, I also wanted to be able to “watch all” and “watch clear”.  So, I used negative numbers for those two special meanings.  I used a #define in advDatabase.h to define those values.

#define ADB_WATCH_ALL -1
#define ADB_WATCH_CLEAR -2

The function is then pretty simple

  1. If it is watch all… then iterate through the database and turn them on
  2. If it is watch clear … then iterate through the database and turn them off
  3. Otherwise make sure that it is a legal number and toggle it.
static void adb_db_watch(int entry)
{
    if(entry == ADB_WATCH_ALL)
    {
        for(int i=0;i<adb_db_count;i++)
        {
            adb_database[i].record = true;
        }
        return;
    }

    if(entry == ADB_WATCH_CLEAR)
    {
        for(int i=0;i<adb_db_count;i++)
        {
            adb_database[i].record = false;
        }
        return;
    }

    if(entry > adb_db_count-1 || entry < ADB_WATCH_CLEAR)
    {
        printf("Record doesnt exist: %d\n",entry);
        return;      
    }
    adb_database[entry].record = !adb_database[entry].record; 

}

Once I have the infrastructure in place, I then add the watch command to usrcmd.c

static int usrcmd_watch(int argc, char **argv)
{

    if(argc == 2 && !strcmp(argv[1],"all"))
    {
        adb_watch(ADB_WATCH_ALL); // all
        return 0;
    }


    if(argc == 2 && !strcmp(argv[1],"clear"))
    {
        adb_watch(ADB_WATCH_CLEAR);
        return 0;
    }

    if(argc == 2)
    {
        int i;
        sscanf(argv[1],"%d",&i);
        adb_watch(i);
        return 0;
    }

    return 0;
}

Add a Record Command

The record command simply turns on the global bool to either true or false and prints out the number of spaces free in the record “buffer”

                case ADB_RECORD:
                    adb_recording = !adb_recording;
                    printf("Record %s Buffer Entries Free=%d\n",adb_recording?"ON":"OFF",
                        ADB_RECORD_MAX-adb_recording_count);
                break;

And the change to usrcmd.c is also simple.

// record = toggles
static int usrcmd_record(int argc, char **argv)
{
    if(argc == 1)
    {
        adb_record(-1);
        return 0;
    }
    return 0;
}

Add an Erase Command

The erase function is like “watch”, as I overload the “entry” to have an ALL which is setup in advDatabase.h

#define ADB_ERASE_ALL -1

The erase is a bit more complicated than the watch.  When you receive a erase command you will either erase them all by iterating over the whole dates, or just erase one.

                case ADB_ERASE:
                    if((int)msg.data0 == ADB_ERASE_ALL)
                    {
                        for(int i=0;i<adb_db_count;i++)
                        {
                            adb_eraseEntry(i);
                        }
                    }
                    else
                        adb_eraseEntry((int)msg.data0);

                    printf("Record Buffer Free %d\n",ADB_RECORD_MAX-adb_recording_count);
                break;

The individual eraseEntry function checks to make sure that you have a legal “entry”.  Then it follows the linked list “freeing” the data structures.

static void adb_eraseEntry(int entry)
{
    if(entry > adb_db_count-1 || entry<0)
    {
        printf("Erase Entry Not Found %d\n",entry);
        return;
    }

    adb_adv_data_t *ptr;
    ptr = (adb_adv_data_t *)adb_database[entry].list->next;
    adb_database[entry].list->next = 0;
    while(ptr)
    {
        adb_adv_data_t *next;
        next = (adb_adv_data_t *)ptr->next;
        free(ptr->data);
        free(ptr);
        adb_database[entry].listCount -= 1;
        adb_recording_count -= 1;
        ptr = next;
    }
}

And, of course, you need to add the command to usrcmd.c

// erase
// erase #
static int usrcmd_erase(int argc, char **argv)
{
    if(argc > 2)
    {
        return 0;
    }

    if(argc == 1)
    {
        adb_erase(ADB_ERASE_ALL);
        return 0;
    }

    int i;
    sscanf(argv[1],"%d",&i);
    adb_erase(i);
    return 0;    

}

Now when you build and program the kit you can turn on/off recording and erase and….

In the next post I will add

  1. Smarter printing
  2. A “filter” to eliminate duplicate advertising packets

AnyCloud Bluetooth Advertising Scanner (Part 6)

Summary

In part 6 of this series I will update the AnyCloud BLE Advertising Scanner to decode advertising packets into a more human readable textual output

Story

We are now 6 (or maybe 7 depending on how you count) articles into this series and we are still looking at raw bytes.  I have gotten to where I am pretty good at understanding those bytes, but that is now way to roll.  You might remember from the article on the IoT Expert Bluetooth Utility library that there were a some interesting functions defined in the header.  Here it is:

wiced_bool_t btutil_isEddystone(uint8_t *data);
wiced_bool_t btutil_is_iBeacon(uint8_t *data);
wiced_bool_t btutil_isCypress(uint8_t *data);

int btutil_adv_len(uint8_t *packet);
void btutil_adv_printPacketDecode(uint8_t *packet);
void btutil_adv_printPacketBytes(uint8_t *packet);

Lets transform our  project from part 6 to use these functions.  In this article I will

  • Redo the print bytes (to be smarter) functionality and to use the built in function
  • Rework the logic for the “print” command implementation
  • Add a new command “decode” which will run the decode function

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Replace two code blocks with function calls

Do you remember this block of code?  It print’s out the 6-bytes of the Bluetooth Address.

    for(int i=0;i<BD_ADDR_LEN;i++)
    {
        printf("%02X:",adb_database[entry].result->remote_bd_addr[i]);
    }

You might have noticed that this function already exists in the BT Utility Library.  Use it.

    btutil_printBDaddress(adb_database[entry].result->remote_bd_addr);

Then you remember this block of code which iterates through and advertising packet and prints out the raw bytes?

// Print the RAW Data of the ADV Packet
    printf(" Data: ");
    int i=0;
    while(adb_database[entry].data[i])
    {
        for(int j=0;j<adb_database[entry].data[i];j++)
        {
            printf("%02X ",adb_database[entry].data[i+1+j]);
        }
        i = i + adb_database[entry].data[i]+1;
    }

Well, it exists in the library as well.  Use it.

btutil_adv_printPacketBytes(adb_database[entry].data);

Fix the “print” Command

In the previous implementation I had two functions for “print”.  The first one printed one entry and the second one printed the whole table.  I decided that I didnt really like this logic, so I compressed those two functions into one function.  Specifically, it take a number “entry”.  If that number is -1 then it will print the whole table.

static void adb_db_printRawPacket(int entry)
{
    int start,end;
 
    if(entry <= -1)
    {
        start = 0;
        end = adb_db_count;
    }
    else
    {
        start = entry;
        end = entry;
    }

    if(end>adb_db_count)
        end = adb_db_count; 

    for(int i=start;i<=end;i++)
    {
    
        printf("%02d MAC: ",i);
        btutil_printBDaddress(adb_database[i].result->remote_bd_addr);
        printf(" Data: ");
        btutil_adv_printPacketBytes(adb_database[i].data);

        printf("\n");
    }
}

Add a new “decode” Command

The next thing to do is to add a function to print out decoded packets (or the whole table).  So I wrote this:

static void adb_printDecodePacket(int entry)
{
    int start,end;
 
    if(entry == -1)
    {
        start = 0;
        end = adb_db_count;
    }
    else
    {
        start = entry;
        end = entry;
    }

    if(end>adb_db_count)
        end = adb_db_count; 

    for(int i=start;i<=end;i++)
    {

        printf("%02d MAC: ",i);
        btutil_printBDaddress(adb_database[i].result->remote_bd_addr);
        printf("\n");
        btutil_adv_printPacketDecode(adb_database[i].data);
        printf("\n");
    }
}

After finishing that block of code, I realized I had implemented almost exactly the same functionality which two different functions.  So, I decided to redo this by doing this.  Notice that it take in a adb_print_method, in other words raw bytes or decoded packet.

typedef enum {
    ADB_PRINT_METHOD_BYTES,
    ADB_PRINT_METHOD_DECODE,
} adb_print_method_t;

static void adb_db_print(adb_print_method_t method,int entry)
{
    int start,end;
 
    if(entry < 0)
    {
        start = 0;
        end = adb_db_count;
    }
    else
    {
        start = entry;
        end = entry;
    }

    if(end>adb_db_count)
        end = adb_db_count; 

    for(int i=start;i<=end;i++)
    {
    
        printf("%02d MAC: ",i);
        btutil_printBDaddress(adb_database[i].result->remote_bd_addr);
        switch(method)
        {
        
        case ADB_PRINT_METHOD_BYTES:
            printf(" Data: ");
            btutil_adv_printPacketBytes(adb_database[i].data);
        break;

        case ADB_PRINT_METHOD_DECODE:
            printf("\n");
            btutil_adv_printPacketDecode(adb_database[i].data);
        break;
        } 
        printf("\n");
    }
}

I don’t show it here, but after changing this I had to fix up the function calls in several places in the advDatabase.

Add a new command to print decode packets

Now that I have a new method to print packets, I add a command to the database to allow the user to call it:

typedef enum {
    ADB_ADD,
    ADB_PRINT_RAW,
    ADB_PRINT_DECODE,
} adb_cmd_t;

Then in the queue loop:

switch(msg.cmd)
            {
                case ADB_ADD:
                    scan_result = (wiced_bt_ble_scan_results_t *)msg.data0;
                    data = (uint8_t *)msg.data1;
                    adb_db_add(scan_result,data);
                break;
                case ADB_PRINT_RAW:
                    adb_db_print(ADB_PRINT_METHOD_BYTES,(int)msg.data0);
                break;
                case ADB_PRINT_DECODE:
                    adb_db_print(ADB_PRINT_METHOD_DECODE,(int)msg.data0);
                break;
            }

Then the actual function

void adb_printDecode(int entry)
{
    adb_cmdMsg_t msg;
    msg.cmd = ADB_PRINT_DECODE;
    msg.data0 = (void *)entry;
    xQueueSend(adb_cmdQueue,&msg,0); // If the queue is full... oh well
}

Then add it to advDatabase.h

void adb_printDecode(int entry);

Finally to the usercmd.c

static int usrcmd_printDecode(int argc, char **argv)
{
    if(argc == 1)
    {
        adb_printDecode(-1);
    }

    if(argc == 2)
    {
        int val;
        sscanf(argv[1],"%d",&val);
        adb_printDecode(val);
    }
    return 0;
}

Program and Test

When I actually program the scanner you can see that I can print out 1 item.  OR I can decode one item.  Notice that one contains 3 fields

  • flags
  • Tx Power Level
  • Manufacturers data.  Apparently an Apple something or the other

And I can print the whole table

Or decode the whole table.

 

AnyCloud Bluetooth Advertising Scanner (Part 5)

Summary

In this article I will add a new task to the AnyCloud BLE Advertising Scanning application which will save the advertising data into a database.

Story

There is still a boatload of mostly unintelligible advertising data coming ripping onto our screen.  It is FINALLY time to start fixing that.  In this article I will create a new task called the advertising database task which will hold the history of advertising packets that I have seen.  I will update the Bluetooth Manager task to submit the advertising packets to a queue running in the advertising database task.

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Create an Advertising Data Database Task

We need to create the file advertisingDatabase.h which will hold the task prototype (so that main can get going).

#pragma once

void adb_task(void *arg);

Then create the advertisingDatabase.c to hold the actual database code.  It will start with the definition of messages which can be sent to the task.  For now just “ADB_ADD”.  To make things a little bit simpler these command can have two data elements (which I call data0 and data1).  Then the main part of the task just

  1. Creates the queue to manage the messages
  2. Process the message until the end of time
#include "FreeRTOS.h"
#include "queue.h"

static QueueHandle_t adb_cmdQueue;
typedef enum {
    ADB_ADD,
} adb_cmd_t;

typedef struct
{
    adb_cmd_t cmd;
    void *data0;
    void *data1;
} adb_cmdMsg_t;

void adb_task(void *arg)
{
    // setup the queue
    adb_cmdMsg_t msg;

    adb_cmdQueue = xQueueCreate(10,sizeof(adb_cmdMsg_t));
    
    while(1)
    {
        BaseType_t status = xQueueReceive(adb_cmdQueue,&msg,portMAX_DELAY);
        if(status == pdTRUE) 
        {
            switch(msg.cmd)
            {
                case ADB_ADD:
                break;
            }

        }
    }
}

To start the task, you need to add it to main.c.

    xTaskCreate(adb_task,"adv database",configMINIMAL_STACK_SIZE*4,0,1,0);

When I build and program this, you can now see the new task.  Good that working.

AnyCloud> Unhandled Bluetooth Management Event: BTM_LOCAL_IDENTITY_KEYS_REQUEST_EVT
Started BT Stack Succesfully

AnyCloud> tasks
Name          State Priority   Stack  Num
------------------------------------------
usrcmd_ta       X       0       228     5
IDLE            R       0       115     7
Tmr Svc         B       0       223     8
CYBT_HCI_       B       5       950     3
sleep_tas       B       6       221     1
CYBT_BT_T       B       4       1371    2
blinkTask       B       0       98      4
adv datab       B       1       479     6
‘B’ – Blocked
‘R’ – Ready
‘D’ – Deleted (waiting clean up)
‘S’ – Suspended, or Blocked without a timeout
Stack = bytes free at highwater
AnyCloud>

Update the Advertising Database to Accept Submitted ADV Packets

If you recall our original setup was to take advertising packets in the Bluetooth Manager thread and print out the data.  The first thing that we want to fix up is the ability of the advertising database task to accept advertising packets which are pushed to its command queue.   To prepare for this I create two local variables to hold the data.

void adb_task(void *arg)
{
    // setup the queue
    adb_cmdMsg_t msg;
    wiced_bt_ble_scan_results_t *scan_result;
    uint8_t *data;

Then I update the ADB_ADD command.  My first, and really simple fix, is to grab the printing code from the Bluetooth Manager task.  Obviously this won’t be an improvement from the original program as far as the users goes, but it will verify that the tasks are working properly together.

                case ADB_ADD:
                    // Print the MAC Address
                    scan_result = (wiced_bt_ble_scan_results_t *)msg.data0;
                    data = (uint8_t *)msg.data1;

                    printf("MAC: ");
                    for(int i=0;i<BD_ADDR_LEN;i++)
                    {
                        printf("%02X:",scan_result->remote_bd_addr[i]);
                    }
                    // Print the RAW Data of the ADV Packet
                    printf(" Data: ");
                    int i=0;
                    while(data[i])
                    {
                        for(int j=0;j<data[i];j++)
                        {
                            printf("%02X ",data[i+1+j]);
                        }
                        i = i + data[i]+1;
                    }
                    printf("\n");
    
                    free(msg.data0);
                    free(msg.data1);
                break;

Then I add a command to the advertisingDatabase.h which the Bluetooth Manager task can call to submit advertising packets

void adb_addAdv(wiced_bt_ble_scan_results_t *scan_result,void *data);

The actual command in advertisingDatabase.c just takes the advertising information, puts it in a command message, then submits it to the command queue.

void adb_addAdv(wiced_bt_ble_scan_results_t *scan_result,void *data)
{
    adb_cmdMsg_t msg;
    msg.cmd = ADB_ADD;
    msg.data0 = (void *)scan_result;
    msg.data1 = (void *)data;
    xQueueSend(adb_cmdQueue,&msg,0); // If you loose an adv packet it is OK...
}

Update the Bluetooth Manager to Submit Adv Packets

Now I go and edit the bluetoothManager. c to submit packets rather than print them.  To do this I greatly simplify the callback.  There is one VERY important issue to deal with, which is one of those potential religious war issues.  Memory.

When you get the callback from the stack, it gives you POINTERS to data for the advertising packet that reside inside of buffers inside of the stack.  As soon as this callback returns this memory is purged.  To prevent this data from getting cleaned up by the stack I

  1. Malloc some memory for the wiced_bt_ble_scan_results
  2. Malloc some memory for the advertising data
  3. Make a copy of the data
  4. Submit it to the Advertising Database

I KNOW from the spec that the largest data packet is 31-bytes (actually it is 31-bytes + one more field with length 0).  So I know the maximum length is 32-bytes  This means that in many situations I will be copying GARBAGE into my buffer if the packet is less than 32 bytes long.  I think that this is simpler than calculating the length and then only copying that much data.

void btm_advCallback(wiced_bt_ble_scan_results_t *p_scan_result, uint8_t *p_adv_data)
{
    wiced_bt_ble_scan_results_t *scan_result = malloc(sizeof(wiced_bt_ble_scan_results_t));
    uint8_t *data = malloc(32);
   
    memcpy(data,p_adv_data,32);
    memcpy(scan_result,p_scan_result->remote_bd_addr,BD_ADDR_LEN);
    adb_addAdv(scan_result,data);
}

When I run this updated program I should get the same stream of data coming out on the serial port.  Sure enough the new thread is working.

Create an Advertising Data Database

Now, lets create an actual database.  To simplify things my database is just an array of structures.  One structure per bluetooth device.  The structure will contain a pointer to the information about the device it just saw and the actual raw data.

typedef struct {
    wiced_bt_ble_scan_results_t *result;
    uint8_t *data;
} adb_adv_t ;

#define ADB_MAX_SIZE (40)
adb_adv_t adb_database[ADB_MAX_SIZE];
int adb_db_count=0;

Then I will create several helper functions to work with the database

  1. Find devices in the database given a mac address
  2. Print an entry in the database
  3. Add entries to the database

First, find an entry in the database.  This function will search through the database and compare the mac address against the mac address in the database.  When the memcmp ==0 meaning it found a match, it will return that entry.

static int adb_db_find(wiced_bt_device_address_t *add)
{
    int rval=-1;
    for(int i=0;i<adb_db_count;i++)
    {
        if(memcmp(add,&adb_database[i].result->remote_bd_addr,BD_ADDR_LEN)==0)
        {
            rval = i;
            break;
        }
    }
    return rval;
}

The print function will make sure that you asked for a legal entry (much must be greater than 0… and less than the max).  Then it will print out the mac address and the raw data.  In a future post I will add a smarter print out.

static void adb_db_printEntry(int entry)
{
    if(!(entry>= 0 && entry <= adb_db_count))
    {
        printf("Illegal entry\n");
        return;
    }
    printf("%02d MAC: ",entry);

    for(int i=0;i<BD_ADDR_LEN;i++)
    {
        printf("%02X:",adb_database[entry].result->remote_bd_addr[i]);
    }

    // Print the RAW Data of the ADV Packet
    printf(" Data: ");
    int i=0;
    while(adb_database[entry].data[i])
    {
        for(int j=0;j<adb_database[entry].data[i];j++)
        {
            printf("%02X ",adb_database[entry].data[i+1+j]);
        }
        i = i + adb_database[entry].data[i]+1;
    }
    printf("\n");
}

To add an entry to the database, first make sure that it isn’t already in the database.  Then when you are sure that it isn’t the database, you just add the pointers to your table.  You need to make sure and not go beyond the end of the table, and if you did, you will have effectively blown away the last entry in the table.  Oh well.

static void adb_db_add(wiced_bt_ble_scan_results_t *scan_result,uint8_t *data)
{
 
    int entry = adb_db_find(&scan_result->remote_bd_addr);
    if(entry == -1)
    {
        
        adb_database[adb_db_count].result = scan_result;
        adb_database[adb_db_count].data = data;
        adb_db_printEntry(adb_db_count);
        adb_db_count = adb_db_count + 1;
        if(adb_db_count == ADB_MAX_SIZE)
        {
            printf("ADV Table Max Size\n");
            adb_db_count = adb_db_count - 1;
        }
    }
    else
    {
        free(scan_result);
        free(data);
    }
}

Add a Command to Print the Database

Now we want to add the ability to print from the command line.  So add a new command message to the list of legal commands.

typedef enum {
    ADB_ADD,
    ADB_PRINT,
} adb_cmd_t;

Then create a new function to print.  If you send in a “-1” it will print the whole table.  Otherwise just print the individual entry.

static void adb_printTable(int entry)
{
    if(entry == -1)
    {
        for(int i=0;i<adb_db_count;i++)
        {
            adb_db_printEntry(i);
        }

    }
    else
    {
        adb_db_printEntry(entry);
    }
    

}

Now edit usercmd.c to have the new command line.  Notice that I use “sscanf” which obviously has some issues.  Too bad.

static int usrcmd_print(int argc, char **argv)
{

    if(argc == 1)
    {
        adb_print(-1); // Print whole table
    }

    if(argc == 2)
    {
        int val;
        sscanf(argv[1],"%d",&val);
        adb_print(val);
    }

    return 0;
}

When I program the project it immediately prints out a bunch of devices that are at my house.  Then you can see I run the “print” command which prints the table.  Finally P do a print 0 to just print the first entry.

In the next article I will add smarter diagnostics to the advertising packets.

AnyCloud Bluetooth Advertising Scanner (Part 4)

Summary

In this article I update the AnyCloud BLE advertising scanner to use the btutil library that was created in the previous post.  In addition, I add a command queue to the bluetoothManger and enable a new command to turn on and off scanning.

Story

If you have been following along until now, which I imagine that you have if you are reading this,  you will have gotten a vomit of device data blasting out onto your serial console.  This isn’t very helpful.  So now what?  I am going to divide this problem into two parts

  1. Creating a new user command to turn on and off scanning (this article)
  2. Creating a database to manage the data + a set of commands to dump it (next article)

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Add the IoT Expert “btutil” Library

Before we actually start all of the command queue stuff, lets move to the btutil library that I talked about in the previous post.  To do this, add the library using the library manager.

Then delete bt_platform_cfg_settings.h and bt_platform_cfg_settings.c from your project.  Finally Rebuild and make sure that everything still works.  That is it.

Multithreading

Id like to explain that there is now some danger.  That danger comes from the fact that we have multiple tasks which are all accessing data plus functions that are talking to each other ASYNCHRONOUSLY.  Specifically we have:

  1. The Bluetooth Stack task – running the Bluetooth stack and management callback
  2. The Bluetooth Stack APIs – e.g. wiced_bt_ble_observe
  3. The usrcmd task – which is interacting with the user on the serial port and talking to the other tasks
  4. A timer_svc task – which runs software timers
  5. The advertising data (which I will start saving in the next article)

When faced with this situation what I typically like to do is provide thread safe public functions for each of the tasks.  Then any other task can call these functions and know that things are not going to get corrupted by a race condition.

To make the design thread safe, I typically like to put an RTOS Queue between the tasks.  These queues are a safe place to send and receive data in a “thread safe” way.  There are two basic design patterns that can be used

  1. Define a message structure (that gets pushed into the queue) and make it global (via a dot-h).  Define a queue handle and make it global (via a dot-h).  Then let any task build messages and push them into the queue to be received in the task that owns the queue.
  2. Define the message structure and queue.  Then define functions which are global (via a dot-h) which know how to interact with the queue.

I typically think that the 2nd method is better, so that is what I am going to do here.

  1. In BluetoothManager.h I will provide a function called “btm_cmdScan”
  2. The usrcmd task will call the btm_cmdScan function which will
  3. Create a btm_cmdMsg_t with the “scan” command and data of true/false
  4. Then push it into the Bluetooth Manager Command Queue
  5. Where a timer callback in the Bluetooth Manager Task will take it out of the queue
  6. Figure out that it is a “scan” command
  7. Then will either turn on or off scanning

Add a Queue to the Bluetooth Manager Thread

So we need two things a message to push into a queue (just a structure) and we need a queue to push it into.  First the message which is just a structure with two elements.  The first element is a command and the second element is some data of type void.  The meaning of the void *data will be different based on the command.

typedef struct {
	btm_cmd_t cmd;
	void *data;
} btm_cmdMsg_t;

But how about the command?  The command is just an enumerate list of commands which will now start with just one command.

typedef enum {
	BTM_SCAN,
} btm_cmd_t;

And know we need to define the queue.

#include "queue.h"
static QueueHandle_t btm_cmdQueue;

Before you can use the queue you need to initialize it.  The best place to initialize this queue is in the management callback right after the stack gets going.  You can see that I tell FreeRTOS that there is a queue which can hold up to 10 commands.  I also tell it that each command is the sizeof the command message.

    switch (event)
    {
        case BTM_ENABLED_EVT:
            printf("Started BT Stack Succesfully\n");
            btm_cmdQueue = xQueueCreate(10,sizeof(btm_cmdMsg_t));

Now we need to create a way for other tasks to create these command messages.  They will do this by calling a function which we will define in the bluetoothManager.h

void btm_cmdScan(bool enable);

This function will live in bluetoothManager.c and it simply

  1. Creates a command
  2. Set the actual command to scan
  3. Sets the void* data to be enable … in other words start or stop scanning.  Remember that a void * can be anything.  See I cast a bool to a void *
  4. Finally push the data into the command queue
void btm_cmdScan(bool enable)
{
    btm_cmdMsg_t msg;
    msg.cmd = BTM_SCAN;
    msg.data = (void *)enable;
  	xQueueSend(btm_cmdQueue, &msg,0);
}

Add a Timer to Process the Queue

So now we have a method to push items into the queue.  How do we get them out of the queue?  To do that I will use a Bluetooth Stack timer that will run every 50ms.

First, define the timer in bluetoothManager.c

#include "wiced_timer.h"
static wiced_timer_ext_t btm_mgmtQueueTimer;

Then define a function which the timer will call.  This function will

  1. Try to get a message out of the queue
  2. IF there is a message it will use a big switch to look at the possible messages
  3. If the message is a scan
  4. Then call the wiced function to either start “observing” or stop “observing”
static void btm_processBluetoothAppQueue()
{
	btm_cmdMsg_t msg;

	 BaseType_t rval;

	 rval = xQueueReceive( btm_cmdQueue,&msg,0);
	 if(rval == pdTRUE)
	 {
		 switch(msg.cmd)
		 {
		 case BTM_SCAN:
            wiced_bt_ble_observe((wiced_bool_t)msg.data,0,btm_advCallback);
			 break;
		 }
	 }
}

The last thing you need to do is start the timer.  The best place to start the timer is in the management callback where you need to

  1. Create the timer
  2. Tell it to start and run every 50ms
    switch (event)
    {
        case BTM_ENABLED_EVT:
            printf("Started BT Stack Succesfully\n");
            btm_cmdQueue = xQueueCreate(10,sizeof(btm_cmdMsg_t));
            wiced_init_timer_ext (&btm_mgmtQueueTimer, btm_processBluetoothAppQueue,0, WICED_TRUE);
            wiced_start_timer_ext (&btm_mgmtQueueTimer, 50);
        break;

A Potential Threading Bug

When I did the implementation originally I created what I thought was a threading bug.  Specifically I used the FreeRTOS timer to process the queue.  In other words instead of using a wiced_timer_ext_t I used a TimerHandle_t.  So what?

The wiced_timer_ext_t is run INSIDE of the BluetoothStack task where the TimerHandle_t is run inside of the Timer_SVC task.

So what?  I was afraid that the call to wiced_bt_ble_obsere was NOT thread safe and needed to be called inside of the same task as the stack.

After some digging I found out that the Bluetooth Stack is threadsafe, so I worried for no reason.  Well, actually, you can never worry enough about making these kinds of threading bugs because they are viscously difficult to debug.

Add a Scan Off & On Command

The last thing that you need to do is add an actual command to the usercmd task to call the bluetooth manager function to turn on and off scanning.

First, add a new prototype for your new command in usercmd.c.  Then add it to the list of legal commands.

static int usrcmd_scan(int argc, char **argv);


static const cmd_table_t cmdlist[] = {

.... deleted stuff

    { "scan","scan [on|off]", usrcmd_scan},

};

Then create the function to process the command line input and call the btm_scan function.

static int usrcmd_scan(int argc, char **argv)
{

    if(argc != 2)
        return 0;

    if(strcmp(argv[1],"on") == 0)
    {
        btm_cmdScan(true);
    }
    else if(strcmp(argv[1],"off") == 0)
    {
        btm_cmdScan(false);

    }
    return 0;

}

Now build it and run it.  You should still get adv packets barfing all over your screen.  But now you can turn on and off the scanning with “scan on” and “scan off”.  In the next article we will create a database to hold the scan packets.

AnyCloud Bluetooth Utilities Library

Summary

This article is a discussion of a library of utilities functions that support AnyCloud Bluetooth development.  It includes settings to configure the hardware, functions to decode stack events, functions to decode advertising packets etc.

Story

The Cypress, now Infineon, Modus Toolbox team has been working to bring the WICED Bluetooth devices to be closer and more compatible with the PSoC 6 tools.  One of the important enablement things we have done is turn on the WICED Bluetooth Host stack on the PSoC 6 so that it can effectively talk to the CYW43XXX Bluetooth / WiFi combo chips.

As I have been using all of the new stuff I found myself adding my own custom functionality…. and after a while I realized (finally) that I should put all of those little helper things into a library, specifically an IoT Expert library.  For now this library contains:

  • The Bluetooth Platform Configuration Settings
  • Functions to decode stack events
  • Functions to decode advertising packets

but imagine with time I will add more functions and templates.

bt_platform_cfg_settings

As I discussed in the article AnyCloud Bluetooth Advertising Scanner (Part 1), every single AnyCloud BLE Stack project will require a structure of type wiced_bt_cfg_settings_t.  This structure is used to setup the hardware before getting the stack going.  Remember you need to make a call like this to initialize the Bluetooth hardware.

    cybt_platform_config_init(&bt_platform_cfg_settings);

At some point this file will be included automatically for you by the Modus Toolbox team, but for now I have added this to my library.  This file look like this.  Basically a bunch of pin definitions which are set for you automatically by the BSP.  Obviously you can make your own, but this should work on all of the Cypress development kits (I think)

#include "cybt_platform_config.h"
#include "cybsp.h"
#include "wiced_bt_stack.h"

const cybt_platform_config_t bt_platform_cfg_settings =
{
    .hci_config =
    {
        .hci_transport = CYBT_HCI_UART,

        .hci =
        {
            .hci_uart =
            {
                .uart_tx_pin = CYBSP_BT_UART_TX,
                .uart_rx_pin = CYBSP_BT_UART_RX,
                .uart_rts_pin = CYBSP_BT_UART_RTS,
                .uart_cts_pin = CYBSP_BT_UART_CTS,

                .baud_rate_for_fw_download = 115200,
                .baud_rate_for_feature     = 115200,

                .data_bits = 8,
                .stop_bits = 1,
                .parity = CYHAL_UART_PARITY_NONE,
                .flow_control = WICED_TRUE
            }
        }
    },

    .controller_config =
    {
        .bt_power_pin      = CYBSP_BT_POWER,
        .sleep_mode =
        {
            #if (bt_0_power_0_ENABLED == 1) /* BT Power control is enabled in the LPA */
            #if (CYCFG_BT_LP_ENABLED == 1) /* Low power is enabled in the LPA, use the LPA configuration */
            .sleep_mode_enabled = true,
            .device_wakeup_pin = CYCFG_BT_DEV_WAKE_GPIO,
            .host_wakeup_pin = CYCFG_BT_HOST_WAKE_GPIO,
            .device_wake_polarity = CYCFG_BT_DEV_WAKE_POLARITY,
            .host_wake_polarity = CYCFG_BT_HOST_WAKE_IRQ_EVENT
            #else /* Low power is disabled in the LPA, disable low power */
            .sleep_mode_enabled = false
            #endif
            #else /* BT Power control is disabled in the LPA – default to BSP low power configuration */
            .sleep_mode_enabled = true,
            .device_wakeup_pin = CYBSP_BT_DEVICE_WAKE,
            .host_wakeup_pin = CYBSP_BT_HOST_WAKE,
            .device_wake_polarity = CYBT_WAKE_ACTIVE_LOW,
            .host_wake_polarity = CYBT_WAKE_ACTIVE_LOW
            #endif
        }
    },

    .task_mem_pool_size    = 2048
};

btutil_stack

When you startup the Bluetooth Host stack you are responsible for providing a “management callback” which has the function prototype

typedef wiced_result_t (wiced_bt_management_cback_t) (wiced_bt_management_evt_t event, wiced_bt_management_evt_data_t *p_event_data);

The first parameter is an “event” which is  just an enumerated list of possible events by the Bluetooth Host Stack.  Here is the actual list.

enum wiced_bt_management_evt_e {
    /* Bluetooth status events */
    BTM_ENABLED_EVT,                                /**< Bluetooth controller and host stack enabled. Event data: wiced_bt_dev_enabled_t */
    BTM_DISABLED_EVT,                               /**< Bluetooth controller and host stack disabled. Event data: NULL */
    BTM_POWER_MANAGEMENT_STATUS_EVT,                /**< Power management status change. Event data: wiced_bt_power_mgmt_notification_t */
    BTM_RE_START_EVT,                               /**< Bluetooth controller and host stack re-enabled. Event data: tBTM_ENABLED_EVT */
    /* Security events */
    BTM_PIN_REQUEST_EVT,                            /**< PIN request (used only with legacy devices). Event data: #wiced_bt_dev_name_and_class_t */
    BTM_USER_CONFIRMATION_REQUEST_EVT,              /**< received USER_CONFIRMATION_REQUEST event (respond using #wiced_bt_dev_confirm_req_reply). Event data: #wiced_bt_dev_user_cfm_req_t */
    BTM_PASSKEY_NOTIFICATION_EVT,                   /**< received USER_PASSKEY_NOTIFY event. Event data: #wiced_bt_dev_user_key_notif_t */
    BTM_PASSKEY_REQUEST_EVT,                        /**< received USER_PASSKEY_REQUEST event @cond DUAL_MODE (respond using #wiced_bt_dev_pass_key_req_reply). Event data: #wiced_bt_dev_user_key_req_t @endcond
                                                     @note  BR/EDR Only */
    BTM_KEYPRESS_NOTIFICATION_EVT,                  /**< received KEYPRESS_NOTIFY event. Event data: #wiced_bt_dev_user_keypress_t */
    BTM_PAIRING_IO_CAPABILITIES_BR_EDR_REQUEST_EVT, /**< Requesting IO capabilities for BR/EDR pairing. Event data: #wiced_bt_dev_bredr_io_caps_req_t 
                                                        @note  BR/EDR Only */
    BTM_PAIRING_IO_CAPABILITIES_BR_EDR_RESPONSE_EVT,/**< Received IO capabilities response for BR/EDR pairing. Event data: @cond DUAL_MODE #wiced_bt_dev_bredr_io_caps_rsp_t @endcond
                                                        @note  BR/EDR Only*/
    BTM_PAIRING_IO_CAPABILITIES_BLE_REQUEST_EVT,    /**< Requesting IO capabilities for BLE pairing. Slave can check peer io capabilities in event data before updating with local io capabilities. Event data: #wiced_bt_dev_ble_io_caps_req_t */
    BTM_PAIRING_COMPLETE_EVT,                       /**< received SIMPLE_PAIRING_COMPLETE event. Event data: #wiced_bt_dev_pairing_cplt_t */
    BTM_ENCRYPTION_STATUS_EVT,                      /**< Encryption status change. Event data: #wiced_bt_dev_encryption_status_t */
    BTM_SECURITY_REQUEST_EVT,                       /**< Security request (respond using #wiced_bt_ble_security_grant). Event data: #wiced_bt_dev_security_request_t */
    BTM_SECURITY_FAILED_EVT,                        /**< Security procedure/authentication failed. Event data: #wiced_bt_dev_security_failed_t */
    BTM_SECURITY_ABORTED_EVT,                       /**< Security procedure aborted locally, or unexpected link drop. Event data: #wiced_bt_dev_name_and_class_t */

    BTM_READ_LOCAL_OOB_DATA_COMPLETE_EVT,           /**< Result of reading local OOB data @cond DUAL_MODE (#wiced_bt_dev_read_local_oob_data). Event data: #wiced_bt_dev_local_oob_t @endcond 
                                                        @note  BR/EDR Only */

    BTM_REMOTE_OOB_DATA_REQUEST_EVT,                /**< OOB data from remote device @cond DUAL_MODE (respond using #wiced_bt_dev_remote_oob_data_reply). Event data: #wiced_bt_dev_remote_oob_t @endcond 
                                                        @note  BR/EDR Only */

    BTM_PAIRED_DEVICE_LINK_KEYS_UPDATE_EVT,         /**< Updated remote device link keys (store device_link_keys to  NV memory). This is the place to
verify that the correct link key has been generated. Event data: #wiced_bt_device_link_keys_t */
    BTM_PAIRED_DEVICE_LINK_KEYS_REQUEST_EVT,        /**< Request for stored remote device link keys (restore device_link_keys from NV memory). If successful, return WICED_BT_SUCCESS. Event data: #wiced_bt_device_link_keys_t */

    BTM_LOCAL_IDENTITY_KEYS_UPDATE_EVT,             /**< Update local identity key (stored local_identity_keys NV memory). Event data: #wiced_bt_local_identity_keys_t */
    BTM_LOCAL_IDENTITY_KEYS_REQUEST_EVT,            /**< Request local identity key (get local_identity_keys from NV memory). If successful, return WICED_BT_SUCCESS. Event data: #wiced_bt_local_identity_keys_t */

    BTM_BLE_SCAN_STATE_CHANGED_EVT,                 /**< BLE scan state change. Event data: #wiced_bt_ble_scan_type_t */
    BTM_BLE_ADVERT_STATE_CHANGED_EVT,               /**< BLE advertisement state change. Event data: #wiced_bt_ble_advert_mode_t */

    /* BLE Secure Connection events */
    BTM_SMP_REMOTE_OOB_DATA_REQUEST_EVT,            /**< SMP remote oob data request. Reply using wiced_bt_smp_oob_data_reply. Event data: #wiced_bt_smp_remote_oob_req_t  */
    BTM_SMP_SC_REMOTE_OOB_DATA_REQUEST_EVT,         /**< LE secure connection remote oob data request. Reply using wiced_bt_smp_sc_oob_reply. Event data: #wiced_bt_smp_sc_remote_oob_req_t 
                                                        @note  BR/EDR Only */
    BTM_SMP_SC_LOCAL_OOB_DATA_NOTIFICATION_EVT,     /**< LE secure connection local OOB data (wiced_bt_smp_create_local_sc_oob_data). Event data: #wiced_bt_smp_sc_local_oob_t */

    BTM_SCO_CONNECTED_EVT,                          /**< SCO connected event. Event data: @cond DUAL_MODE #wiced_bt_sco_connected_t @endcond
                                                        @note  BR/EDR Only */
    BTM_SCO_DISCONNECTED_EVT,                       /**< SCO disconnected event. Event data: @cond #wiced_bt_sco_disconnected_t @endcond
                                                        @note  BR/EDR Only */
    BTM_SCO_CONNECTION_REQUEST_EVT,                 /**< SCO connection request event. Event data: @cond #wiced_bt_sco_connection_request_t @endcond
                                                        @note  BR/EDR Only */
    BTM_SCO_CONNECTION_CHANGE_EVT,                  /**< SCO connection change event. Event data: @cond #wiced_bt_sco_connection_change_t @endcond
                                                        @note  BR/EDR Only */
    BTM_BLE_CONNECTION_PARAM_UPDATE,                /**< BLE connection parameter update. Event data: #wiced_bt_ble_connection_param_update_t */
    BTM_BLE_PHY_UPDATE_EVT,                         /**< BLE Physical link update. Event data: wiced_bt_ble_phy_update_t */
    BTM_LPM_STATE_LOW_POWER,                        /**< BT device wake has been deasserted. Used for Host Stack Use Case. */
    BTM_MULTI_ADVERT_RESP_EVENT,                    /**< Multi adv command status event Used for the status of the command sent */
#if SMP_CATB_CONFORMANCE_TESTER == TRUE
    BTM_SMP_SC_PEER_INFO_EVT                        /** The Secure Connections support information of the peer device */
#endif

};

While you are trying to figure out what is going on during the development, it is very useful to be able to print out the name of the events (instead of the numbers).  In other words instead of doing

printf("Event = %02X\n",event);

it is way better to do

printf("Event Name=%s\n",btutil_getBTEventName(event));

While I was looking at an example project I found this function which I thought was awesome.

const char *btutil_getBTEventName(wiced_bt_management_evt_t event)
{
    switch ( (int)event )
    {
    CASE_RETURN_STR(BTM_ENABLED_EVT)
    CASE_RETURN_STR(BTM_DISABLED_EVT)
    CASE_RETURN_STR(BTM_POWER_MANAGEMENT_STATUS_EVT)
    CASE_RETURN_STR(BTM_PIN_REQUEST_EVT)
    CASE_RETURN_STR(BTM_USER_CONFIRMATION_REQUEST_EVT)
    CASE_RETURN_STR(BTM_PASSKEY_NOTIFICATION_EVT)
    CASE_RETURN_STR(BTM_PASSKEY_REQUEST_EVT)
    CASE_RETURN_STR(BTM_KEYPRESS_NOTIFICATION_EVT)
    CASE_RETURN_STR(BTM_PAIRING_IO_CAPABILITIES_BR_EDR_REQUEST_EVT)
    CASE_RETURN_STR(BTM_PAIRING_IO_CAPABILITIES_BR_EDR_RESPONSE_EVT)
    CASE_RETURN_STR(BTM_PAIRING_IO_CAPABILITIES_BLE_REQUEST_EVT)
    CASE_RETURN_STR(BTM_PAIRING_COMPLETE_EVT)
    CASE_RETURN_STR(BTM_ENCRYPTION_STATUS_EVT)
    CASE_RETURN_STR(BTM_SECURITY_REQUEST_EVT)
    CASE_RETURN_STR(BTM_SECURITY_FAILED_EVT)
    CASE_RETURN_STR(BTM_SECURITY_ABORTED_EVT)
    CASE_RETURN_STR(BTM_READ_LOCAL_OOB_DATA_COMPLETE_EVT)
    CASE_RETURN_STR(BTM_REMOTE_OOB_DATA_REQUEST_EVT)
    CASE_RETURN_STR(BTM_PAIRED_DEVICE_LINK_KEYS_UPDATE_EVT)
    CASE_RETURN_STR(BTM_PAIRED_DEVICE_LINK_KEYS_REQUEST_EVT)
    CASE_RETURN_STR(BTM_LOCAL_IDENTITY_KEYS_UPDATE_EVT)
    CASE_RETURN_STR(BTM_LOCAL_IDENTITY_KEYS_REQUEST_EVT)
    CASE_RETURN_STR(BTM_BLE_SCAN_STATE_CHANGED_EVT)
    CASE_RETURN_STR(BTM_BLE_ADVERT_STATE_CHANGED_EVT)
    CASE_RETURN_STR(BTM_SMP_REMOTE_OOB_DATA_REQUEST_EVT)
    CASE_RETURN_STR(BTM_SMP_SC_REMOTE_OOB_DATA_REQUEST_EVT)
    CASE_RETURN_STR(BTM_SMP_SC_LOCAL_OOB_DATA_NOTIFICATION_EVT)
    CASE_RETURN_STR(BTM_SCO_CONNECTED_EVT)
    CASE_RETURN_STR(BTM_SCO_DISCONNECTED_EVT)
    CASE_RETURN_STR(BTM_SCO_CONNECTION_REQUEST_EVT)
    CASE_RETURN_STR(BTM_SCO_CONNECTION_CHANGE_EVT)
    CASE_RETURN_STR(BTM_BLE_CONNECTION_PARAM_UPDATE)
#ifdef CYW20819A1
    CASE_RETURN_STR(BTM_BLE_PHY_UPDATE_EVT)
#endif
    }

    return NULL;
}

And then I learned something new when I looked at the “function” CASE_RETURN_STR which used compiler trick to turn an enumerated value into a string.

#define CASE_RETURN_STR(enum_val)          case enum_val: return #enum_val;

This allows you to do this in your management callback (notice line 16 where the string is printed out)

wiced_result_t app_bt_management_callback(wiced_bt_management_evt_t event, wiced_bt_management_evt_data_t *p_event_data)
{
    wiced_result_t result = WICED_BT_SUCCESS;

    switch (event)
    {
        case BTM_ENABLED_EVT:
            if (WICED_BT_SUCCESS == p_event_data->enabled.status)
            {
                printf("Started BT Stack Succesfully\n");
                wiced_bt_ble_observe(WICED_TRUE,0,obv_callback);
            }
            break;

        default:
            printf("Unhandled Bluetooth Management Event: %s\n", btutil_getBTEventName(event));
            break;
    }

    return result;
}

It turns out that there are several other places where the stack gives you an event-ish thing.  So these functions were created as well

#pragma once

const char *btutil_getBTEventName(wiced_bt_management_evt_t event);
const char *btutil_getBLEAdvertModeName(wiced_bt_ble_advert_mode_t mode);
const char *btutil_getBLEGattDisconnReasonName(wiced_bt_gatt_disconn_reason_t reason);
const char *btutil_getBLEGattStatusName(wiced_bt_gatt_status_t status);

btutil_adv_decode

In AnyCloud Bluetooth Advertising Scanner (Part 3) I discussed the format of the advertising packet.  So I created functions which will decode the data in the advertising packets.  More on the future article AnyCloud Bluetooth Advertising Scanner (Part 4 or maybe 5 or maybe 6).  Here are the functions:

#pragma once

wiced_bool_t btutil_isEddystone(uint8_t *data);
wiced_bool_t btutil_is_iBeacon(uint8_t *data);
wiced_bool_t btutil_isCypress(uint8_t *data);

int btutil_adv_len(uint8_t *packet);
void btutil_adv_printPacketDecode(uint8_t *packet);
void btutil_adv_printPacketBytes(uint8_t *packet);

btutil

And because I like to have just one include to get access to all of the function I created “btutil.h” which just includes all of the headers in one place.

#pragma once

#include "btutil_adv_decode.h"
#include "btutil_stack.h"
#include "btutil_general.h"
#include "bt_platform_cfg_settings.h"

Add to the IoT Expert Manifest

In order to set it up for my library to live in the library manager I updated the IoT Expert Manifest file to have a link to the GitHub repository

<middleware>
  
    <name>Bluetooth Utilities</name>
    <id>btutil</id>
    <uri>https://github.com/iotexpert/btutil</uri>
    <desc>A library of Bluetooth Debugging Utilties for Cypress PSoC6 Anycloud</desc>
    <category>IoT Expert</category>
    <req_capabilities>psoc6</req_capabilities>
    <versions>
      <version flow_version="1.0,2.0">
        <num>master</num>
        <commit>master</commit>
        <desc>master</desc>
      </version>
    </versions>
  </middleware>

And now the library manager has the IoT Expert Bluetooth Utilities.

 

 

AnyCloud Bluetooth Advertising Scanner (Part 3)

Summary

In this article I discuss BLE “Observing” and add that functionality to my PSoC 6 – CYW43xxx AnyCloud BLE Adverting Scanner project.

Story

In part1 of this series I discussed the pieces parts required to get the AnyCloud Bluetooth Stack operating using the AnyCloud SDK running on a PSoC 6 with a CY43xxx combo.  Then in part 2 I built a project with those parts and started up the Bluetooth Host stack.  The project didn’t really do anything, actually nothing, so it wasn’t very interesting, but it was going.  In this article I will discuss BLE advertising scanning, how to configure it in the AnyCloud project and finally how to add it to the project.

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Explain BLE Advertising – Scanner/Observer

You might recall that there are four roles that a BLE device can perform

  • Peripheral – low power devices that broadcast advertisements, then accept a single connection
  • Central – devices like cell phones that connect to peripherals.  They may run multiple connections at a time.
  • Broadcaster – a nonconnectable peripheral that sends out advertisements
  • Observer – A central-like device that listens for broadcasters (or advertising peripherals)

And you might remember that advertisements are short, up to 31-byte, packets of data that give information which can be used for one or more of:

  • advertising the availability to connect
  • advertising services
  • advertising the name
  • advertising vendor specific information
  • advertising beacon data (like temperature or …)
  • advertising location

And, if you forgot, BLE operates on 40 channels.  But to save power in peripherals, all of the advertising happens on channel 37, 38 and 39.  Specifically a peripheral or broadcaster will send out the advertising data on channel 37, then 38 then 39, then wait… then do it again.  But why one channel at a time?  Because BLE radio’s can be tuned to transmit and receive on only one channel at a time (a power saving and complexity reducing feature)

Inside of the Central/Observer it will listen on channel 37 for a “window” amount of time.  Then it will do nothing for an interval-window amount of time.  Then it will do that same thing on channel 28 then 39.  But why only one channel at a time?  Same reason as above, it saves power and simplifies the design.  Why not have the window and the interval be the same?  Once again, it saves power.

Here is a picture:

But, what happens if you are not listening when the advertiser advertises?  You missed it.  Tough shit.  It turns out that setting the scan window and interval will greatly impact the probability that you hear advertisements.  And, you are more likely to hear advertisements because they are sent on three channels.  But it seems like it will never work.  Will it? … yes, of course, or they wouldn’t have done it that way 🙂

BLE Advertising – The Advertiser Peripheral or Broadcaster

So what exactly is inside of an advertising packet?  Volume 6 part B Section 2.3 of the bluetooth core spec describes the advertising protocol data unit (PDU)

But what is inside of the header?

This leaves us with what is inside of the “payload”.  The answer is that the ADV_IND Payload Data Unit (PDU) contains an address of 6-bytes plus up to 31 bytes of data.

The AdvA field shall contain the advertiser’s public or random device address as indicated by TxAdd.

The actual AdvData field is further broken up into “AD Structures” like this:

And what is the “AD Type”, well it is a one byte of one of the following:

And then where do you find the assigned numbers for the field types?  In the “Assigned Numbers and GAP“.  Here is a clip from the spec.

And conveniently enough we enumerated them for you inside of the SDK header file wiced_bt_ble.h

/** Advertisement data types */
enum wiced_bt_ble_advert_type_e {
    BTM_BLE_ADVERT_TYPE_FLAG                        = 0x01,                 /**< Advertisement flags */
    BTM_BLE_ADVERT_TYPE_16SRV_PARTIAL               = 0x02,                 /**< List of supported services - 16 bit UUIDs (partial) */
    BTM_BLE_ADVERT_TYPE_16SRV_COMPLETE              = 0x03,                 /**< List of supported services - 16 bit UUIDs (complete) */
    BTM_BLE_ADVERT_TYPE_32SRV_PARTIAL               = 0x04,                 /**< List of supported services - 32 bit UUIDs (partial) */
    BTM_BLE_ADVERT_TYPE_32SRV_COMPLETE              = 0x05,                 /**< List of supported services - 32 bit UUIDs (complete) */
    BTM_BLE_ADVERT_TYPE_128SRV_PARTIAL              = 0x06,                 /**< List of supported services - 128 bit UUIDs (partial) */
    BTM_BLE_ADVERT_TYPE_128SRV_COMPLETE             = 0x07,                 /**< List of supported services - 128 bit UUIDs (complete) */
    BTM_BLE_ADVERT_TYPE_NAME_SHORT                  = 0x08,                 /**< Short name */
    BTM_BLE_ADVERT_TYPE_NAME_COMPLETE               = 0x09,                 /**< Complete name */
    BTM_BLE_ADVERT_TYPE_TX_POWER                    = 0x0A,                 /**< TX Power level  */
    BTM_BLE_ADVERT_TYPE_DEV_CLASS                   = 0x0D,                 /**< Device Class */
    BTM_BLE_ADVERT_TYPE_SIMPLE_PAIRING_HASH_C       = 0x0E,                 /**< Simple Pairing Hash C */
    BTM_BLE_ADVERT_TYPE_SIMPLE_PAIRING_RAND_C       = 0x0F,                 /**< Simple Pairing Randomizer R */
    BTM_BLE_ADVERT_TYPE_SM_TK                       = 0x10,                 /**< Security manager TK value */
    BTM_BLE_ADVERT_TYPE_SM_OOB_FLAG                 = 0x11,                 /**< Security manager Out-of-Band data */
    BTM_BLE_ADVERT_TYPE_INTERVAL_RANGE              = 0x12,                 /**< Slave connection interval range */
    BTM_BLE_ADVERT_TYPE_SOLICITATION_SRV_UUID       = 0x14,                 /**< List of solicitated services - 16 bit UUIDs */
    BTM_BLE_ADVERT_TYPE_128SOLICITATION_SRV_UUID    = 0x15,                 /**< List of solicitated services - 128 bit UUIDs */
    BTM_BLE_ADVERT_TYPE_SERVICE_DATA                = 0x16,                 /**< Service data - 16 bit UUID */
    BTM_BLE_ADVERT_TYPE_PUBLIC_TARGET               = 0x17,                 /**< Public target address */
    BTM_BLE_ADVERT_TYPE_RANDOM_TARGET               = 0x18,                 /**< Random target address */
    BTM_BLE_ADVERT_TYPE_APPEARANCE                  = 0x19,                 /**< Appearance */
    BTM_BLE_ADVERT_TYPE_ADVERT_INTERVAL             = 0x1a,                 /**< Advertising interval */
    BTM_BLE_ADVERT_TYPE_LE_BD_ADDR                  = 0x1b,                 /**< LE device bluetooth address */
    BTM_BLE_ADVERT_TYPE_LE_ROLE                     = 0x1c,                 /**< LE role */
    BTM_BLE_ADVERT_TYPE_256SIMPLE_PAIRING_HASH      = 0x1d,                 /**< Simple Pairing Hash C-256 */
    BTM_BLE_ADVERT_TYPE_256SIMPLE_PAIRING_RAND      = 0x1e,                 /**< Simple Pairing Randomizer R-256 */
    BTM_BLE_ADVERT_TYPE_32SOLICITATION_SRV_UUID     = 0x1f,                 /**< List of solicitated services - 32 bit UUIDs */
    BTM_BLE_ADVERT_TYPE_32SERVICE_DATA              = 0x20,                 /**< Service data - 32 bit UUID */
    BTM_BLE_ADVERT_TYPE_128SERVICE_DATA             = 0x21,                 /**< Service data - 128 bit UUID */
    BTM_BLE_ADVERT_TYPE_CONN_CONFIRM_VAL            = 0x22,                 /**< LE Secure Connections Confirmation Value */
    BTM_BLE_ADVERT_TYPE_CONN_RAND_VAL               = 0x23,                 /**< LE Secure Connections Random Value */
    BTM_BLE_ADVERT_TYPE_URI                         = 0x24,                 /**< URI */
    BTM_BLE_ADVERT_TYPE_INDOOR_POS                  = 0x25,                 /**< Indoor Positioning */
    BTM_BLE_ADVERT_TYPE_TRANS_DISCOVER_DATA         = 0x26,                 /**< Transport Discovery Data */
    BTM_BLE_ADVERT_TYPE_SUPPORTED_FEATURES          = 0x27,                 /**< LE Supported Features */
    BTM_BLE_ADVERT_TYPE_UPDATE_CH_MAP_IND           = 0x28,                 /**< Channel Map Update Indication */
    BTM_BLE_ADVERT_TYPE_PB_ADV                      = 0x29,                 /**< PB-ADV */
    BTM_BLE_ADVERT_TYPE_MESH_MSG                    = 0x2A,                 /**< Mesh Message */
    BTM_BLE_ADVERT_TYPE_MESH_BEACON                 = 0x2B,                 /**< Mesh Beacon */
    BTM_BLE_ADVERT_TYPE_PSRI                        = 0x2E,                 /**< Generic Audio Provate Set Random Identifier */
    BTM_BLE_ADVERT_TYPE_3D_INFO_DATA                = 0x3D,                 /**< 3D Information Data */
    BTM_BLE_ADVERT_TYPE_MANUFACTURER                = 0xFF                  /**< Manufacturer data */
};

How does scanning work in the AnyCloud Bluetooth Stack?

To turn on observing/scanning you need to call the function:

wiced_bt_dev_status_t wiced_bt_ble_observe (wiced_bool_t start, uint8_t duration, wiced_bt_ble_scan_result_cback_t *p_scan_result_cback);

Which will cause the host stack to tell the controller to start scanning for advertising packets.  It will set the scan window and scan interval the low duty scan settings from the bluetooth configuration structure… which we setup with the Bluetooth configurator.

 .low_duty_scan_interval          = CY_BT_LOW_DUTY_SCAN_INTERVAL,                              /**< Low duty scan interval */
        .low_duty_scan_window            = CY_BT_LOW_DUTY_SCAN_WINDOW,                                /**< Low duty scan window */
        .low_duty_scan_duration          = CY_BT_LOW_DUTY_SCAN_DURATION,                              /**< Low duty scan duration in seconds (0 for infinite) */

When the controller hears an advertising packet, it will send the HCI advertising report to the Bluetooth host stack, which with then call you back.  Specifically it will call you back by calling the p_scan_result_cback” function.

You provide the callback function which has the prototype:

typedef void (wiced_bt_ble_scan_result_cback_t) (wiced_bt_ble_scan_results_t *p_scan_result, uint8_t *p_adv_data);

which contains two parameters, p_scan_result which is a structure that has the mac address and some thing data plus the p_adv_data which has the raw bytes of the advertising packet.

Add Observing to our Project

OK.  Lets add this to our project by creating a callback function like this:

Lines 5-9: Just prints out the raw bytes of the MAC address of the remote device, the one advertising

To print out the raw advertising data you need to remember that it is formatted as

  1. A length (of all of the data of the field)
  2. A type
  3. The rest of the data

When you find a field of length of 0 you know that you have reached the end of the data

On Lines 13-20: I print out one field at a time and the raw data

//
void obv_callback(wiced_bt_ble_scan_results_t *p_scan_result, uint8_t *p_adv_data)
{
    // Print the MAC Address
    printf("MAC: ");
    for(int i=0;i<6;i++)
    {
        printf("%02X:",p_scan_result->remote_bd_addr[i]);
    }
    // Print the RAW Data of the ADV Packet
    printf(" Data: ");
    int i=0;
    while(p_adv_data[i])
    {
        for(int j=0;j<p_adv_data[i];j++)
        {
            printf("%02X ",p_adv_data[i+1+j]);
        }
        i = i + p_adv_data[i]+1;
    }
    printf("\n");
    
}

Then update the management callback to start the scanner after the stack is successfully started

    switch (event)
    {
        case BTM_ENABLED_EVT:

            if (WICED_BT_SUCCESS == p_event_data->enabled.status)
            {
                printf("Started BT Stack Succesfully\n");
                wiced_bt_ble_observe(WICED_TRUE,0,obv_callback);

            }

Program and Test

Now when I run the program data comes blasting out of the screen because there are a boatload of ble devices in my house

AnyCloud> Unhandled Bluetooth Management Event: 0x16
Started BT Stack Succesfully
MAC: 76:99:58:E8:8B:1F:Data: 01 1A 0A 0C FF 4C 00 10 06 13 1A 54 F7 5A 7A 
MAC: 9E:7B:EF:0B:74:20:Data: 01 06 16 F7 FD 01 0C C2 81 CE 0C 74 58 77 19 C8 E3 84 A3 42 50 98 00 00 00 00 03 
MAC: 6F:11:7C:FF:02:13:Data: 01 1A 0A 05 FF 4C 00 10 06 03 1E BA 24 58 3D 
MAC: 3F:64:BE:4E:29:0C:Data: 01 04 FF 00 4C 02 15 26 86 F3 9C BA DA 46 58 85 4A A6 2E 7E 5E 8B 8D 00 01 00 00 C9 
MAC: 47:4B:F1:53:2C:84:Data: 01 06 FF 4C 00 10 05 08 18 79 1E C2 
MAC: C8:69:CD:18:BC:E6:Data: 01 1A 0A 0C FF 4C 00 10 05 0C 14 17 BF E9 
MAC: 27:F6:6F:1E:7A:78:Data: 01 1A FF 4C 00 09 06 03 12 C0 A8 20 0D 
MAC: 6F:AE:84:F6:6A:9F:Data: 01 06 FF 4C 00 10 05 08 18 79 1E C2 
MAC: 3F:64:BE:4E:29:0C:Data: 01 04 FF 00 4C 02 15 26 86 F3 9C BA DA 46 58 85 4A A6 2E 7E 5E 8B 8D 00 01 00 00 C9 
MAC: 41:EE:B4:9C:5C:5F:Data: 01 1A 0A 07 FF 4C 00 10 06 33 1A 49 59 46 B4 
MAC: 9E:7B:EF:0B:74:20:Data: 01 06 16 F7 FD 01 0C C2 81 CE 0C 74 58 77 19 C8 E3 84 A3 42 50 98 00 00 00 00 03 
MAC: C8:EB:ED:C8:AC:1C:Data: 01 0A 03 66 66 19 D0 07 FF EE 03 1C AC C8 ED EB C8 
MAC: 76:99:58:E8:8B:1F:Data: 01 1A 0A 0C FF 4C 00 10 06 13 1A 54 F7 5A 7A

In the next article Ill add some more smarts to manage the data to be easier to look at.

For your information here is all of the file bluetoothManager.c

#include <stdio.h>
#include <stdlib.h>

#include "cybsp.h"

#include "FreeRTOS.h"

#include "bluetoothManager.h"
#include "wiced_bt_stack.h"
#include "wiced_bt_dev.h"
#include "wiced_bt_trace.h"

//
void obv_callback(wiced_bt_ble_scan_results_t *p_scan_result, uint8_t *p_adv_data)
{
    // Print the MAC Address
    printf("MAC: ");
    for(int i=0;i<6;i++)
    {
        printf("%02X:",p_scan_result->remote_bd_addr[i]);
    }
    // Print the RAW Data of the ADV Packet
    printf(" Data: ");
    int i=0;
    while(p_adv_data[i])
    {
        for(int j=0;j<p_adv_data[i];j++)
        {
            printf("%02X ",p_adv_data[i+1+j]);
        }
        i = i + p_adv_data[i]+1;
    }
    printf("\n");
    
}

/**************************************************************************************************
* Function Name: app_bt_management_callback()
***************************************************************************************************
* Summary:
*   This is a Bluetooth stack event handler function to receive management events from
*   the BLE stack and process as per the application.
*
* Parameters:
*   wiced_bt_management_evt_t event             : BLE event code of one byte length
*   wiced_bt_management_evt_data_t *p_event_data: Pointer to BLE management event structures
*
* Return:
*  wiced_result_t: Error code from WICED_RESULT_LIST or BT_RESULT_LIST
*
*************************************************************************************************/
wiced_result_t app_bt_management_callback(wiced_bt_management_evt_t event, wiced_bt_management_evt_data_t *p_event_data)
{
    wiced_result_t result = WICED_BT_SUCCESS;

    switch (event)
    {
        case BTM_ENABLED_EVT:

            if (WICED_BT_SUCCESS == p_event_data->enabled.status)
            {
                printf("Started BT Stack Succesfully\n");
                wiced_bt_ble_observe(WICED_TRUE,0,obv_callback);

            }
            else
            {
            	printf("Error enabling BTM_ENABLED_EVENT\n");
            }

            break;

        default:
            printf("Unhandled Bluetooth Management Event: 0x%x\n", event);
            break;
    }

    return result;
}

 

AnyCloud Bluetooth Advertising Scanner (Part 2)

Summary

The second article in a series discussing the creation of a PSoC 6 + CYW43xxx Advertising Scanner using the AnyCloud SDK.  This article will use the learning from Part 1 to create a template project that starts the BLE stack.

Story

In the previous article I discussed the structure of the Cypress/Infineon Bluetooth Stack and its integration into AnyCloud.  A bunch of “theory”, well I say BS to that.  Let’s build something.

There are

Article Topic
AnyCloud Bluetooth Advertising Scanner (Part 1) Introduction to AnyCloud Bluetooth Advertising
AnyCloud Bluetooth Advertising Scanner (Part 2) Creating an AnyCloud Bluetooth project
AnyCloud Bluetooth Advertising Scanner (Part 3) Adding Observing functionality to the project
AnyCloud Bluetooth Utilities Library A set of APIs for enhancement of the AnyCloud Library
AnyCloud Bluetooth Advertising Scanner (Part 4) Adding a command line to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 5) Adding a history database to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 6) Decoding advertising packets
AnyCloud Bluetooth Advertising Scanner (Part 7) Adding recording commands to the command line
AnyCloud Bluetooth Advertising Scanner (Part 8) Adding filtering to the scanner
AnyCloud Bluetooth Advertising Scanner (Part 9) Improve the print and add packet age
AnyCloud Bluetooth Advertising Scanner (Part 10) Sort the database

All of the code can be found at git@github.com:iotexpert/AnyCloudBLEScanner.git and https://github.com/iotexpert/AnyCloudBLEScanner.git

There are git tags in place starting at part 5 so that you can look at just that version of the code.  "git tag" to list the tags.  And "git checkout part6" to look at the part 6 version of the code.

You can also create a new project with this is a template if you have the IoT Expert Manifest Files installed

Recall from Part 1 that you need three things to startup the Bluetooth Stack

  • The Hardware Configuration Structure that matches : cybt_platform_config_t
  • The Bluetooth Stack Configuration Structure that matches : wiced_bt_cfg_settings_t
  • The Bluetooth Management Callback that matches : typedef wiced_result_t (wiced_bt_management_cback_t) (wiced_bt_management_evt_t event, wiced_bt_management_evt_data_t *p_event_data);

Then you need to call

  • The hardware initialization function : cybt_platform_config_init
  • The stack initialization function : wiced_bt_stack_init

Ok let’s do this!

Basic Project

You can do all of these steps from the Eclipse IDE for ModusToolbox.  Or you can do it from the individual programs and the command line.  I like Visual Studio code, so this article will be done completely from the command line and individual configurators.

Run the new project creator from the start menu.  Start by creating a project for the development kit that you have, in my case the one currently plugged into my computer is the CY8CKIT-062S2-43012, so that is what I pick.  But, this project will work with any of the WiFI/BT combo chips attached to PSoC 6.

In previous articles I discussed the template that I use to get things going with FreeRTOS.  I won’t discuss that here, but I want FreeRTOS and the NTShell, so pick the IoT Expert FreeRTOS NTShell Template.

After about a minute you should have a project.  I always like to build the project to make sure that everything is working before I get too far down the road of modifying anything.  Run “make -j build”

arh (master) AnyCloudBLEScanner $ make -j build
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk

Prebuild operations complete
Commencing build operations...

Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk

Initializing build: MTBShellTemplate Debug CY8CKIT-062S2-43012 GCC_ARM

Auto-discovery in progress...
-> Found 205 .c file(s)
-> Found 46 .S file(s)
-> Found 23 .s file(s)
-> Found 0 .cpp file(s)
-> Found 0 .o file(s)
-> Found 6 .a file(s)
-> Found 503 .h file(s)
-> Found 0 .hpp file(s)
-> Found 0 resource file(s)
Applying filters...
Auto-discovery complete

Constructing build rules...
Build rules construction complete

==============================================================================
= Building application =
==============================================================================
Generating compilation database file...
-> ./build/compile_commands.json
Compilation database file generation complete
Building 193 file(s)
    Compiling app file lowPower.c
    Compiling app file main.c
    Compiling app file usrcmd.c
    Compiling ext file startup_psoc6_02_cm4.S

..... a bunch of lines deleted

    Compiling ext file psoc6_01_cm0p_sleep.c
    Compiling ext file psoc6_02_cm0p_sleep.c
    Compiling ext file psoc6_03_cm0p_sleep.c
    Compiling ext file psoc6_04_cm0p_sleep.c
    Compiling ext file cy_retarget_io.c
    Linking output file MTBShellTemplate.elf
==============================================================================
= Build complete =
==============================================================================

Calculating memory consumption: CY8C624ABZI-S2D44 GCC_ARM -Og

   ---------------------------------------------------- 
  | Section Name         |  Address      |  Size       | 
   ---------------------------------------------------- 
  | .cy_m0p_image        |  0x10000000   |  6044       | 
  | .text                |  0x10002000   |  54876      | 
  | .ARM.exidx           |  0x1000f65c   |  8          | 
  | .copy.table          |  0x1000f664   |  24         | 
  | .zero.table          |  0x1000f67c   |  8          | 
  | .data                |  0x080022e0   |  1688       | 
  | .cy_sharedmem        |  0x08002978   |  8          | 
  | .noinit              |  0x08002980   |  148        | 
  | .bss                 |  0x08002a14   |  2136       | 
  | .heap                |  0x08003270   |  1029520    | 
   ---------------------------------------------------- 

  Total Internal Flash (Available)          2097152    
  Total Internal Flash (Utilized)           64812      

  Total Internal SRAM (Available)           1046528    
  Total Internal SRAM (Utilized with heap)  1033500    

Then to be sure it is working, program the development kit.

arh (master) AnyCloudBLEScanner $ make program
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk

Prebuild operations complete
Commencing build operations...

Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk

Initializing build: MTBShellTemplate Debug CY8CKIT-062S2-43012 GCC_ARM

Auto-discovery in progress...
-> Found 205 .c file(s)
-> Found 46 .S file(s)
-> Found 23 .s file(s)
-> Found 0 .cpp file(s)
-> Found 0 .o file(s)
-> Found 6 .a file(s)
-> Found 503 .h file(s)
-> Found 0 .hpp file(s)
-> Found 0 resource file(s)
Applying filters...
Auto-discovery complete

Constructing build rules...
Build rules construction complete

==============================================================================
= Building application =
==============================================================================
Generating compilation database file...
-> ./build/compile_commands.json
Compilation database file generation complete
Building 193 file(s)
==============================================================================
= Build complete =
==============================================================================

Calculating memory consumption: CY8C624ABZI-S2D44 GCC_ARM -Og

   ---------------------------------------------------- 
  | Section Name         |  Address      |  Size       | 
   ---------------------------------------------------- 
  | .cy_m0p_image        |  0x10000000   |  6044       | 
  | .text                |  0x10002000   |  54876      | 
  | .ARM.exidx           |  0x1000f65c   |  8          | 
  | .copy.table          |  0x1000f664   |  24         | 
  | .zero.table          |  0x1000f67c   |  8          | 
  | .data                |  0x080022e0   |  1688       | 
  | .cy_sharedmem        |  0x08002978   |  8          | 
  | .noinit              |  0x08002980   |  148        | 
  | .bss                 |  0x08002a14   |  2136       | 
  | .heap                |  0x08003270   |  1029520    | 
   ---------------------------------------------------- 

  Total Internal Flash (Available)          2097152    
  Total Internal Flash (Utilized)           64812      

  Total Internal SRAM (Available)           1046528    
  Total Internal SRAM (Utilized with heap)  1033500    


Programming target device... 
Open On-Chip Debugger 0.10.0+dev-4.1.0.1058 (2020-08-11-03:45)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
Info : auto-selecting first available session transport "swd". To override use 'transport select <transport>'.
adapter speed: 2000 kHz
adapter srst delay: 25
adapter srst pulse_width: 25
** Auto-acquire enabled, use "set ENABLE_ACQUIRE 0" to disable
cortex_m reset_config sysresetreq
cortex_m reset_config sysresetreq
Info : Using CMSIS loader 'CY8C6xxA_SMIF' for bank 'psoc6_smif0_cm0' (footprint 14672 bytes)
Warn : SFlash programming allowed for regions: USER, TOC, KEY
Info : CMSIS-DAP: SWD  Supported
Info : CMSIS-DAP: FW Version = 2.0.0
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 0 TDO = 0 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : KitProg3: FW version: 1.14.514
Info : KitProg3: Pipelined transfers disabled, please update the firmware
Info : VTarget = 3.215 V
Info : kitprog3: acquiring the device...
Info : clock speed 2000 kHz
Info : SWD DPIDR 0x6ba02477
Info : psoc6.cpu.cm0: hardware has 4 breakpoints, 2 watchpoints
***************************************
** Silicon: 0xE453, Family: 0x102, Rev.: 0x12 (A1)
** Detected Device: CY8C624ABZI-S2D44
** Detected Main Flash size, kb: 2048
** Flash Boot version: 3.1.0.378
** Chip Protection: NORMAL
***************************************
Info : psoc6.cpu.cm4: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for psoc6.cpu.cm0 on 3333
Info : Listening on port 3333 for gdb connections
Info : starting gdb server for psoc6.cpu.cm4 on 3334
Info : Listening on port 3334 for gdb connections
Info : SWD DPIDR 0x6ba02477
Info : kitprog3: acquiring the device...
psoc6.cpu.cm0 halted due to debug-request, current mode: Thread 
xPSR: 0x41000000 pc: 0x00000190 msp: 0x080ff800
** Device acquired successfully
** psoc6.cpu.cm4: Ran after reset and before halt...
psoc6.cpu.cm4 halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x0000012a msp: 0x080ff800
** Programming Started **
auto erase enabled
Info : Flash write discontinued at 0x1000179c, next section at 0x10002000
Info : Padding image section 0 at 0x1000179c with 100 bytes (bank write end alignment)
[100%] [################################] [ Erasing     ]
[100%] [################################] [ Programming ]
Info : Padding image section 1 at 0x1000fd24 with 220 bytes (bank write end alignment)
[100%] [################################] [ Erasing     ]
[100%] [################################] [ Programming ]
wrote 62976 bytes from file /Users/arh/mtw/AnyCloudBLEScanner/build/CY8CKIT-062S2-43012/Debug/MTBShellTemplate.hex in 2.069358s (29.719 KiB/s)
** Programming Finished **
** Verify Started **
verified 62656 bytes in 0.122208s (500.683 KiB/s)
** Verified OK **
** Resetting Target **
Info : SWD DPIDR 0x6ba02477
shutdown command invoked
Info : psoc6.dap: powering down debug domain...
  
arh (master) AnyCloudBLEScanner $

When that is done, open up a terminal window and you should have a functioning base project.  Notice that I ran “help” and “tasks” from the command shell.

Now that we have a basic project working, add the Bluetooth libraries.  Run the library manager by typing “make modlibs”.  Then select “bluetooth-freertos” and the library manager will automatically select the other libraries you need.  Press Update then Close.

Next, run the bluetooth configurator by running “make config_bt”  This tool will help you make the bluetooth stack configuration structure.  When the configurator starts, press “New”

Then select our device (the PSoC 6 and the Combo chip)

Click on the “GAP Settings”.  Then press the Plus and add “Observer configuration”

Then setup the scan settings (more detail on these numbers in the next article)

  • Low duty scan window (ms) = 60
  • Low duty scan interval (ms) = 60
  • Low duty scan timeout = deselected (meaning no timeout)

Then save your configuration file.  Notice that I called it “btconfig”

When you are done you will have a directory called “GeneratedSource” inside of your project with the needed files.

The next step is to fix up the Makefile.  I like changing the name of the “App”.

# Name of application (used to derive name of final linked file).
APPNAME=AnyCloudBLEScanner

Then you need the “FREERTOS WICED_BLE” components.

# Enable optional code that is ordinarily disabled by default.
#
# Available components depend on the specific targeted hardware and firmware
# in use. In general, if you have
#
#    COMPONENTS=foo bar
#
# ... then code in directories named COMPONENT_foo and COMPONENT_bar will be
# added to the build
#
COMPONENTS=FREERTOS WICED_BLE

If you run make vscode it will update the workspace with all of the stuff needed for Visual Studio Code to be able to find all of the files.

arh (master) AnyCloudBLEScanner $ make vscode
Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk

Prebuild operations complete
Commencing build operations...

Tools Directory: /Applications/ModusToolbox/tools_2.2
CY8CKIT-062S2-43012.mk: ../mtb_shared/TARGET_CY8CKIT-062S2-43012/latest-v2.X/CY8CKIT-062S2-43012.mk

Initializing build: MTBShellTemplate Debug CY8CKIT-062S2-43012 GCC_ARM

Auto-discovery in progress...
-> Found 230 .c file(s)
-> Found 46 .S file(s)
-> Found 23 .s file(s)
-> Found 0 .cpp file(s)
-> Found 0 .o file(s)
-> Found 22 .a file(s)
-> Found 561 .h file(s)
-> Found 0 .hpp file(s)
-> Found 0 resource file(s)
Applying filters...
Auto-discovery complete

Constructing build rules...
Build rules construction complete

==============================================================================
= Generating IDE files =
==============================================================================

==============================================================================
= Building application =
==============================================================================
Generating compilation database file...
-> ./build/compile_commands.json
Compilation database file generation complete

echo "The existing MTBShellTemplate.code-workspace file has been saved to .vscode/backup";
The existing MTBShellTemplate.code-workspace file has been saved to .vscode/backup
The existing c_cpp_properties.json file has been saved to .vscode/backup
The existing launch.json file has been saved to .vscode/backup
Modifying existing settings.json file. Check against the backup copy in .vscode/backup
The existing tasks.json file has been saved to .vscode/backup

Generated Visual Studio Code files: c_cpp_properties.json launch.json openocd.tcl settings.json tasks.json

J-Link users, please see the comments at the top of the launch.json
   file about setting the location of the gdb-server.

Instructions:
1. Review the modustoolbox.toolsPath property in .vscode/settings.json
2. Open VSCode
3. Install "C/C++" and "Cortex-Debug" extensions
4. File->Open Folder (Welcome page->Start->Open folder)
5. Select the app root directory and open
6. Builds: Terminal->Run Task
7. Debugging: "Bug icon" on the left-hand pane

  
arh (master) AnyCloudBLEScanner $

Inside of Visual Studio Code, create a new file called “bt_platform_cfg_settings.h” and add:

#include "cybt_platform_config.h"
#include "cybsp.h"
#include "wiced_bt_stack.h"

extern const cybt_platform_config_t bt_platform_cfg_settings;

Inside of Visual Studio Code, create a new file called “bt_platform_cfg_settings.c” and add:

#include "cybt_platform_config.h"
#include "cybsp.h"
#include "wiced_bt_stack.h"

const cybt_platform_config_t bt_platform_cfg_settings =
{
    .hci_config =
    {
        .hci_transport = CYBT_HCI_UART,

        .hci =
        {
            .hci_uart =
            {
                .uart_tx_pin = CYBSP_BT_UART_TX,
                .uart_rx_pin = CYBSP_BT_UART_RX,
                .uart_rts_pin = CYBSP_BT_UART_RTS,
                .uart_cts_pin = CYBSP_BT_UART_CTS,

                .baud_rate_for_fw_download = 115200,
                .baud_rate_for_feature     = 115200,

                .data_bits = 8,
                .stop_bits = 1,
                .parity = CYHAL_UART_PARITY_NONE,
                .flow_control = WICED_TRUE
            }
        }
    },

    .controller_config =
    {
        .bt_power_pin      = CYBSP_BT_POWER,
        .sleep_mode =
        {
            #if (bt_0_power_0_ENABLED == 1) /* BT Power control is enabled in the LPA */
            #if (CYCFG_BT_LP_ENABLED == 1) /* Low power is enabled in the LPA, use the LPA configuration */
            .sleep_mode_enabled = true,
            .device_wakeup_pin = CYCFG_BT_DEV_WAKE_GPIO,
            .host_wakeup_pin = CYCFG_BT_HOST_WAKE_GPIO,
            .device_wake_polarity = CYCFG_BT_DEV_WAKE_POLARITY,
            .host_wake_polarity = CYCFG_BT_HOST_WAKE_IRQ_EVENT
            #else /* Low power is disabled in the LPA, disable low power */
            .sleep_mode_enabled = false
            #endif
            #else /* BT Power control is disabled in the LPA – default to BSP low power configuration */
            .sleep_mode_enabled = true,
            .device_wakeup_pin = CYBSP_BT_DEVICE_WAKE,
            .host_wakeup_pin = CYBSP_BT_HOST_WAKE,
            .device_wake_polarity = CYBT_WAKE_ACTIVE_LOW,
            .host_wake_polarity = CYBT_WAKE_ACTIVE_LOW
            #endif
        }
    },

    .task_mem_pool_size    = 2048
};

Inside of Visual Studio Code, create bluetoothManager.h.  Remember this is the Bluetooth Stack Management Callback

#pragma once
#include "wiced_bt_stack.h"
#include "wiced_bt_dev.h"

wiced_result_t app_bt_management_callback(wiced_bt_management_evt_t event, wiced_bt_management_evt_data_t *p_event_data);

Inside of Visual Studio code, create bluetoothManager.c.  This function does a whole lotta nothin… except saying that things got started.

#include <stdio.h>
#include <stdlib.h>

#include "cybsp.h"

#include "FreeRTOS.h"

#include "bluetoothManager.h"
#include "wiced_bt_stack.h"
#include "wiced_bt_dev.h"
#include "wiced_bt_trace.h"


/**************************************************************************************************
* Function Name: app_bt_management_callback()
***************************************************************************************************
* Summary:
*   This is a Bluetooth stack event handler function to receive management events from
*   the BLE stack and process as per the application.
*
* Parameters:
*   wiced_bt_management_evt_t event             : BLE event code of one byte length
*   wiced_bt_management_evt_data_t *p_event_data: Pointer to BLE management event structures
*
* Return:
*  wiced_result_t: Error code from WICED_RESULT_LIST or BT_RESULT_LIST
*
*************************************************************************************************/
wiced_result_t app_bt_management_callback(wiced_bt_management_evt_t event, wiced_bt_management_evt_data_t *p_event_data)
{
    wiced_result_t result = WICED_BT_SUCCESS;

    switch (event)
    {
        case BTM_ENABLED_EVT:

            if (WICED_BT_SUCCESS == p_event_data->enabled.status)
            {
                printf("Started BT Stack Succesfully\n");
            }
            else
            {
            	printf("Error enabling BTM_ENABLED_EVENT\n");
            }

            break;

        default:
            printf("Unhandled Bluetooth Management Event: 0x%x\n", event);
            break;
    }

    return result;
}

Next, update main.c with the required includes.

#include "bluetoothManager.h"
#include "cycfg_bt_settings.h"
#include "bt_platform_cfg_settings.h"

Then update main.c to start the stack

    cybt_platform_config_init(&bt_platform_cfg_settings);
    wiced_bt_stack_init (app_bt_management_callback, &wiced_bt_cfg_settings);

Now build and program it, remember “make -j build” and “make program”.  Look, we have a functioning stack with the two bluetooth thread running.

In the next article Ill finally get around to building the Bluetooth Scanner.

AnyCloud – Wireless Connection Manager (Part 2)

Summary

Part 2 of the discussion of using the Infineon (Cypress) PSoC 6 with a CYW4343W and the AnyCloud Connection Manager with Modus Toolbox.  The AnyCloud Connection Manager is an RTOS thread that lets you manage a connection to a WiFi network.  It knows how to scan for networks, attach, detach etc. and was recently released for use with PSoC6 and 43xxx WiFi combos.

The Story

In the last article I walked you through creating a project using the wireless connection manager (WCM) which is one of the libraries that is part of the Infineon/Cypress AnyCloud SDK.  I introduced the wifi-mw-core, wifi-connection-manager and ntshell libraries.  In this article I will update the program to include commands to

  • connect (to an Access Point)
  • disconnect (from an Access Point)
  • print (mac address and ip address)

Add the Connect

I want to add a command that will let me issue a command line like

  • connect SSID (if it doesn’t have a passphrase)
  • connect SSID passphrase

In order to connect to an Access Point you need to call the WCM API, cy_wcm_connect_ap.  Here is the function prototype:

cy_rslt_t cy_wcm_connect_ap(const cy_wcm_connect_params_t *connect_params, cy_wcm_ip_address_t *ip_addr)

This function requires that you give it two arguments

  1. A pointer for a place to store the IP address (that comes from the DHCP server)
  2. A pointer to a structure of connection parameters.  That structure is as follows:
typedef struct
{
    cy_wcm_ap_credentials_t  ap_credentials;       /**< Access point credentials */
    cy_wcm_mac_t             BSSID;                /**< Specifies the MAC address of Access Point (optional) */
    cy_wcm_ip_setting_t      *static_ip_settings;  /**< Specifies the static IP settings of the device (optional) */
    cy_wcm_wifi_band_t       band;                 /**< Specifies the Radio band to be connected (optional) */
} cy_wcm_connect_params_t;

Typically you would setup the “ap_credentials” part of the structure (unless you happen to know the MAC address of the AP you want to connect to).  Those credentials include the SSID and password as well as the security (WPA2 PSK etc…)

typedef struct
{
    cy_wcm_ssid_t        SSID;                /**< SSID of the Wi-Fi network to join, SSID should be a null terminated string. */
    cy_wcm_passphrase_t  password;            /**< Password needed to join the AP, password should be a null terminated string. */
    cy_wcm_security_t    security;            /**< Wi-Fi Security. @see cy_wcm_security_t. */
} cy_wcm_ap_credentials_t;

Security is an enumeration of possible security types.

typedef enum
{
    WHD_SECURITY_OPEN             = 0,                                                                 /**< Open security                                         */
    WHD_SECURITY_WEP_PSK          = WEP_ENABLED,                                                       /**< WEP PSK Security with open authentication             */
    WHD_SECURITY_WEP_SHARED       = (WEP_ENABLED | SHARED_ENABLED),                                    /**< WEP PSK Security with shared authentication           */
    WHD_SECURITY_WPA_TKIP_PSK     = (WPA_SECURITY | TKIP_ENABLED),                                     /**< WPA PSK Security with TKIP                            */
    WHD_SECURITY_WPA_AES_PSK      = (WPA_SECURITY | AES_ENABLED),                                      /**< WPA PSK Security with AES                             */
    WHD_SECURITY_WPA_MIXED_PSK    = (WPA_SECURITY | AES_ENABLED | TKIP_ENABLED),                       /**< WPA PSK Security with AES & TKIP                      */
    WHD_SECURITY_WPA2_AES_PSK     = (WPA2_SECURITY | AES_ENABLED),                                     /**< WPA2 PSK Security with AES                            */
    WHD_SECURITY_WPA2_TKIP_PSK    = (WPA2_SECURITY | TKIP_ENABLED),                                    /**< WPA2 PSK Security with TKIP                           */
    WHD_SECURITY_WPA2_MIXED_PSK   = (WPA2_SECURITY | AES_ENABLED | TKIP_ENABLED),                      /**< WPA2 PSK Security with AES & TKIP                     */
    WHD_SECURITY_WPA2_FBT_PSK     = (WPA2_SECURITY | AES_ENABLED | FBT_ENABLED),                       /**< WPA2 FBT PSK Security with AES & TKIP */
    WHD_SECURITY_WPA3_SAE         = (WPA3_SECURITY | AES_ENABLED),                                     /**< WPA3 Security with AES */
    WHD_SECURITY_WPA3_WPA2_PSK    = (WPA3_SECURITY | WPA2_SECURITY | AES_ENABLED),                     /**< WPA3 WPA2 PSK Security with AES */

    WHD_SECURITY_WPA_TKIP_ENT     = (ENTERPRISE_ENABLED | WPA_SECURITY | TKIP_ENABLED),                /**< WPA Enterprise Security with TKIP                     */
    WHD_SECURITY_WPA_AES_ENT      = (ENTERPRISE_ENABLED | WPA_SECURITY | AES_ENABLED),                 /**< WPA Enterprise Security with AES                      */
    WHD_SECURITY_WPA_MIXED_ENT    = (ENTERPRISE_ENABLED | WPA_SECURITY | AES_ENABLED | TKIP_ENABLED),  /**< WPA Enterprise Security with AES & TKIP               */
    WHD_SECURITY_WPA2_TKIP_ENT    = (ENTERPRISE_ENABLED | WPA2_SECURITY | TKIP_ENABLED),               /**< WPA2 Enterprise Security with TKIP                    */
    WHD_SECURITY_WPA2_AES_ENT     = (ENTERPRISE_ENABLED | WPA2_SECURITY | AES_ENABLED),                /**< WPA2 Enterprise Security with AES                     */
    WHD_SECURITY_WPA2_MIXED_ENT   = (ENTERPRISE_ENABLED | WPA2_SECURITY | AES_ENABLED | TKIP_ENABLED), /**< WPA2 Enterprise Security with AES & TKIP              */
    WHD_SECURITY_WPA2_FBT_ENT     = (ENTERPRISE_ENABLED | WPA2_SECURITY | AES_ENABLED | FBT_ENABLED),  /**< WPA2 Enterprise Security with AES & FBT               */

    WHD_SECURITY_IBSS_OPEN        = (IBSS_ENABLED),                                                    /**< Open security on IBSS ad-hoc network                  */
    WHD_SECURITY_WPS_SECURE       = AES_ENABLED,                                                       /**< WPS with AES security                                 */

    WHD_SECURITY_UNKNOWN          = -1,                                                                /**< May be returned by scan function if security is unknown. Do not pass this to the join function! */

    WHD_SECURITY_FORCE_32_BIT     = 0x7fffffff                                                         /**< Exists only to force whd_security_t type to 32 bits */
} whd_security_t;

Having to know the security of the AP is a total pain in the neck.  Where do you find the security from?  It turns out that when an AP beacons, the security of that SSID is one of the things that is broadcast.  What this means is that my program will need to

  1. When the connect command is called it should scan for the SSID that is part of the connect command & wait
  2. When the scan finds that SSID it will put the security type into the correct datastructure
  3. Then call the connect.

The way that I will do this is to

  1. Build a filter (that looks only for the user specified SSID)
  2. Provides a pointer for a place to store the security type.

I use the cy_wcm_scan function to do this.  Here is the function prototype:

cy_rslt_t cy_wcm_start_scan(cy_wcm_scan_result_callback_t callback, void *user_data, cy_wcm_scan_filter_t *scan_filter)

The scan filter is just a structure that specified

  1. A mode (which type of filter you want)
  2. The specific thing that you are looking for.
typedef struct
{
    cy_wcm_scan_filter_type_t      mode;        /**< Scan filter mode */
    union
    {
        cy_wcm_ssid_t              SSID;        /**< Service Set Identification */
        cy_wcm_mac_t               BSSID;       /**< MAC address of Access Point */
        cy_wcm_wifi_band_t         band;        /**< Radio band */
        cy_wcm_scan_rssi_range_t   rssi_range;  /**< RSSI range */
    } param;                                    /**< Scan filter mode specific paramter */
} cy_wcm_scan_filter_t;

The mode is simply an enumeration of the types of filters:

typedef enum
{
   CY_WCM_SCAN_FILTER_TYPE_SSID = 0,   /**< Denotes SSID based scan filtering */
   CY_WCM_SCAN_FILTER_TYPE_MAC,        /**< Denotes MAC  based scan filtering */
   CY_WCM_SCAN_FILTER_TYPE_BAND,       /**< Denotes BAND based scan filtering */
   CY_WCM_SCAN_FILTER_TYPE_RSSI,       /**< Denotes RSSI based scan filtering */
}cy_wcm_scan_filter_type_t;

What I want to do is start the scan and then wait for a semaphore.  To do this I will create a semaphore variable at the top of the netTask.c

SemaphoreHandle_t scanApSempahore = NULL;

Inside of the switch I will

  1. Create a connection parameters structure (line 226-227)
  2. setup the scan filter (line 231-232)
  3. create the semaphore (line 235)
  4. run the scan (line 236)
  5. wait for the semaphore to be set or timeout (line 239) notice that I hardcoded it to 10 seconds
				cy_wcm_connect_params_t connect_params;
				memset(&connect_params, 0, sizeof(cy_wcm_connect_params_t));

				// setup scan filter - In order to connect to an SSID you need to know the security type
				// To find the security I scan for JUST that SSID which will tell me the security type
				scanFilter.mode = CY_WCM_SCAN_FILTER_TYPE_SSID;
				strcpy((char *)scanFilter.param.SSID,(char *)msg.val0);

				// The scan callback will either 1) unlock the semaphore or 2) timeout (meaning it didnt find it)
				scanApSempahore = xSemaphoreCreateBinary();
				cy_wcm_start_scan(findApCallback,&connect_params.ap_credentials.security,&scanFilter);
				
				// The semaphore will return pdFALSE if it TIMES out or pdTrue IF it got unlocked by the scan
				if(xSemaphoreTake( scanApSempahore, pdMS_TO_TICKS(10000)) == pdTRUE)

In the scan callback I will check to see if I have real data (in other words the scan is not complete).  In the setup above I made the user data be a pointer to the place to store the security.  On line 54 I will store the security type that came back from the scan in the place pointed to by the user data pointer.  Then I will stop the scan and give the semaphore.

// This callback is used to find a specific SSID and then store the security type into the user data
// When I want to connect it will scan with a "filter" and the user data will be a pointer to the
// place to store the security
void findApCallback( cy_wcm_scan_result_t *result_ptr, void *user_data, cy_wcm_scan_status_t status )
{
	if(status == CY_WCM_SCAN_INCOMPLETE)
	{
		whd_security_t *mySecurity = (whd_security_t *)user_data;
	 	*mySecurity = result_ptr->security;
		cy_wcm_stop_scan();
		xSemaphoreGive(scanApSempahore);
	}
}

Now back in the switch statement you can actually connect because you know the security type (line 244)  The else clause on line 253 handles the case where the timeout of the semaphore occurred, meaning that the scan didn’t find the AP.

				// The semaphore will return pdFALSE if it TIMES out or pdTrue IF it got unlocked by the scan
				if(xSemaphoreTake( scanApSempahore, pdMS_TO_TICKS(10000)) == pdTRUE)
				{
					strcpy((char *)connect_params.ap_credentials.SSID,(char *)msg.val0);
					strcpy((char *)connect_params.ap_credentials.password,(char *)msg.val1);

					result = cy_wcm_connect_ap(&connect_params,&ip_addr);
					if(result == CY_RSLT_SUCCESS)
						printf("Connect Succeeded SSID=%s\n",(char *)msg.val0);
					else
					{
						printf("Connect to %s failed\n",(char *)msg.val0);
					}	
				}
				else
				{
					printf("Scan semaphore failed - couldnt find AP\n");
				}
				free((void *)msg.val0); // Free the SSID and PW that was passed by the caller
				free((void *)msg.val1);

			}
			break;

With all of the connection work done, you can add the scan command to “usrcmd.c”.  It just looks at the number of arguments (either 2 or 3), then sets up the message to send to the network task, the queues the message.

static int usrcmd_connect(int argc, char **argv)
{
    networkQueueMsg_t msg;

    if(argc == 2)
    {
        msg.val0 = (uint32_t)malloc(strlen(argv[1])+1);
        msg.val1 = (uint32_t)malloc(sizeof(""));
        strcpy((char *)msg.val0,argv[1]);
        strcpy((char *)msg.val1,"");
        msg.cmd = net_connect;
        xQueueSend(networkQueue,(const void *)&msg,portMAX_DELAY);
    }
    
    if(argc == 3)
    {
        msg.val0 = (uint32_t)malloc(strlen(argv[1])+1);
        msg.val1 = (uint32_t)malloc(strlen(argv[2])+1);
        strcpy((char *)msg.val0,argv[1]);
        strcpy((char *)msg.val1,argv[2]);
        msg.cmd = net_connect;
        xQueueSend(networkQueue,(const void *)&msg,portMAX_DELAY);
    }

    
    return 0;

}

Add the Disconnect Command

The disconnect command is trivial.  Just call the disconnect api.

			case net_disconnect:
				cy_wcm_disconnect_ap();
			break;

Which you also need to add to the usercmd.c

static int usrcmd_disconnect(int argc, char **argv)
{
    networkQueueMsg_t msg;
    msg.cmd = net_disconnect;
    xQueueSend(networkQueue,(const void *)&msg,portMAX_DELAY);
    
    return 0;
}

Add the Print Command

The print command will have two optional parameters, IP (to print the current IP address) and MAC (to print our MAC address).  The first command is print ip.

			case net_printip:
			result = cy_wcm_get_ip_addr(CY_WCM_INTERFACE_TYPE_STA,&ip_addr,1);
			if(result == CY_RSLT_SUCCESS)
			{
				printf("IP Address=");
				printIp(&ip_addr);
				printf("\n");
			}
			else if(result == CY_RSLT_WCM_NETWORK_DOWN)
				printf("Network disconnected\n");
			else 
				printf("IP Address call return unknown %d\n",(int)result);
			break;

The MAC address command is also simple:

			case net_printmac:
				result = cy_wcm_get_mac_addr(CY_WCM_INTERFACE_TYPE_STA,&mac_addr,1);
				if(result == CY_RSLT_SUCCESS)
				{
					printf("MAC Address =");
					printMac(mac_addr);
					printf("\n");
				}
				else
					printf("MAC Address = Unknown\n");
			break;
		}

And you need to add the print command to usrcmd.c

static int usrcmd_print(int argc, char **argv)
{
    networkQueueMsg_t msg;
    if(argc == 2 && strcmp(argv[1],"ip")==0)
    {
        msg.cmd = net_printip;
        xQueueSend(networkQueue,(const void *)&msg,portMAX_DELAY);
    }
    if(argc == 2 && strcmp(argv[1],"mac")==0)
    {
        msg.cmd = net_printmac;
        xQueueSend(networkQueue,(const void *)&msg,portMAX_DELAY);
    }
    return 0;

}

All of this code is available on github at

  • git@github.com:iotexpert/wcm_example
  • https://github.com/iotexpert/wcm_example

AnyCloud – Wireless Connection Manager (Part 1)

Summary

A discussion of using the Infineon (Cypress) PSoC 6 with a CYW4343W and the AnyCloud Connection Manager with Modus Toolbox.  The AnyCloud Connection Manager is an RTOS thread that lets you manage a connection to a WiFi network.  It knows how to scan for networks, attach, detach etc. and was recently released for use with PSoC6 and 43xxx WiFi combos.

The Story

In the WICED WiFI SDK there is an example program called “test.console” which allows you to use a UART command line to do networking “stuff”.  With the release of the new AnyCloud SDK I decided that I should rebuild some of that program using the PSoC and WiFi.  Basically a set of commands like “scan” to interact with the Radio World!  In the picture you below you can see that I use the command “scan” to list out the networks at my house.

You can also use the command “help” to list out the commands.

Architecture

My implementation will have three tasks

  1. A Blinking LED Task (which doesn’t interact with any other tasks)
  2. The NT Shell – which will send commands to the network queue.
  3. The Network Task which will receive commands from the NT Shell task and trigger the Wireless Connection Manager to do something
  4. The Wireless Connection Manager which will interact with the WiFI Radio via the Wireless Host driver.

Make the Basic Project

To build this project start by creating a new project.  The development board that I had plugged into my computer when I began this project is a CY8CPROTO-062-4343W so this is the one I will select.

In the new project creator pick the “CY8CPROTO-062-4343W”

I created a FreeRTOS template project that does all of the right stuff.  I wrote an article about how to use the IoT Expert Manifestt here, and until you add the IoT Manifest to your setup you will not get the FreeRTOS Template project.

After the new project work is done you should see

Add the nt-shell, which comes from my IoT Expert library (read about how to add the IoT Expert library here).

I have been on a kick of using Visual Studio code.  So, run “make vscode” to setup the project for Visual Studio Code.

In the finder, copy the template files from nt-shell into your project.

You can also do it with the command line.

Edit main.c to include the configuration.

#include "ntshell.h"
#include "psoc6_ntshell_port.h"

Update the main.c to have the ntShellThread

// Global variable with a handle to the shell
ntshell_t ntshell;

void ntShellTask()
{

  printf("Started ntshell\n");
  setvbuf(stdin, NULL, _IONBF, 0);
  ntshell_init(
               &ntshell,
               ntshell_read,
               ntshell_write,
               ntshell_callback,
               (void *)&ntshell);
  ntshell_set_prompt(&ntshell, "AnyCloud> ");
  vtsend_erase_display(&ntshell.vtsend);
  ntshell_execute(&ntshell);
}

And start the ntshell task.

    xTaskCreate(ntShellTask, "nt shell task", configMINIMAL_STACK_SIZE*2,0 /* args */ ,4 /* priority */, 0);

When you want to add a command to the shell you need to do three things

  1. Add a function prototype for the command
  2. Add the command to the command list
  3. Write the function
static int usrcmd_help(int argc, char **argv);
static int usrcmd_info(int argc, char **argv);
static int usrcmd_clear(int argc, char **argv);
static int usrcmd_printargs(int argc, char **argv);
static const cmd_table_t cmdlist[] = {
    { "help", "This is a description text string for help command.", usrcmd_help },
    { "info", "This is a description text string for info command.", usrcmd_info },
    { "clear", "Clear the screen", usrcmd_clear },
    { "printargs","print the list of arguments", usrcmd_printargs},
};

Here is an example of of the printargs command.  The shell will call your function with a ARGC=number of arguments and ARGV[] an array of the pointers to the actual arguments.

static int usrcmd_printargs(int argc, char **argv)
{
    printf("ARGC = %d\n",argc);

    for(int i =0;i<argc;i++)
    {
        printf("argv[%d] = %s\n",i,argv[i]);
    }
    return 0;

}

Build and test your program.

Turn on the Connection Manager and Core Middleware Library

Now, add the wifi-mw-core library & Connection Manager using the library manager.  You can start it with “make modlibs”.  These two libraries are in the “WiFi Middleware” category.

Copy the FreeRTOSConfig.h from the libs/wifi-mw-core/configs directory into your project (so you can modify it)

Update the Makefile to include the WiFi components (line 71) and the MBED TLS configuration (line 86)

COMPONENTS=FREERTOS PSOC6HAL LWIP MBEDTLS 4343W
# Like COMPONENTS, but disable optional code that was enabled by default.
DISABLE_COMPONENTS=

# By default the build system automatically looks in the Makefile's directory
# tree for source code and builds it. The SOURCES variable can be used to
# manually add source code to the build process from a location not searched
# by default, or otherwise not found by the build system.
SOURCES=

# Like SOURCES, but for include directories. Value should be paths to
# directories (without a leading -I).
INCLUDES=

# Add additional defines to the build process (without a leading -D).
DEFINES=MBEDTLS_USER_CONFIG_FILE='"configs/mbedtls_user_config.h"' CYBSP_WIFI_CAPABLE

Create networkTask.h/.c & Start the Task in main.c

Now let’s create the networkTask.  This task will control all of the networking functions in the system.  The first file, networkTask.h, is the public interface.  It declares a Queue where you can push messages (line 5) an enumeration of the messages (lines 7-14), a definition of the message data (lines 17-22) and finally the network task declaration (line 24).

#pragma once
#include "FreeRTOS.h"
#include "queue.h"

extern QueueHandle_t networkQueue;

typedef enum {
    net_scan,
    net_connect,
    net_disconnect,
    net_printip,
    net_printmac,

} networkCmd_t;


typedef struct {
    networkCmd_t cmd;
    uint32_t val0;
    uint32_t val1;

} networkQueueMsg_t;

void networkTask(void *arg);

Ill go ahead and modify main.c to start the yet to be written network task.  You need to include the header file for the task and define a handle for the task.

#include "networkTask.h"

TaskHandle_t networkTaskHandle;

Finally in main function, start the task.

    xTaskCreate(networkTask, "networkTask", configMINIMAL_STACK_SIZE*8,0 /* args */ ,4/* priority */, &networkTaskHandle);

Create the file networkTask.c.  Make a bunch of includes.

#include <stdio.h>
#include <stdlib.h>
#include "FreeRTOS.h"
#include "task.h"
#include "cy_wcm.h"
#include "cy_wcm_error.h"
#include "whd_types.h"
#include "queue.h"
#include "semphr.h"
#include "networkTask.h"

Now let’s define the actual task function.  This function will

  1. Call the wcm_init function to make a WiFi Station (lines 201-203)
  2. Tell the task that you would like to be called back when things happen (line 205)
  3. Initialize the Queue to receive command messages from other tasks.
  4. Make an infinite loop to receive the commands and process the messages.  Notice that I dont do anything with the message for now.  Ill add the code later.
// The networkTask will:
// - startup the wireless connection manager
// - sit waiting on the rtos queue... getting messages from other tasks
// - and do ( net_scan, net_connect, net_disconnect, net_printip, net_printmac,)

void networkTask(void *arg)
{
	cy_rslt_t result;
	cy_wcm_config_t config;
	cy_wcm_scan_filter_t scanFilter;

	memset(&config, 0, sizeof(cy_wcm_config_t));
    config.interface = CY_WCM_INTERFACE_TYPE_STA;
	cy_wcm_init	(&config);

	cy_wcm_register_event_callback(	wcmCallback	);

	networkQueue = xQueueCreate( 5, sizeof(networkQueueMsg_t));

	while(1)
	{
		networkQueueMsg_t msg;

		xQueueReceive(networkQueue,(void *)&msg,portMAX_DELAY);  // Wait for commands from other tasks

		switch(msg.cmd)
		{
			case net_scan: // 0=stop scan !0=start scan
			break;

			case net_connect:
			break;

			case net_disconnect:
			break;

			case net_printip:
			break;

			case net_printmac:
			break;
		}
	}

}

Create Utilities for Printing out the IP and MAC Address

It the network task I want to be able to print out IP address (both IPV4 and IPV6).  I also want to be able to print out 6-byte MAC address.  In the Cypress drivers, an IP address is a structure that holds either a IPV4 or and IPV6 address.  It then contains the 4 or 6 bytes of the address.  Here is the type definition.

/**
 * IP Address Structure
 */
typedef struct
{
    cy_wcm_ip_version_t version;  /**< IP version */
    union
    {
        uint32_t v4;     /**< IPv4 address bytes */
        uint32_t v6[4];  /**< IPv6 address bytes */
    } ip;                /**< IP address bytes */
} cy_wcm_ip_address_t;

The IP v ersion is just an enumeration.

/**
 * IP Version
 */
typedef enum
{
    CY_WCM_IP_VER_V4 = 4,      /**< Denotes IPv4 version */
    CY_WCM_IP_VER_V6 = 6       /**< Denotes IPv6 version */
} cy_wcm_ip_version_t;

To print an address, figure out which version you are working on.  Then dump the raw bytes.  Notice in the IPV4 case it is encoded into a uint32_t as 4 continuous bytes.

void printIp(cy_wcm_ip_address_t *ipad)
{
	if(ip_addr.version == CY_WCM_IP_VER_V4)
	{
		printf("%d.%d.%d.%d",(int)ipad->ip.v4>>0&0xFF,(int)ipad->ip.v4>>8&0xFF,(int)ipad->ip.v4>>16&0xFF,(int)ipad->ip.v4>>24&0xFF);
	}
	else if (ip_addr.version == CY_WCM_IP_VER_V6)
	{
		for(int i=0;i<4;i++)
		{
			printf("%0X:",(unsigned int)ip_addr.ip.v6[i]);
		}
	}
	else
	{
		printf("IP ERROR %d",ipad->version);
	}			
}

A MAC address is just an array of uint8_t of length CY_WCM_MAC_ADDR_LEN  (which we pound defined to 6)

typedef uint8_t cy_wcm_mac_t[CY_WCM_MAC_ADDR_LEN];                   /**< Unique 6-byte MAC address */

So, the print is just a loop. (probably should have done something slightly better so I dont end up with the trailing : – oh well)

void printMac(cy_wcm_mac_t mac)
{
	for(int i=0;i<CY_WCM_MAC_ADDR_LEN;i++)
	{
		uint8_t val = mac[i];
		printf("%02X:",val);
	}
}

Add the Scan Command

For the scan I want to print out the:

  1. SSID
  2. RSSI (in dBM)
  3. Channel
  4. Band
  5. Speed
  6. Type of Ap
  7. Country Code
  8. MAC address of the Access Point (AKA BSSID)
  9. Type of Security

In order to have the WCM run a scan you need to call the function cy_wcm_start_scan.  What this will do is tell the 4343W to scan all the channels on the 2.4GHZ band and listen for access point beacons.  This function has three arguments

  1. A function pointer to call back when you find an AP
  2. A user settable data pointer
  3. A filter (which can limit to an SSID, BSSID or RSSI)
cy_rslt_t cy_wcm_start_scan(cy_wcm_scan_result_callback_t callback, void *user_data, cy_wcm_scan_filter_t *scan_filter)

Here is my version of the scanCallback which I put in the networkTask.c file.

// This function is called back when the user asks for an overall scan.
// It just prints out the information about the networks that it hears about
void scanCallback( cy_wcm_scan_result_t *result_ptr, void *user_data, cy_wcm_scan_status_t status )

The result_ptr is a pointer to a structure that contains the data for the found access point.

/**
 * Structure for storing scan results
 */
typedef struct
{
    cy_wcm_ssid_t                SSID;             /**< Service Set Identification (i.e. Name of Access Point)                    */
    cy_wcm_mac_t                 BSSID;            /**< Basic Service Set Identification (i.e. MAC address of Access Point)       */
    int16_t                      signal_strength;  /**< Receive Signal Strength Indication in dBm. <-90=Very poor, >-30=Excellent */
    uint32_t                     max_data_rate;    /**< Maximum data rate in kilobits/s                                           */
    cy_wcm_bss_type_t            bss_type;         /**< Network type                                                              */
    cy_wcm_security_t            security;         /**< Security type                                                             */
    uint8_t                      channel;          /**< Radio channel that the AP beacon was received on                          */
    cy_wcm_wifi_band_t           band;             /**< Radio band                                                                */
    uint8_t                      ccode[2];         /**< Two letter ISO country code from AP                                       */
    uint8_t                      flags;            /**< flags                                                                     */
    uint8_t                      *ie_ptr;          /**< Pointer to received Beacon/Probe Response IE(Information Element)         */
    uint32_t                     ie_len;           /**< Length of IE(Information Element)                                         */
} cy_wcm_scan_result_t;

The other parameter to the callback is the status.  The status will either be CY_WCM_SCAN_COMPLETE or CY_WCM_SCAN_INCOMPLETE.  The first thing that to do is see if this callback is the one that tells me that the scan is complete, if so I just return (and don’t print anything)

	if(status == CY_WCM_SCAN_COMPLETE)
		return;

Then I print out the raw SSID, Signal Strength and Channel.

	printf("%32s\t%d\t%d\t",result_ptr->SSID,result_ptr->signal_strength,result_ptr->channel);

Next I figure out what channel we are talking about.  The 4343W is single band 2.4GHZ, however, other Cypress chips have dual band.

	switch(result_ptr->band)
	{
		case CY_WCM_WIFI_BAND_ANY:
			printf("ANY");
		break;
		case CY_WCM_WIFI_BAND_5GHZ:
			printf("5.0 GHZ");
		break;
		case CY_WCM_WIFI_BAND_2_4GHZ:
			printf("2.4 GHZ");
		break;
	}

Then I printout the maximum data rate, which is 0 for all of my test cases.  I have no idea why.

	printf("%d",(int)result_ptr->max_data_rate);

Then I printout what type of AP we are talking about.

	switch(result_ptr->bss_type)
	{
		case CY_WCM_BSS_TYPE_INFRASTRUCTURE:
			printf("INFR");
		break; 	
		case CY_WCM_BSS_TYPE_ADHOC: 
			printf("ADHOC");
		break;	
		case CY_WCM__BSS_TYPE_ANY: 	
			printf("ANY");
		break;
		case CY_WCM_BSS_TYPE_MESH:
			printf("MESG");
		break;
		case CY_WCM_BSS_TYPE_UNKNOWN: 
			printf("UNKWN");
		break;
	}

Then the country code.

	printf("%c%c",result_ptr->ccode[0],result_ptr->ccode[1]);

Then the Basic Service Set ID of the AP, which is also known as the MAC address of the AP.

	printMac(result_ptr->BSSID);

Then the security type.

	switch(result_ptr->security)
	{
		case CY_WCM_SECURITY_OPEN:
			printf("OPEN");
		break;
    	case CY_WCM_SECURITY_WEP_PSK:
			printf("WEP_PSK");
		break;
		case CY_WCM_SECURITY_WEP_SHARED:
			printf("WEP_SHARED");
		break;
    	case CY_WCM_SECURITY_WPA_TKIP_PSK:
			printf("WPA_TKIP_PSK");
		break;
		case CY_WCM_SECURITY_WPA_AES_PSK:
			printf("WPA_AES_PSK");
		break;
		case CY_WCM_SECURITY_WPA_MIXED_PSK:
			printf("WPA_MIXED_PSK");
		break;
		case CY_WCM_SECURITY_WPA2_AES_PSK:
			printf("WPA2_AES_PSK");
		break;
		case CY_WCM_SECURITY_WPA2_TKIP_PSK:
			printf("WPA2_TKIP_PSK");
		break;
		case CY_WCM_SECURITY_WPA2_MIXED_PSK:
			printf("WPA2_MIXED_PSK");
		break;
		case CY_WCM_SECURITY_WPA2_FBT_PSK:
			printf("WPA2_FBT_PSK");
		break;
		case CY_WCM_SECURITY_WPA3_SAE:
			printf("WPA3_SAE");
		break;
		case CY_WCM_SECURITY_WPA3_WPA2_PSK:
			printf("WPA3_WPA2_PSK");
		break;
		case CY_WCM_SECURITY_IBSS_OPEN:
			printf("IBSS_OPEN");
		break;
		case CY_WCM_SECURITY_WPS_SECURE:
			printf("WPS_SECURE");
		break;
		case CY_WCM_SECURITY_UNKNOWN:
			printf("UNKNOWN");
		break;
		case CY_WCM_SECURITY_FORCE_32_BIT:
			printf("FORCE_32_BIT");
		break;
	}

With a complete scanCallback function I can now update the networkTask to deal with the queued commands of net_scan type.  This simply either stops or starts the scan based on the message parameter.

			case net_scan: // 0=stop scan !0=start scan
				if(msg.val0 == 0)
					cy_wcm_stop_scan();
				else
				{
					printf("\n");
					result = cy_wcm_start_scan(scanCallback,0,0);
					if(result != CY_RSLT_SUCCESS)
						printf("Scan error\n");
				}
			break;

The last things to do is to add the scan command to the usrcmd.c

static int usrcmd_scan(int argc, char **argv)
{

    static int state=0;

    if(argc==1)
    {
        state = (state ==0)?1:0;
    }
    else if(argc == 2)
    {
        if(strcmp(argv[1],"on") == 0)
        {
            state=1;
        } 
        else if(strcmp(argv[1],"off") == 0)
        {
            state = 0;
        }
        else
        {
            printf("usage: scan [on|off]\n");
            return 0;
        }
        
    }
    else
    {
        printf("usage scan: [on|off]\n");
        return 0;
    }
    
    networkQueueMsg_t msg;
    msg.cmd = net_scan;
    msg.val0 = state;
    xQueueSend(networkQueue,(const void *)&msg,portMAX_DELAY);
    return 0;
}

OK.  Let it rip.  You should be able to run network scans.  In the next Article I will add a connect and disconnect commands.

You can find all of this example code at https://github.com/iotexpert/wcm_example

MBED OS & CY8CKIT_062S2_43012 & Segger emWin & NTP

Summary

Have you ever wondered about the nature of time?  Given the demographics of my readers I am quite sure that all of you have pondered this topic.  In this article I will solve one of the great mysteries of human kind.  What time is it?  Well that may be a little bit over dramatic 🙂  Actually what I will show you is how to use the CY8CKIT-062S2-43012 development kit to get time from the internet using the Network Time Protocol (NTP), update the PSoC 6 RTC and display it on the CY8CKIT-028-TFT using MBED OS.

Unfortunately I will not show you a beautiful way to convert UTC to Eastern Time because I don’t actually know what that way would be which is very frustrating.  Every way that I know requires me to drag a lot of crap into my PSoC6 which I don’t really want to do.

For this article I will discuss:

  1. MBED OS Project Setup
  2. MBED OS Event Queues
  3. The RTOS Architecture
  4. The Display Function(s)
  5. The NTP Thread
  6. The WiFI/Main Thread
  7. The Whole Program

Project Setup: Create and Configure Project + Add the Libraries

You should start this project by creating a project by running “mbed new NTPDemo” or by using mbed studio to create a new project.  To run this project requires at least mbed-os-5.13.3.  You have two basic choices to get the latest mbed.  For some reason which I don’t totally understand when you run mbed new it gives you a slightly older version of mbed-os.  To get a new version you can either “cd mbed-os ; mbed update mbed-os-5.13.3” or to get the latest “cd mbed-os ; mbed update master”.

In this project I use three libraries, emWin, the IoT Expert ST7789V library (for the display) and the ntp-client library.  To add them run

The emWin library requires that you tell emWin about which version of the library .a to link with.  You can do this by adding this to the mbed_app.json

{
    "target_overrides": {
        "*": {
            "target.components_add": ["EMWIN_OSNTS"]
        }
    }
}

MBED OS Event Queues

For this project I will use one of the cool RTOS mechanisms that is built into MBED OS,  the “EventQueue“.   There is a nice tutorial in the MBED OS documentation.  An EventQueue is a tool for running a function “later” and in a different thread context.  What does that mean?  It means that there is a thread that sits around waiting until you tell it to run a function.  You tell it to run the function by pushing a function pointer into it’s EventQueue.  In other words, an EventQueue is a thread that waits for functions to be pushed into queue.  When the function is pushed into the queue it runs it.

How is this helpful?  There are a several of reasons.

  • If you are in an ISR it allows you to defer execution of something to the main program.
  • It can be used to serialize access to some resource – in my case the display.
  • It allows you to schedule some event to happen regularly

When MBED OS starts it automatically creates two of these event queues one of the queue threads runs at “osPriorityNormal” and can be accessed via mbed_event_queue();  The other event queue runs at “osPriorityHigh” and can be accesed by mbed_highprio_event_queue();  For some reason (which I don’t understand) these queues are documented on a separate page.

The RTOS Architecture

Here is a picture of the architecture of my program.

 

The Main Thread

The main function which is also the main thread, which then becomes the WiFi Thread

  1. Initializes the GUI
  2. Starts up the Display Event Queue
  3. Turns on the WiFi and attaches a callback (to notify the program of WiFI Status Changes)
  4. Try’s to connect to the WiFi Network
  5. If it fails, it updates the display and try’s again after 2 seconds
  6. Once it is connected it starts up the NTP Server Thread
  7. And then waits for the WiFi semaphore to be set… which only happens if WiFi gets disconnected at which point it goes back to the start of the WiFI connection and try again.
int main()
{
    int wifiConnectionAttempts;
    int ret;

    GUI_Init();
    displayQueue = mbed_event_queue();
    displayQueue->call_every(1000, &updateDisplayTime);

    wifi = WiFiInterface::get_default_instance();
    wifi->attach(&wifiStatusCallback);

   while(1)
   {
       wifiConnectionAttempts = 1;
        do {

            ret = wifi->connect(MBED_CONF_APP_WIFI_SSID, MBED_CONF_APP_WIFI_PASSWORD, NSAPI_SECURITY_WPA_WPA2);
            displayQueue->call(updateDisplayWiFiConnectAttempts,wifiConnectionAttempts);

            if (ret != 0) {
                wifiConnectionAttempts += 1;
                wait(2.0); // If for some reason it doesnt work wait 2s and try again
            }
        } while(ret !=0);

        // If the NTPThread is not running... then start it up
        if(netTimeThreadHandle.get_state() == Thread::Deleted)
            netTimeThreadHandle.start(NTPTimeThread);
 
        WiFiSemaphore.acquire();
   }

Display Event Queue

Display EventQueue is used to run functions which update the display.  By using an EventQueue it ensure that the Display updates happen serially and there are no display resource conflicts.  The four functions are

  1. updateDisplayWiFiStatus
  2. updateDisplayWifiConnectAttempts
  3. updateDisplayNTPCount
  4. updateDisplayTime

I wanted a function which could display the state of the WiFi connection on the screen.  This is a string of text which is generated in the connection status function.  In order to send the message, the connection status function will “malloc” which then requires the updateDisplayWiFiStatus function to free the memory associated with the message.

#define DISP_LEFTMARGIN 10
#define DISP_TOPMARGIN 4
#define DISP_LINESPACE 2
// updateDisplayWiFiStatus
// Used to display the wifi status
void updateDisplayWiFiStatus(char *status)
{
    GUI_SetFont(GUI_FONT_16_1);
    GUI_DispStringAt(status,DISP_LEFTMARGIN, DISP_TOPMARGIN); 
    free(status);  
}

When I started working on this program I had a bug in my connections so I added the ability to tell how many WiFI connections attempts had happened.  I also wondered how many times there might be a disconnect if I ran this program a long time.  The answer is I ran it for two days and it didn’t disconnect a single time.  This function simply takes a number from the caller and displays it on the screen.  Notice that I use snprintf to make sure that I don’t overrun the buffer (which I doubt could happen because I made it 128 bytes).

// updateDisplayWiFiConnectAttempts
// This function displays the number of attempted connections
void updateDisplayWiFiConnectAttempts(int count)
{
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"WiFi Connect Attempts = %d",count); 
    GUI_SetFont(GUI_FONT_16_1);
    GUI_DispStringAt(buffer,DISP_LEFTMARGIN, DISP_TOPMARGIN + (GUI_GetFontSizeY()+DISP_LINESPACE) ); 
}

I was curious how many times the NTP connection would happen.  So I added the ability to display a count.  Notice that I use a static variable to keep track of the number of times this function is called rather than pushing the number as an argument.  Perhaps this is a design flaw?

// updateDisplayNTPCount
// updates the display with the number of time the NTP Server has been called
void updateDisplayNTPCount(void)
{
    static int count=0;
    char buffer[128];
    count = count + 1;
    snprintf(buffer,sizeof(buffer),"NTP Updates = %d\n",count);
    GUI_SetFont(GUI_FONT_16_1);
    GUI_DispStringHCenterAt(buffer,LCD_GetXSize()/2,LCD_GetYSize() - GUI_GetFontSizeY()); // near the bottom
}

The main display function is the seconds which is displayed in the middle of the screen.  I get the time from the RTC in the PSoC and is set by the NTP Server.  Notice my rather serious hack to handle the Eastern time difference to UTC… which unfortunately only works in the Summer.

// updateDisplayTime
// This function updates the time on the screen
void updateDisplayTime()
{
  time_t rawtime;
  struct tm * timeinfo;
  char buffer [128];

  time (&rawtime);
  rawtime = rawtime - (4*60*60); // UTC - 4hours ... serious hack which only works in summer

  timeinfo = localtime (&rawtime);
  strftime (buffer,sizeof(buffer),"%r",timeinfo);
  GUI_SetFont(GUI_FONT_32B_1);
  GUI_DispStringHCenterAt(buffer,LCD_GetXSize()/2,LCD_GetYSize()/2 - GUI_GetFontSizeY()/2);
}

NTP Time Thread

The Network Time Protocol was invented in 1981 by Dr. David Mills for use in getting Internet connected computers to have the right time.  Since then it has been expanded a bunch of times to include Cellular, GPS and other networks.  The scheme includes methods for dealing with propogation delay etc.  However, for our purposes we will just ask one of the NIST computers, what time is it?

The way it works is that you setup a structure with 48 bytes in it.  Then you open a UDP connection to an NTP server (which NIST runs for you) then it will fill out the same structure with some time data and send it back to you.  Here is the packet:

typedef struct
{

  uint8_t li_vn_mode;      // Eight bits. li, vn, and mode.
                           // li.   Two bits.   Leap indicator.
                           // vn.   Three bits. Version number of the protocol.
                           // mode. Three bits. Client will pick mode 3 for client.

  uint8_t stratum;         // Eight bits. Stratum level of the local clock.
  uint8_t poll;            // Eight bits. Maximum interval between successive messages.
  uint8_t precision;       // Eight bits. Precision of the local clock.

  uint32_t rootDelay;      // 32 bits. Total round trip delay time.
  uint32_t rootDispersion; // 32 bits. Max error aloud from primary clock source.
  uint32_t refId;          // 32 bits. Reference clock identifier.

  uint32_t refTm_s;        // 32 bits. Reference time-stamp seconds.
  uint32_t refTm_f;        // 32 bits. Reference time-stamp fraction of a second.

  uint32_t origTm_s;       // 32 bits. Originate time-stamp seconds.
  uint32_t origTm_f;       // 32 bits. Originate time-stamp fraction of a second.

  uint32_t rxTm_s;         // 32 bits. Received time-stamp seconds.
  uint32_t rxTm_f;         // 32 bits. Received time-stamp fraction of a second.

  uint32_t txTm_s;         // 32 bits and the most important field the client cares about. Transmit time-stamp seconds.
  uint32_t txTm_f;         // 32 bits. Transmit time-stamp fraction of a second.

} __PACKED ntp_packet_t;              // Total: 384 bits or 48 bytes.

The code to send the packet is really simple.  The only trick is that when you send data on the network you almost always use big endian, so you need to use the function nthol to convert.

void NTPClient::set_server(char* server, int port) {
    nist_server_address = server;
    nist_server_port = port;
}

time_t NTPClient::get_timestamp(int timeout) {
    const time_t TIME1970 = (time_t)2208988800UL;
    int ntp_send_values[12] = {0};
    int ntp_recv_values[12] = {0};

    SocketAddress nist;

    if (iface) {
        int ret_gethostbyname = iface->gethostbyname(nist_server_address, &nist);

        if (ret_gethostbyname < 0) {
            // Network error on DNS lookup
            return ret_gethostbyname;
        }

        nist.set_port(nist_server_port);

        memset(ntp_send_values, 0x00, sizeof(ntp_send_values));
        ntp_send_values[0] = '\x1b';

        memset(ntp_recv_values, 0x00, sizeof(ntp_recv_values));

        UDPSocket sock;
        sock.open(iface);
        sock.set_timeout(timeout);

        sock.sendto(nist, (void*)ntp_send_values, sizeof(ntp_send_values));

        SocketAddress source;
        const int n = sock.recvfrom(&source, (void*)ntp_recv_values, sizeof(ntp_recv_values));

        if (n > 10) {
            return ntohl(ntp_recv_values[10]) - TIME1970;

The times in the structure are represented with two 32-bit numbers

  • # of seconds since 1/1/1900 (notice this is not 1970)
  • # of fractional seconds in 1/2^32 chunks (that ain’t a whole lotta time)

The four numbers are

  • Reference Time – when you last sent a packet
  • Origin Time – when you sent the packet (from your clock)
  • Receive Time – when the NTP server received your packet
  • Transmit Time – when your NTP server sent the packet back to you

You know when you send the packet – called T1.  You know when you received the packet – called T4.  You know when the other side received your packet – called T2 and you know when the other side sent the packet called T3.  With this information you can calculate the network delay, stability of the clocks etc.  However, the simplest thing to do is to take the transit time, which is in UTC, and set your clock assuming 0 delay.

In MBEDOS to set the RTC clock in the PSoC you call the function with the number of seconds since 1/1/1970.  Don’t forget that the time that comes back from NTP is in seconds since 1/1/1900.

                set_time(timestamp);

Given that the PSoC 6 RTC counts in seconds you can just ignore the partial seconds.

WiFi Semaphore

At the top of main I registered to WiFi that I want a callback when the state of the WiFi changes.

    wifi->attach(&wifiStatusCallback);

This function does two things.

  • Updates the screen as the state goes from unconnected to connected
  • Unlocks a semaphore to tell the main thread to reconnect.
// wifiStatusCallback
// Changes the display when the wifi status is changed
void wifiStatusCallback(nsapi_event_t status, intptr_t param)
{
    const int buffSize=40;
    char *statusText;
    statusText = (char *)malloc(buffSize);

    switch(param) {
        case NSAPI_STATUS_LOCAL_UP:
            snprintf(statusText,buffSize,"WiFi IP = %s",wifi->get_ip_address());
            break;
        case NSAPI_STATUS_GLOBAL_UP:
            snprintf(statusText,buffSize,"WiFi IP = %s",wifi->get_ip_address());
            break;
        case NSAPI_STATUS_DISCONNECTED:
            WiFiSemaphore.release();
            snprintf(statusText,buffSize,"WiFi Disconnected");
            break;
        case NSAPI_STATUS_CONNECTING:
            snprintf(statusText,buffSize,"WiFi Connecting");
            break;
        default:
            snprintf(statusText,buffSize,"Not Supported");
            break;
    }
    displayQueue->call(updateDisplayWiFiStatus,statusText);
}

The Whole Program

Here is the whole program.

#include "mbed.h"
#include "GUI.h"
#include "mbed_events.h"
#include "ntp-client/NTPClient.h"

Thread netTimeThreadHandle;

WiFiInterface *wifi;
EventQueue *displayQueue;
Semaphore WiFiSemaphore;

/******************************************************************************************
*
* Display Functions
*
********************************************************************************************/

#define DISP_LEFTMARGIN 10
#define DISP_TOPMARGIN 4
#define DISP_LINESPACE 2
// updateDisplayWiFiStatus
// Used to display the wifi status
void updateDisplayWiFiStatus(char *status)
{
    GUI_SetFont(GUI_FONT_16_1);
    GUI_DispStringAt(status,DISP_LEFTMARGIN, DISP_TOPMARGIN); 
    free(status);  
}

// updateDisplayWiFiConnectAttempts
// This function displays the number of attempted connections
void updateDisplayWiFiConnectAttempts(int count)
{
    char buffer[128];
    snprintf(buffer,sizeof(buffer),"WiFi Connect Attempts = %d",count); 
    GUI_SetFont(GUI_FONT_16_1);
    GUI_DispStringAt(buffer,DISP_LEFTMARGIN, DISP_TOPMARGIN + (GUI_GetFontSizeY()+DISP_LINESPACE) ); 
}

// updateDisplayNTPCount
// updates the display with the number of time the NTP Server has been called
void updateDisplayNTPCount(void)
{
    static int count=0;
    char buffer[128];
    count = count + 1;
    snprintf(buffer,sizeof(buffer),"NTP Updates = %d\n",count);
    GUI_SetFont(GUI_FONT_16_1);
    GUI_DispStringHCenterAt(buffer,LCD_GetXSize()/2,LCD_GetYSize() - GUI_GetFontSizeY()); // near the bottom
}

// updateDisplayTime
// This function updates the time on the screen
void updateDisplayTime()
{
  time_t rawtime;
  struct tm * timeinfo;
  char buffer [128];

  time (&rawtime);
  rawtime = rawtime - (4*60*60); // UTC - 4hours ... serious hack which only works in summer

  timeinfo = localtime (&rawtime);
  strftime (buffer,sizeof(buffer),"%r",timeinfo);
  GUI_SetFont(GUI_FONT_32B_1);
  GUI_DispStringHCenterAt(buffer,LCD_GetXSize()/2,LCD_GetYSize()/2 - GUI_GetFontSizeY()/2);
}

/******************************************************************************************
* NTPTimeThread
* This thread calls the NTP Timeserver to get the UTC time
* It then updates the time in the RTC
* And it updates the display by adding an event to the display queue
********************************************************************************************/
void NTPTimeThread()
{
    NTPClient ntpclient(wifi);

    while(1)
    {
        if(wifi->get_connection_status() == NSAPI_STATUS_GLOBAL_UP)
        {
            time_t timestamp = ntpclient.get_timestamp();
            if (timestamp < 0) {
                // probably need to do something different here
            } 
            else 
            {
                set_time(timestamp);
                displayQueue->call(updateDisplayNTPCount);
            }
        }
        wait(60.0*5); // Goto the NTP server every 5 minutes
    }
}

/******************************************************************************************
*
* Main & WiFi Thread
*
********************************************************************************************/

// wifiStatusCallback
// Changes the display when the wifi status is changed
void wifiStatusCallback(nsapi_event_t status, intptr_t param)
{
    const int buffSize=40;
    char *statusText;
    statusText = (char *)malloc(buffSize);

    switch(param) {
        case NSAPI_STATUS_LOCAL_UP:
            snprintf(statusText,buffSize,"WiFi IP = %s",wifi->get_ip_address());
            break;
        case NSAPI_STATUS_GLOBAL_UP:
            snprintf(statusText,buffSize,"WiFi IP = %s",wifi->get_ip_address());
            break;
        case NSAPI_STATUS_DISCONNECTED:
            WiFiSemaphore.release();
            snprintf(statusText,buffSize,"WiFi Disconnected");
            break;
        case NSAPI_STATUS_CONNECTING:
            snprintf(statusText,buffSize,"WiFi Connecting");
            break;
        default:
            snprintf(statusText,buffSize,"Not Supported");
            break;
    }
    displayQueue->call(updateDisplayWiFiStatus,statusText);
}


int main()
{
    int wifiConnectionAttempts;
    int ret;

    GUI_Init();
    displayQueue = mbed_event_queue();
    displayQueue->call_every(1000, &updateDisplayTime);

    wifi = WiFiInterface::get_default_instance();
    wifi->attach(&wifiStatusCallback);

   while(1)
   {
       wifiConnectionAttempts = 1;
        do {

            ret = wifi->connect(MBED_CONF_APP_WIFI_SSID, MBED_CONF_APP_WIFI_PASSWORD, NSAPI_SECURITY_WPA_WPA2);
            displayQueue->call(updateDisplayWiFiConnectAttempts,wifiConnectionAttempts);

            if (ret != 0) {
                wifiConnectionAttempts += 1;
                wait(2.0); // If for some reason it doesnt work wait 2s and try again
            }
        } while(ret !=0);

        // If the NTPThread is not running... then start it up
        if(netTimeThreadHandle.get_state() == Thread::Deleted)
            netTimeThreadHandle.start(NTPTimeThread);
 
        WiFiSemaphore.acquire();
   }
}