Mouser PSoC 6 WiFi/BT MBED: L4 Temperature Thread

IoT Design with Cypress PSoC® 6 MCUs and Wi-Fi/Bluetooth using Arm® Mbed™

# Lesson GitHub Project
0 Introduction
1 Developer Resources
2 Your First Project & The Blinking Thread https://github.com/iotexpert/mouser-mbed-02.git
3 Display Thread https://github.com/iotexpert/mouser-mbed-03.git
4 Temperature Thread https://github.com/iotexpert/mouser-mbed-04.git
5 CapSense Thread https://github.com/iotexpert/mouser-mbed-05.git
6 WiFi & NTP Thread https://github.com/iotexpert/mouser-mbed-06.git
7 The CY8CKIT-062-WiFi-BT https://github.com/iotexpert/mouser-mbed-07.git 
8 Amazon AWS MQTT Thread - Part1 https://github.com/iotexpert/mouser-mbed-08.git
9 Amazon AWS MQTT Thread - Part2 https://github.com/iotexpert/mouser-mbed-09.git

You can “mbed import https://github.com/iotexpert/mouser-mbed-09.git“ to make a copy of the project in your workspace.

The final architecture of the thermostat looks like this.

 

Summary

In Lesson 04 I will add temperature sensing to the system.

The new thread called “temperatureThread” acts as the actual guts of the thermostat.  It reads the temperature sensor, keeps track of the setPoint of the system and turns on and off Heat and Cooling.  Notice that it doesn’t know anything about displaying information,  it just uses the functions calls we created for the displayThread.

To implement this I will:

  1. Explain Thermistors
  2. Import lesson03
  3. Create and code temperatureThread.h
  4. Create and code temperatureThread.cpp
  5. Fix blinkThread.cpp
  6. Update main.cpp
  7. Build, Program and Test

Thermistor

On the wing of the CY8CPROTO-062-4343W there is a thermistor that you can use to measure temperature.   What is a thermistor?  It is simply a resistor that changes resistance in a known way based on temperature.

If you know the resistance you can calculate the temperature using the Steinhart-Hart equation.  Here is the Thermistor Wikipedia article.

When you look at the schematic for the development kit you can see how the circuit is attached to the PSoC.

Basically, a known 10K resistor in series with the thermistor with a measurement point between them.  To calculate the resistance of the thermistor you use Ohms low, V=IR.  Or even better R=V/I.

To use this circuit you need to assign a voltage to the THERM_VDD, use a DigialOut to drive 3.3v to that signal and a DigialOut to drive ground to the THERM_GND.

Then use the ADC to find the voltage for THERM_OUT

To do the calculation, first the Thermistor voltage is THERM_VDD-THERM_OUT.

Then you need to get the current.  To do this you calculate the current through the reference resistor I=V/R or I=(THERM_OUT/10K).  This will be the same current as the Thermistor.

Now you can calculate the thermistor resistance.

Here is a picture of the thermistor on the board.

Here is the code that implements the thermistor measurement.

static DigitalOut thermVDD(P10_3,1);
static DigitalOut thermGND(P10_0,0);
static AnalogIn thermOut(P10_1);

static void readTemp()
{
    float refVoltage = thermOut.read() * 2.4; // Range of ADC 0->2*Vref
    float refCurrent = refVoltage  / 10000.0; // 10k Reference Resistor
    float thermVoltage = 3.3 - refVoltage;    // Assume supply voltage is 3.3v
    float thermResistance = thermVoltage / refCurrent; 
    float logrT = (float32_t)log((float64_t)thermResistance);

    /* Calculate temperature from the resistance of thermistor using Steinhart-Hart Equation */
    float stEqn = (float32_t)((0.0009032679) + ((0.000248772) * logrT) + 
                             ((2.041094E-07) * pow((float64)logrT, (float32)3)));

    float temperatureC = (float32_t)(((1.0 / stEqn) - 273.15)  + 0.5);
    temperatureF = (temperatureC * 9.0/5.0) + 32;
}

Import Lesson 03

To build the project, start by importing Lesson 03:

https://github.com/iotexpert/mouser-mbed-03.git

Give it a new name of “mouser-mbed-04”:

Create and code temperatureThread.h

As before we want a thread for the temperature system called “temperatureThread.h”.  Create the “dot h”:

This file will just declare the thread and interface functions.

#ifndef TEMPERATURE_H
#define TEMPERATURE_H

void temperatureThread();

void tempSendUpdateSetpointF(float setPoint);
void tempSendDeltaSetpointF(float delta);


#endif

Create and code temperatureThread.cpp

Now we need the actual thread.  Make the new file “temperatureThread.cpp”

This thread will have two state variables.  One for temperatureF and one for the setPoint of the thermostat.

This thread will also have a queue just like the displayThread.  This queue will be used to let other parts of the system change the setPoint. One command to make a “delta” change  e.g. “go up 1 degree” and one command to make an absolute change e.g “set to 78 degrees”

#include "mbed.h"
#include "temperatureThread.h"
#include "displayThread.h"

static float temperatureF;
static float setPoint = 75.0;

static void readTemp();

typedef enum {
    CMD_setPointDelta,
    CMD_setPoint,

} command_t;


typedef struct {
    command_t cmd;
    float    value;   /* AD result of measured voltage */
} msg_t;

As before to make things easier for the other parts of the system to send messages to the queue, I create functions to send the two commands.

void tempSendDeltaSetpointF(float delta)
{
    msg_t *message = mpool.alloc();
    if(message)
    {
        message->cmd = CMD_setPointDelta;
        message->value = delta;
        queue.put(message);
    }
}

void tempSendUpdateSetpointF(float setPoint)
{
    msg_t *message = mpool.alloc();
    if(message)
    {
        message->cmd = CMD_setPoint;
        message->value = setPoint;
        queue.put(message);
    }
}

Now you need the function to read the temperature and update the state variable temperatureF

static DigitalOut thermVDD(P10_3,1);
static DigitalOut thermGND(P10_0,0);
static AnalogIn thermOut(P10_1);

static void readTemp()
{
    float refVoltage = thermOut.read() * 2.4; // Range of ADC 0->2*Vref
    float refCurrent = refVoltage  / 10000.0; // 10k Reference Resistor
    float thermVoltage = 3.3 - refVoltage;    // Assume supply voltage is 3.3v
    float thermResistance = thermVoltage / refCurrent; 
    float logrT = (float32_t)log((float64_t)thermResistance);

    /* Calculate temperature from the resistance of thermistor using Steinhart-Hart Equation */
    float stEqn = (float32_t)((0.0009032679) + ((0.000248772) * logrT) + 
                             ((2.041094E-07) * pow((float64)logrT, (float32)3)));

    float temperatureC = (float32_t)(((1.0 / stEqn) - 273.15)  + 0.5);
    temperatureF = (temperatureC * 9.0/5.0) + 32;
}

The temperatureThread listens for messages to be put into the queue.  It then processes them by either increment/decrement of the setPoint or by setting an absolute setPoint.

Also, the queue wait will timeout every 200ms and when that happens it will read the temperature and update the display.

It will also send the cool, heat or off based on a +- 0.5 degree difference from the setPoint.  In other words:

  • temperatureF > setPoint + 0.5 = cool
  • temperatureF < setPoint -0.5 = heat
  • Otherwise OFF
void temperatureThread()
{

    char buffer[128];
    displaySendUpdateTemp(temperatureF);
    displaySendUpdateSetPoint(setPoint);
    
    while(1)
    {
        osEvent evt = queue.get(200);
        if (evt.status == osEventMessage) {
            msg_t *message = (msg_t*)evt.value.p;
            switch(message->cmd)
            {
                case CMD_setPointDelta:
                    setPoint += message->value;
                    displaySendSetPoint(setPoint);
                break;
                case CMD_setPoint:
                    setPoint = message->value;
                    displaySendUpdateSetPoint(setPoint);
                break;

            }
            mpool.free(message);

        }
        else
        {
            readTemp();

            // Control the HVAC system with +- 0.5 degree of Hystersis
            if(temperatureF < setPoint - 0.5)
                displaySendUpdateMode(-1.0);
            else if (temperatureF > setPoint + 0.5)
                displaySendUpdateMode(1.0);
            else
                displaySendUpdateMode(0.0);

            displaySendUpdateTemp(temperatureF); 
        }
    }

}

The whole file (to make the copy/paste easier):

#include "mbed.h"
#include "temperatureThread.h"
#include "displayThread.h"

static float temperatureF;
static float setPoint = 75.0;

static void readTemp();

typedef enum {
    CMD_setPointDelta,
    CMD_setPoint,

} command_t;


typedef struct {
    command_t cmd;
    float    value;   /* AD result of measured voltage */
} msg_t;


static Queue<msg_t, 32> queue;
static MemoryPool<msg_t, 16> mpool;

void tempSendDeltaSetpointF(float delta)
{
    msg_t *message = mpool.alloc();
    if(message)
    {
        message->cmd = CMD_setPointDelta;
        message->value = delta;
        queue.put(message);
    }
}

void tempSendUpdateSetpointF(float setPoint)
{
    msg_t *message = mpool.alloc();
    if(message)
    {
        message->cmd = CMD_setPoint;
        message->value = setPoint;
        queue.put(message);
    }
}

void temperatureThread()
{

    char buffer[128];
    displaySendUpdateTemp(temperatureF);
    displaySendUpdateSetPoint(setPoint);
    
    while(1)
    {
        osEvent evt = queue.get(200);
        if (evt.status == osEventMessage) {
            msg_t *message = (msg_t*)evt.value.p;
            switch(message->cmd)
            {
                case CMD_setPointDelta:
                    setPoint += message->value;
                    displaySendUpdateSetPoint(setPoint);
                break;
                case CMD_setPoint:
                    setPoint = message->value;
                    displaySendUpdateSetPoint(setPoint);
                break;

            }
            mpool.free(message);

        }
        else
        {
            readTemp();

            // Control the HVAC system with +- 0.5 degree of Hystersis
            if(temperatureF < setPoint - 0.5)
                displaySendUpdateMode(-1.0);
            else if (temperatureF > setPoint + 0.5)
                displaySendUpdateMode(1.0);
            else
                displaySendUpdateMode(0.0);

            displaySendUpdateTemp(temperatureF); 
        }
    }

}


static DigitalOut thermVDD(P10_3,1);
static DigitalOut thermGND(P10_0,0);
static AnalogIn thermOut(P10_1);

static void readTemp()
{
    float refVoltage = thermOut.read() * 2.4; // Range of ADC 0->2*Vref
    float refCurrent = refVoltage  / 10000.0; // 10k Reference Resistor
    float thermVoltage = 3.3 - refVoltage;    // Assume supply voltage is 3.3v
    float thermResistance = thermVoltage / refCurrent; 
    float logrT = (float32_t)log((float64_t)thermResistance);

    /* Calculate temperature from the resistance of thermistor using Steinhart-Hart Equation */
    float stEqn = (float32_t)((0.0009032679) + ((0.000248772) * logrT) + 
                             ((2.041094E-07) * pow((float64)logrT, (float32)3)));

    float temperatureC = (float32_t)(((1.0 / stEqn) - 273.15)  + 0.5);
    temperatureF = (temperatureC * 9.0/5.0) + 32;
}

Fix blinkThread.cpp

Now edit the blinkThread.cpp to remove the test code from Lesson 03:

#include "mbed.h"
#include "blinkThread.h"
#include "displayThread.h"

static DigitalOut led1(LED1);

void blinkThread()
{
 
    while (true) 
    {
        led1 = !led1;
        displaySendUpdateTime();
        ThisThread::sleep_for(500);

    }
}

Update main.cpp

Then update main.cpp to start the new temperatureThread:

#include "mbed.h"
#include "blinkThread.h"
#include "displayThread.h"
#include "temperatureThread.h"

Thread blinkThreadHandle;
Thread displayThreadHandle;
Thread temperatureThreadHandle;

int main()
{

    printf("Started System\n");

    blinkThreadHandle.start(blinkThread);
    displayThreadHandle.start(displayThread);
    temperatureThreadHandle.start(temperatureThread);

}

Build, Program and Test

When you program the development kit you should be able to change the temperature by putting your finger on the thermistor:

And your terminal should look something like this:

Mouser PSoC 6 WiFi/BT MBED: L6 WiFi & NTP Thread

IoT Design with Cypress PSoC® 6 MCUs and Wi-Fi/Bluetooth using Arm® Mbed™

# Lesson GitHub Project
0 Introduction
1 Developer Resources
2 Your First Project & The Blinking Thread https://github.com/iotexpert/mouser-mbed-02.git
3 Display Thread https://github.com/iotexpert/mouser-mbed-03.git
4 Temperature Thread https://github.com/iotexpert/mouser-mbed-04.git
5 CapSense Thread https://github.com/iotexpert/mouser-mbed-05.git
6 WiFi & NTP Thread https://github.com/iotexpert/mouser-mbed-06.git
7 The CY8CKIT-062-WiFi-BT https://github.com/iotexpert/mouser-mbed-07.git 
8 Amazon AWS MQTT Thread - Part1 https://github.com/iotexpert/mouser-mbed-08.git
9 Amazon AWS MQTT Thread - Part2 https://github.com/iotexpert/mouser-mbed-09.git

You can “mbed import https://github.com/iotexpert/mouser-mbed-09.git“ to make a copy of the project in your workspace.

The final architecture of the thermostat looks like this.

 

Summary

I don’t know about you guys, but it annoys me every time I see that the clock isn’t set.  In this lesson we will start the IoT-ifying of this system by attaching it to WiFi and getting the network time.

Inside of the PSoC is a RealTime clock that is driven by the crystal oscillator on the board.  However, “What time is it?”  turns out to be a pretty simple question to answer if you are attached to the network.  You find out using an NTP server.  This lesson will attach to WiFi, and then every 5 minutes go get the UTC time from an NTP server.

To implement this I will

  1. Import lesson05
  2. Add the NTP Server Library
  3. Create & Code ntpThread.h
  4. Create & Code ntpThread.cpp
  5. Update main.cpp
  6. Build, Program and Test

Import lesson05

First import the Lesson 05 to create a new project.

https://github.com/iotexpert/mouser-mbed-05.git

Add the NTP Server Library

For this lesson I will use a library that knows how to talk to an NTP server using a TCP socket.  To get this library click on the libraries tab and press “+”

Now provide the path to the library.

https://github.com/ARMmbed/ntp-client

Use the master branch:

Create & Code ntpThread.h

As always I am going to run the NTP Client in a thread.  This thread will be called “ntpThread”:

There is nothing to this but the function definition:

#ifndef NTP_THREAD_H
#define NTP_THREAD_H
void ntpThread();

#endif

Create & Code ntpThread.cpp

Next create the actual thread code:

This code will need to include the ntp-client library.  Then I do something semi-evil by making an external reference to the WiFiInterface which will be declared in main.cpp.

Finally I will poll the NTP server every 5 minutes.  Notice that if the thing fails it tries again in 10 seconds.  When I get the time I write it into the RTC using the standard-C set_time function.  This function was connected by Cypress to the RTC hardware in the PSoC 6.

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

extern WiFiInterface *wifi;


void ntpThread()
{
    NTPClient ntpclient(wifi);
 
    uint32_t sleepTime = 1000 * 60 * 5 ; // 5 minutes
    while(1)
    {

        if(wifi->get_connection_status() == NSAPI_STATUS_GLOBAL_UP)
        {
            time_t timestamp = ntpclient.get_timestamp();
            if (timestamp < 0) {
                sleepTime = 1000 * 10 ; // 10 seconds
            } 
            else 
            {
                set_time(timestamp);
                sleepTime = 1000 * 60 * 5 ; // 5 minutes

            }
        }
        ThisThread::sleep_for(sleepTime); // Goto the NTP server every 5 minutes
    }
}

Update main.cpp

Now I need to update main.cpp.  This time is a little bit different since I need to setup a connection to the WiFi network.  I start by getting a pointer to the WiFi in the system and then connecting before I start all of the threads.

Notice that I hard-coded the SSID and Password.  I also try again until I have a WiFi connection.

The rest of main.cpp is normal.

#include "mbed.h"
#include "blinkThread.h"
#include "displayThread.h"
#include "temperatureThread.h"
#include "capsenseThread.h"
#include "ntpThread.h"

Thread blinkThreadHandle;
Thread displayThreadHandle;
Thread temperatureThreadHandle;
Thread capsenseThreadHandle;
Thread ntpThreadHandle;

WiFiInterface *wifi;

int main()
{

    printf("Started System\n");

    int ret;
    wifi = WiFiInterface::get_default_instance();

    do {
            ret = wifi->connect("CYFI_IOT_EXT", "cypresswicedwifi101", NSAPI_SECURITY_WPA_WPA2);
            if (ret != 0) {
            ThisThread::sleep_for(2000); // If for some reason it doesnt work wait 2s and try again
            }
    } while(ret !=0);


    ntpThreadHandle.start(ntpThread);
    blinkThreadHandle.start(blinkThread);
    displayThreadHandle.start(displayThread);
    temperatureThreadHandle.start(temperatureThread);
    capsenseThreadHandle.start(capsenseThread);
    
}

Build, Program and Test

Now when I program this version, after 10 seconds or so, my time is updated… how cool is that?

Mouser PSoC 6 WiFi/BT MBED: L5 CapSense Thread

IoT Design with Cypress PSoC® 6 MCUs and Wi-Fi/Bluetooth using Arm® Mbed™

# Lesson GitHub Project
0 Introduction
1 Developer Resources
2 Your First Project & The Blinking Thread https://github.com/iotexpert/mouser-mbed-02.git
3 Display Thread https://github.com/iotexpert/mouser-mbed-03.git
4 Temperature Thread https://github.com/iotexpert/mouser-mbed-04.git
5 CapSense Thread https://github.com/iotexpert/mouser-mbed-05.git
6 WiFi & NTP Thread https://github.com/iotexpert/mouser-mbed-06.git
7 The CY8CKIT-062-WiFi-BT https://github.com/iotexpert/mouser-mbed-07.git 
8 Amazon AWS MQTT Thread - Part1 https://github.com/iotexpert/mouser-mbed-08.git
9 Amazon AWS MQTT Thread - Part2 https://github.com/iotexpert/mouser-mbed-09.git

You can “mbed import https://github.com/iotexpert/mouser-mbed-09.git“ to make a copy of the project in your workspace.

The final architecture of the thermostat looks like this.

 

Summary

In Lesson 05 we will add a CapSense GUI to enable the user to change the setPoint of the thermostat.

Specifically I will create a new Thread called “CapSense Thread” which will read the state of the CapSense Buttons and send messages to the temperatureThread.

To implement this I will:

  1. Import Lesson 04
  2. Add the Cypress CapSense library
  3. Run the CapsSense configurator
  4. Create and code capsenseThread.h
  5. Create and code capsenseThread.cpp
  6. Update main.cpp
  7. Build, Program and Test

Import Lesson 04

First import Lesson 04 and call your new project “mouser-mbed-05”

https://github.com/iotexpert/mouser-mbed-04.git

Add the Cypress CapSense library

In order to use the CapSense library you will need to import it from the Cypress GitHub site.  To do this we will use the Mbed Studio Library manager.  Click on the “Libraries” tab. Then press the “+” button to add a new library.

Give the Library manager the URL to the Cypress CapSense library:

https://github.com/cypresssemiconductorco/capsense.git

Specify that you want your library to point to the master branch.

Once that is done your library screen should look something like this.  When I took this screen shot I noticed that I was on the old version of mbed-os so I pressed the update button.

Now both libraries are at the latest version.

Run the CapSense Configurator

First, lets run the CapSense configurator to look at the CapSense configuration.  You can find it in ModusToolbox/tools_2.0/capsense_configurator. Then use file>open to open the Target’s CapSense configuration file from targets/TARGET_Cypress/TARGET_PSOC6/TARGET_CY8PROTO_062_4343w/COMPONENT_BSP_DESIGN_MODUS/design.cycapsense.

Here is the advanced tab:

The BSP/Target as delivered inside of Mbed OS does not have the generated source code required to run CapSense.  I could press save in the CapSense configurator GUI, but this would modify my version of Mbed OS.  I would rather add these files to the make project.  In order to get this code into my project I run the CapSense Configurator from the command line and tell to save the files locally.

  • /Applications/ModusToolbox/tools_2.0/capsense-configurator/capsense-configurator-cli -c mbed-os/targets/TARGET_Cypress/TARGET_PSOC6/TARGET_CY8CKIT_062_WIFI_BT/COMPONENT_BSP_DESIGN_MODUS/design.cycapsense -o `pwd` -g

Here is what the output looks like:

Create and Code capsenseThread.h

As with all of the other lessons, I want a new thread called “capsenseThread”  Start by making the “dot h”

This will only have the capsenseThread function.

#ifndef CAPSENSE_THREAD_H
#define CAPSENSE_THREAD_H

void capsenseThread(void);

#endif

Create and code capsenseThread.cpp

Now make the cpp file.

The Cypress CapSense system is a combination of hardware and firmware.  It works by:

  1. Initializing the Hardware
  2. Initializing the Interrupt
  3. Initializing the callback
  4. Starting a scan (run the hardware block)
  5. Processing the raw data when the scan is done and send a message
  6. Starting another Scan

I got the basis for this code by using the CapSense example project that Cypress has posted on GitHub. You can find that code example at:

github.com/cypresssemiconductorco/mtb-example-psoc6-capsense-buttons-slider

Initializing the Hardware

    /* Configure AMUX bus for CapSense */
    init_cycfg_routing();
    /* Configure PERI clocks for CapSense */
    Cy_SysClk_PeriphAssignDivider(PCLK_CSD_CLOCK, CYBSP_CSD_CLK_DIV_HW, CYBSP_CSD_CLK_DIV_NUM);
    Cy_SysClk_PeriphDisableDivider(CYBSP_CSD_CLK_DIV_HW, CYBSP_CSD_CLK_DIV_NUM);
    Cy_SysClk_PeriphSetDivider(CYBSP_CSD_CLK_DIV_HW, CYBSP_CSD_CLK_DIV_NUM, 0u);
    Cy_SysClk_PeriphEnableDivider(CYBSP_CSD_CLK_DIV_HW, CYBSP_CSD_CLK_DIV_NUM);
    

    /* Initialize the CSD HW block to the default state. */
    Cy_CapSense_Init(&cy_capsense_context);

Initializing the Interrupt

This sets up the CapSense interrupt to work with the hardware block

    const cy_stc_sysint_t CapSense_ISR_cfg =
    {
        .intrSrc = CYBSP_CSD_IRQ,
        .intrPriority = 4u
    };
    /* Initialize CapSense interrupt */
    Cy_SysInt_Init(&CapSense_ISR_cfg, &CapSense_InterruptHandler);
    NVIC_ClearPendingIRQ(CapSense_ISR_cfg.intrSrc);
    NVIC_EnableIRQ(CapSense_ISR_cfg.intrSrc);

Initializing the callback

The callback is used to set the semaphore when the scan is done.

    /* Initialize the CapSense firmware modules. */
    Cy_CapSense_Enable(&cy_capsense_context);
    Cy_CapSense_RegisterCallback(CY_CAPSENSE_END_OF_SCAN_E, CapSenseEndOfScanCallback, &cy_capsense_context);

Starting a scan (run the hardware block)

    Cy_CapSense_ScanAllWidgets(&cy_capsense_context);  // Launch the initial scan   

Processing Raw Data when the scan completes, and Send Message

        if (CY_CAPSENSE_NOT_BUSY == Cy_CapSense_IsBusy(&cy_capsense_context))
        {
            Cy_CapSense_ProcessAllWidgets(&cy_capsense_context);
            uint32_t currBtn0Status = Cy_CapSense_IsSensorActive(CY_CAPSENSE_BUTTON0_WDGT_ID, CY_CAPSENSE_BUTTON0_SNS0_ID, &cy_capsense_context);        
            uint32_t currBtn1Status = Cy_CapSense_IsSensorActive(CY_CAPSENSE_BUTTON1_WDGT_ID, CY_CAPSENSE_BUTTON1_SNS0_ID, &cy_capsense_context);       

            if(currBtn0Status != prevBtn0Status && currBtn0Status==1 )
            {
                tempSendDeltaSetpointF(-0.1);
            }
            prevBtn0Status = currBtn0Status;
    
            if(currBtn1Status != prevBtn1Status && currBtn1Status == 1)
            {
                tempSendDeltaSetpointF(0.1);
            } 
            prevBtn1Status = currBtn1Status;

             
        } 

Starting another Scan

Cy_CapSense_ScanAllWidgets(&cy_capsense_context);

Here is the whole file:

#include "mbed.h"
#include "cy_pdl.h"
#include "cycfg_capsense.h"
#include "cycfg.h"
#include "temperatureThread.h"

static Semaphore capsense_sem;


/*****************************************************************************
* Function Name: CapSense_InterruptHandler()
******************************************************************************
* Summary:
*  Wrapper function for handling interrupts from CSD block.
*
*****************************************************************************/
static void CapSense_InterruptHandler(void)
{
    Cy_CapSense_InterruptHandler(CYBSP_CSD_HW, &cy_capsense_context);
}


/*****************************************************************************
* Function Name: CapSenseEndOfScanCallback()
******************************************************************************
* Summary:
*  This function releases a semaphore to indicate end of a CapSense scan.
*
* Parameters:
*  cy_stc_active_scan_sns_t* : pointer to active sensor details.
*
*****************************************************************************/
static void CapSenseEndOfScanCallback(cy_stc_active_scan_sns_t * ptrActiveScan)
{
    capsense_sem.release();
}


void capsenseThread(void)
{

    /* Configure AMUX bus for CapSense */
    init_cycfg_routing();
    /* Configure PERI clocks for CapSense */
    Cy_SysClk_PeriphAssignDivider(PCLK_CSD_CLOCK, CYBSP_CSD_CLK_DIV_HW, CYBSP_CSD_CLK_DIV_NUM);
    Cy_SysClk_PeriphDisableDivider(CYBSP_CSD_CLK_DIV_HW, CYBSP_CSD_CLK_DIV_NUM);
    Cy_SysClk_PeriphSetDivider(CYBSP_CSD_CLK_DIV_HW, CYBSP_CSD_CLK_DIV_NUM, 0u);
    Cy_SysClk_PeriphEnableDivider(CYBSP_CSD_CLK_DIV_HW, CYBSP_CSD_CLK_DIV_NUM);
    

    /* Initialize the CSD HW block to the default state. */
    Cy_CapSense_Init(&cy_capsense_context);

    const cy_stc_sysint_t CapSense_ISR_cfg =
    {
        .intrSrc = CYBSP_CSD_IRQ,
        .intrPriority = 4u
    };
    /* Initialize CapSense interrupt */
    Cy_SysInt_Init(&CapSense_ISR_cfg, &CapSense_InterruptHandler);
    NVIC_ClearPendingIRQ(CapSense_ISR_cfg.intrSrc);
    NVIC_EnableIRQ(CapSense_ISR_cfg.intrSrc);

    /* Initialize the CapSense firmware modules. */
    Cy_CapSense_Enable(&cy_capsense_context);
    Cy_CapSense_RegisterCallback(CY_CAPSENSE_END_OF_SCAN_E, CapSenseEndOfScanCallback, &cy_capsense_context);

    Cy_CapSense_ScanAllWidgets(&cy_capsense_context);  // Launch the initial scan   
    
    uint32_t prevBtn0Status = 0u; 
    uint32_t prevBtn1Status = 0u;
     
    while(1)
    {

        capsense_sem.acquire();
        if (CY_CAPSENSE_NOT_BUSY == Cy_CapSense_IsBusy(&cy_capsense_context))
        {
            Cy_CapSense_ProcessAllWidgets(&cy_capsense_context);
            uint32_t currBtn0Status = Cy_CapSense_IsSensorActive(CY_CAPSENSE_BUTTON0_WDGT_ID, CY_CAPSENSE_BUTTON0_SNS0_ID, &cy_capsense_context);        
            uint32_t currBtn1Status = Cy_CapSense_IsSensorActive(CY_CAPSENSE_BUTTON1_WDGT_ID, CY_CAPSENSE_BUTTON1_SNS0_ID, &cy_capsense_context);       

            if(currBtn0Status != prevBtn0Status && currBtn0Status==1 )
            {
                tempSendDeltaSetpointF(-0.1);
            }
            prevBtn0Status = currBtn0Status;
    
            if(currBtn1Status != prevBtn1Status && currBtn1Status == 1)
            {
                tempSendDeltaSetpointF(0.1);
            } 
            prevBtn1Status = currBtn1Status;

            Cy_CapSense_ScanAllWidgets(&cy_capsense_context);  
        } 

    }
}

Update main.cpp

Inside of the main.cpp I will make the usual changes to turn on the CapSense thread.

#include "mbed.h"
#include "blinkThread.h"
#include "displayThread.h"
#include "temperatureThread.h"
#include "capsenseThread.h"

Thread blinkThreadHandle;
Thread displayThreadHandle;
Thread temperatureThreadHandle;
Thread capsenseThreadHandle;

int main()
{

    printf("Started System\n");

    blinkThreadHandle.start(blinkThread);
    displayThreadHandle.start(displayThread);
    temperatureThreadHandle.start(temperatureThread);
    capsenseThreadHandle.start(capsenseThread);

}

Build, Program and Test

When I build, program and test I can see that by pressing the two CapSense Buttons I can raise and lower the setPoint.

Mouser PSoC 6 WiFi/BT MBED: L7 The CY8CKIT-062-WiFi-BT

IoT Design with Cypress PSoC® 6 MCUs and Wi-Fi/Bluetooth using Arm® Mbed™

# Lesson GitHub Project
0 Introduction
1 Developer Resources
2 Your First Project & The Blinking Thread https://github.com/iotexpert/mouser-mbed-02.git
3 Display Thread https://github.com/iotexpert/mouser-mbed-03.git
4 Temperature Thread https://github.com/iotexpert/mouser-mbed-04.git
5 CapSense Thread https://github.com/iotexpert/mouser-mbed-05.git
6 WiFi & NTP Thread https://github.com/iotexpert/mouser-mbed-06.git
7 The CY8CKIT-062-WiFi-BT https://github.com/iotexpert/mouser-mbed-07.git 
8 Amazon AWS MQTT Thread - Part1 https://github.com/iotexpert/mouser-mbed-08.git
9 Amazon AWS MQTT Thread - Part2 https://github.com/iotexpert/mouser-mbed-09.git

You can “mbed import https://github.com/iotexpert/mouser-mbed-09.git“ to make a copy of the project in your workspace.

The final architecture of the thermostat looks like this.

 

Summary

This weekend as I was getting ready for the workshop, my son, Nicholas, was making fun of me for my user interface.  To fix this I decided to show how you could move platforms.  Specifically to the CY8CKIT-062-WiFi-BT which you can buy from mouser.com

You could literally just change the target and things would work… almost.  The one exception is that this development kit doesn’t have a thermistor.  So, I soldered a different kind of temperature sensor, called a TMP36, onto the kit which means I have to make a few small code updates to the temperatureThread.

In addition to changing to the TMP36 I will also use the TFT as a display for the system, so I need to modify the displayThread.

One of the major points that I wanted to make with this lesson is that if you design your code correctly, a change like this isn’t very hard.

To implement this I will:

  1. Provide a TMP36 Temperature Sensor Tutorial
  2. Modify the CY8CKIT-028-TFT
  3. Import Lesson06
  4. Update temperatureThread.cpp
  5. Build, Program and Test
  6. Add Segger emWin Library
  7. Add IoTExpert CY8CKIT-028-TFT configuration
  8. Update mbed_app.json to include driver
  9. Update displayThread.cpp
  10. Build, Program and Test

TMP36 Temperature Sensor

The TMP36 is a single wire temperature sensor that gives you a voltage that is directly proportional to the temperature.  Specifically, it is 10mV per degree C.

The Analog Devices TMP36 that I am using I bought here from mouser.com.  It looks like a little 3-wire transistor looking thing.

Here is a snapshot from the data sheet of the temperature voltage response

Yes I know that I am a digital guy, but for just three wires, signal, ground and power, it sure looks like there is a lot going on in the package.

The data sheet (which is actually really interesting)  gives you the parameters to make the linear equation for temperature

To calculate the temperature you just calculate:

T = (degree C/10mV) * V – 50 degree C

Here is the function to read the temperature:

static AnalogIn tmp36(A5);
static void readTemp()
{
    float volts;
    
    volts = tmp36.read() * 2.4;
    float temperatureC = 1/.01 * volts - 50.0;
    temperatureF = (temperatureC * 9.0/5.0) + 32;
}

Modify the CY8CKIT-028-TFT

Start this process by attaching the TMP36.  On the back of the CY8CKIT-028-TFT I solder three wires onto the temperature sensor.  One to 3.3v, one to ground and one to A5.  Here is the picture of my ugly ugly solder job.  Yes, that is duct-tape holding it onto the back of the shield.  And yes that is a prototype sticker.

I also remove the PDM microphone which is also attached to A5 (I’m not sure that it would cause a problem… but better safe than sorry)

Import Lesson06

Now, fix the code.  As always, start by importing the previous lesson.

https://github.com/iotexpert/mouser-mbed-06.git

Update temperatureThread.cpp

In order to switch between the thermistor and the TMP36 I create two #defines based on the target that is currently active.  This is cool because the Mbed OS creates these #defines automatically.

#ifdef TARGET_CY8CPROTO_062_4343W
#define THERMISTOR
#endif

#ifdef TARGET_CY8CKIT_062_WIFI_BT
#define TMP36
#endif

There are a bunch of ways that could have been done, including with the Mbed configuration system which you control with the mbed_app.json.  But this was expedient.

Next, I create the code to measure the temperature using the AnalogIn object. Notice that it is connected to the Arduino A5 pin. Also notice that when the TMP36 is active I have a different readTemp function so I don’t need to change anything else to get the temperature.

#ifdef TMP36
static AnalogIn tmp36(A5);
static void readTemp()
{
    float volts;
    
    volts = tmp36.read() * 2.4;
    float temperatureC = 1/.01 * volts - 50.0; // from the data sheet
    temperatureF = (temperatureC * 9.0/5.0) + 32; // conver to F
}
#endif

Then I update the thermistor readTemp function with the THERMISTOR #define

#ifdef THERMISTOR
static DigitalOut thermVDD(P10_3,1);
static DigitalOut thermGND(P10_0,0);
static AnalogIn thermOut(P10_1);

static void readTemp()
{
    float refVoltage = thermOut.read() * 2.4; // Range of ADC 0->2*Vref
    float refCurrent = refVoltage  / 10000.0; // 10k Reference Resistor
    float thermVoltage = 3.3 - refVoltage;    // Assume supply voltage is 3.3v
    float thermResistance = thermVoltage / refCurrent; 
    float logrT = (float32_t)log((float64_t)thermResistance);

    /* Calculate temperature from the resistance of thermistor using Steinhart-Hart Equation */
    float stEqn = (float32_t)((0.0009032679) + ((0.000248772) * logrT) + 
                             ((2.041094E-07) * pow((float64)logrT, (float32)3)));

    float temperatureC = (float32_t)(((1.0 / stEqn) - 273.15)  + 0.5);
    temperatureF = (temperatureC * 9.0/5.0) + 32;
}
#endif

Here is the whole file:

#include "mbed.h"
#include "temperatureThread.h"
#include "displayThread.h"


#ifdef TARGET_CY8CPROTO_062_4343W
#define THERMISTOR
#endif

#ifdef TARGET_CY8CKIT_062_WIFI_BT
#define TMP36
#endif


static float temperatureF;
static float setPoint = 75.0;

static void readTemp();

typedef enum {
    CMD_setPointDelta,
    CMD_setPoint,

} command_t;


typedef struct {
    command_t cmd;
    float    value;   /* AD result of measured voltage */
} msg_t;


static Queue<msg_t, 32> queue;
static MemoryPool<msg_t, 16> mpool;

void tempSendDeltaSetpointF(float delta)
{
    msg_t *message = mpool.alloc();
    if(message)
    {
        message->cmd = CMD_setPointDelta;
        message->value = delta;
        queue.put(message);
    }
}

void tempSendUpdateCurrentSetPointF(float setPoint)
{
    msg_t *message = mpool.alloc();
    if(message)
    {
        message->cmd = CMD_setPoint;
        message->value = setPoint;
        queue.put(message);
    }
}

void temperatureThread()
{

    char buffer[128];
    displaySendUpdateTemp(temperatureF);
    displaySendUpdateSetPoint(setPoint);
    
    while(1)
    {
        osEvent evt = queue.get(200);
        if (evt.status == osEventMessage) {
            msg_t *message = (msg_t*)evt.value.p;
            switch(message->cmd)
            {
                case CMD_setPointDelta:
                    setPoint += message->value;
                    displaySendUpdateSetPoint(setPoint);
                break;
                case CMD_setPoint:
                    setPoint = message->value;
                    displaySendUpdateSetPoint(setPoint);
                break;

            }
            mpool.free(message);

        }
        else
        {
            readTemp();

            // Control the HVAC system with +- 0.5 degree of Hystersis
            if(temperatureF < setPoint - 0.5)
                displaySendUpdateMode(-1.0);
            else if (temperatureF > setPoint + 0.5)
                displaySendUpdateMode(1.0);
            else
                displaySendUpdateMode(0.0);

            displaySendUpdateTemp(temperatureF);
        
        }
    }

}


#ifdef THERMISTOR
static DigitalOut thermVDD(P10_3,1);
static DigitalOut thermGND(P10_0,0);
static AnalogIn thermOut(P10_1);

static void readTemp()
{
    float refVoltage = thermOut.read() * 2.4; // Range of ADC 0->2*Vref
    float refCurrent = refVoltage  / 10000.0; // 10k Reference Resistor
    float thermVoltage = 3.3 - refVoltage;    // Assume supply voltage is 3.3v
    float thermResistance = thermVoltage / refCurrent; 
    float logrT = (float32_t)log((float64_t)thermResistance);

    /* Calculate temperature from the resistance of thermistor using Steinhart-Hart Equation */
    float stEqn = (float32_t)((0.0009032679) + ((0.000248772) * logrT) + 
                             ((2.041094E-07) * pow((float64)logrT, (float32)3)));

    float temperatureC = (float32_t)(((1.0 / stEqn) - 273.15)  + 0.5);
    temperatureF = (temperatureC * 9.0/5.0) + 32;
}
#endif

#ifdef TMP36
static AnalogIn tmp36(A5);
static void readTemp()
{
    float volts;
    
    volts = tmp36.read() * 2.4;
    float temperatureC = 1/.01 * volts - 50.0;
    temperatureF = (temperatureC * 9.0/5.0) + 32;
}
#endif

Build, Program and Test

With that little update I can now build, program and test. Notice that my display is still working… and I can change the temperature by holding the TMP36 between my fingers.

Add Segger emWin Library

But, I haven’t really addressed Nicholas’ complaint, the GUI.  To do this I am going to use the Segger emWin library to control the TFT display.  Go to the library manager and add the library.

https://github.com/cypresssemiconductorco/emwin.git

Use the master branch:

Add IoTExpert CY8CKIT-028-TFT configuration

The emWin library doesn’t know anything about how the TFT is attached.  So, I created a driver library which you can use for all of the Cypress development kits.

https://github.com/iotexpert/mbed-os-emwin-st7789v.git

Use the master branch:

Now your libraries should look like this.

Update mbed_app.json to include emWin Driver

In order to use this driver you need to tell Mbed OS to add the correct archive file.  To do this update mbed_app.json

{
    "target_overrides": {
        "*": {
            "target.components_add": ["EMWIN_OSNTS"],
            "platform.stdio-baud-rate": 115200
        }
    }
}

Update displayThread.cpp

Finally update the displayThread.  Start by adding the include for the Segger library.

#ifdef TARGET_CY8CKIT_062_WIFI_BT
#include "GUI.h"
#endif

Then update the displayAtXY function to also print on the display.  By doing it here I don’t have to change anything else in the code because this is the only function that draws on the screen.

static void displayAtXY(int x, int y,char *buffer)
{
    #ifdef TARGET_CY8CKIT_062_WIFI_BT
    GUI_SetTextAlign(GUI_TA_LEFT);
    GUI_DispStringAt(buffer,(x-1)*8,(y-1)*16); // 8x16 font
    #endif
    // row column
    printf("3[%d;%dH%s",y,x,buffer);
    fflush(stdout);
}

Update the main thread to initialize the display when the thread starts.

void displayThread()
{
    char buffer[128];

    printf("3[2J3[H"); // Clear Screen and go Home
    printf("3[?25l"); // Turn the cursor off
    fflush(stdout);

    #ifdef TARGET_CY8CKIT_062_WIFI_BT
        GUI_Init();
        GUI_SetColor(GUI_WHITE);
        GUI_SetBkColor(GUI_BLACK);
        GUI_SetFont(GUI_FONT_8X16_1);
    #endif

Build, Program and Test

When you program, now you should have a TFT display.  Happy Nicholas?

Mouser PSoC 6 WiFi/BT MBED: L8 Amazon AWS MQTT Thread – Part1

IoT Design with Cypress PSoC® 6 MCUs and Wi-Fi/Bluetooth using Arm® Mbed™

# Lesson GitHub Project
0 Introduction
1 Developer Resources
2 Your First Project & The Blinking Thread https://github.com/iotexpert/mouser-mbed-02.git
3 Display Thread https://github.com/iotexpert/mouser-mbed-03.git
4 Temperature Thread https://github.com/iotexpert/mouser-mbed-04.git
5 CapSense Thread https://github.com/iotexpert/mouser-mbed-05.git
6 WiFi & NTP Thread https://github.com/iotexpert/mouser-mbed-06.git
7 The CY8CKIT-062-WiFi-BT https://github.com/iotexpert/mouser-mbed-07.git 
8 Amazon AWS MQTT Thread - Part1 https://github.com/iotexpert/mouser-mbed-08.git
9 Amazon AWS MQTT Thread - Part2 https://github.com/iotexpert/mouser-mbed-09.git

You can “mbed import https://github.com/iotexpert/mouser-mbed-09.git“ to make a copy of the project in your workspace.

The final architecture of the thermostat looks like this.

 

Summary

In this lesson we will plumb in the ability to talk to the Amazon Web Services MQTT broker.  Specifically to publish messages from the broker to change the setPoint of the thermostat.

To implement this I will:

  1. Discuss MQTT
  2. Import Lesson 07
  3. Add the Cypress AWS IoT Middleware
  4. Add the Cypress Connectivity Utilities Library
  5. Create an AWS Policy, Thing, and Certificate
  6. Modify the keys to “C” format
  7. Create and Code awsThread.h
  8. Create and Code awsThread.cpp
  9. Update main.cpp
  10. Build, Program and Test

MQTT

There are five basic ideas that you need to understand when using MQTT.

  • (Message) Broker
  • Topic
  • Subscriber
  • Publisher
  • Messages

The Broker is a TCP/IP server sitting at the middle of an MQTT network.  It is responsible for receiving Messages from Publishers and forwarding them on to Subscribers.  A broker can run multiple simultaneous communication channels called Topics.

Topics are ad-hoc (application semantics) and are just a string of characters … like “setPoint” or “thing/setPoint”

Messages are just a string of bytes.  Typically formatted as JSON.  This is by convention and is not a requirement.

MQTT clients can be Publishers or Subscribers or both.

Import Lesson 07

Start by importing Lesson 07.

https://github.com/iotexpert/mouser-mbed-07.git


Add the Cypress AWS IoT Middleware

Cypress has built a library which will help you manage connections to AWS.  We call it the aws IoT middleware library.  Go to the library manager and add it:

  • https://github.com/cypresssemiconductorco/aws-iot.git

Use the master branch:

Add the Cypress Connectivity Utilities Library

The AWS IoT library needs to use another library of connectivity functions.  Add it:

  • https://github.com/cypresssemiconductorco/connectivity-utilities.git

Select the master branch:

Create an AWS Policy, Thing, and Certificate

In order to make a connection to AWS you need to have a policy attached to a certificate.  In addition it makes sense to have an AWS “Thing” to represent your system.

Start from the AWS Console and press “Secure->Policies”.  Then press “Create”

Give your policy a name.  Add all “iot:*” and all resource and “allow”.  Then press “Create”:

Once the policy is created you will see it in the catalog:

Create a new Thing by clicking “Manage -> Things”.  Then click “Create””

Press “Create a single thing”:

First give your thing a name (in this case mouser) then click “Next”:

Click “Create certificate”

You MUST DOWNLOAD these files now.  You will not be given another chance. Press activate:

Which will show the certificate as active.  Next press “attach a policy”:

Then pick the Mouser policy:

Now you have your new “Thing”.

Modify the keys to “C” Format

In order to make a connection to AWS you need a valid certificate, the private key that goes with the certificate and the CA certificate of AWS.    You just created these files in the previous step.

These keys come looking like this:

-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAsRLypD2uhZNZgtfPAuX8M3ZFNYcBU/BHulG9CJrETKwmjXOV
j1l9U/Tlu3p/hGIFEhrkbqt1SGvPEj8x7U5kyCpJde1JojObW4m2S5T7WuXxd5JQ
Y+rqWkyrvwp/1HPFLRB3n3OopGaUcnEZwegsMznUcsi+kZwrUDkqBWFIQbEEKjW8
UzZxqmbmNSCsJQXHCv9OPrKdeisBnkQrpRkTz4uSG3yZI6jG7acx3UZkMcc6DvDo
bbz8PoBisZq4JcKthwpQdiV1RdS4aqCc0wq5Jv7DvhifqNcrOK3PT+zkck82jqIC
VDd58NkY6Y0URIyqjPCPPIltCbrAB/+u41rrtQIDAQABAoIBAQCQAhvprOxpX+u1
OLP35HjWlYI1xSU0Ub7T7bPx8oRg4sS711uz6JC/nfTUIwzf6iO7lLlgs/q/OkZ+
zXxaRZ47GAEEckWnL5dSu83Q7En7o/RcTVcp25xace5fgTdy3fBm9PSEbjih83cZ
F5heFecUhhyceVxa6YpkRQlCtNph6SwFfsY1BmfhHISHh4U7XYityUatdalQfOtL
Vf79TRIE/X1aX/H/xXYsqNQPCSGUzNj1BU5aHOM93B+hNg8q1t+Ze5w6Q/HkMBTb
5VPI8fabvaAV6WUrrWtEl8ErcokutYWNRadXH0ieCGiuI2uoK0qqgrBb2MwZwv81
ZtzCm3QlAoGBAOj5zlZtcWN5Mfb9PHonPSwFPE4pud2fZCJjO9btQWb6ELYwp9qW
2aRaoaQm9bA7pTwMaSRQYGC9eKPu8hu+cqPXJZ1gg1GrYP+IfJMChDAM0x7Faks4
B7ZDjfQlRGUJGAkqLQ4/4+k2y75KtZdnri5Z40myh4f0+PuiDDRwPcGjAoGBAMKS
2VuNefMvzqKW7g/drRLzChudUL96r2BWI8nkl7S9UChtKVgdqJcrNSiKXbTv0wLn
pWsnlOIxPVJAi1u5we045U+ZQadgIg4UqeIBVfRVKm4ZKrInSCT4uMriGDDJuvQl
NXBv9PIX8WCaPWpG2rcsOFsi0mNBcImVHMAhc2LHAoGBAIk6k8Ku5opMWhT9J0Fg
mZSzZMk5pMSZXXcv8pBv4gVRKMTYNhb4oixAQlQZqsBq8bJEMS51tb9l+4i8d5nF
/WrqkLp5ngBeLV13PMGvSsOu2jCW4jx6PXirpBL6XKYSzDihwjZRheLaJvrosLwF
E0E0K0A+y7xWnM5DrmK49nd3AoGAQdp70F24yZMDp8nXdu07F6/EWwZKfxQh6UQe
RsWkhtqQF66ikJ0xI0DPdBIolwWYcGJAfVzfKhMqQv1vbTMYrJZWHjOrod+KhyN9
P+3dzp1IiAzig3uCEmlP+fK95z1PljRFuvFZgNqTqnNpl9+1RMulo0rM1CUg1p/u
JCTuLZ8CgYEAwUsBplIARj4i+hhyVWC1WOR+I4AqgRAauCwliO9gvToJzjkQsc5q
RZxPbXLbm907wy6/7P/tKT20WhZ0b7szZsXchdeX20xSfAa4N0bJTZy3OiS0CYtF
3+Xw1Mlu3Gihr89X9QeSot0tH9m6QZky032aLW/8jbNT3Eb49niqQZA=
-----END RSA PRIVATE KEY-----

But you need them to look like this so that they can be read in by the Mbed TLS library.

// Private key
const char SSL_CLIENTKEY_PEM[] = \
"-----BEGIN RSA PRIVATE KEY-----\n"\
"MIIEpQIBAAKCAQEAsRLypD2uhZNZgtfPAuX8M3ZFNYcBU/BHulG9CJrETKwmjXOV\n"\
"j1l9U/Tlu3p/hGIFEhrkbqt1SGvPEj8x7U5kyCpJde1JojObW4m2S5T7WuXxd5JQ\n"\
"Y+rqWkyrvwp/1HPFLRB3n3OopGaUcnEZwegsMznUcsi+kZwrUDkqBWFIQbEEKjW8\n"\
"UzZxqmbmNSCsJQXHCv9OPrKdeisBnkQrpRkTz4uSG3yZI6jG7acx3UZkMcc6DvDo\n"\
"bbz8PoBisZq4JcKthwpQdiV1RdS4aqCc0wq5Jv7DvhifqNcrOK3PT+zkck82jqIC\n"\
"VDd58NkY6Y0URIyqjPCPPIltCbrAB/+u41rrtQIDAQABAoIBAQCQAhvprOxpX+u1\n"\
"OLP35HjWlYI1xSU0Ub7T7bPx8oRg4sS711uz6JC/nfTUIwzf6iO7lLlgs/q/OkZ+\n"\
"zXxaRZ47GAEEckWnL5dSu83Q7En7o/RcTVcp25xace5fgTdy3fBm9PSEbjih83cZ\n"\
"F5heFecUhhyceVxa6YpkRQlCtNph6SwFfsY1BmfhHISHh4U7XYityUatdalQfOtL\n"\
"Vf79TRIE/X1aX/H/xXYsqNQPCSGUzNj1BU5aHOM93B+hNg8q1t+Ze5w6Q/HkMBTb\n"\
"5VPI8fabvaAV6WUrrWtEl8ErcokutYWNRadXH0ieCGiuI2uoK0qqgrBb2MwZwv81\n"\
"ZtzCm3QlAoGBAOj5zlZtcWN5Mfb9PHonPSwFPE4pud2fZCJjO9btQWb6ELYwp9qW\n"\
"2aRaoaQm9bA7pTwMaSRQYGC9eKPu8hu+cqPXJZ1gg1GrYP+IfJMChDAM0x7Faks4\n"\
"B7ZDjfQlRGUJGAkqLQ4/4+k2y75KtZdnri5Z40myh4f0+PuiDDRwPcGjAoGBAMKS\n"\
"2VuNefMvzqKW7g/drRLzChudUL96r2BWI8nkl7S9UChtKVgdqJcrNSiKXbTv0wLn\n"\
"pWsnlOIxPVJAi1u5we045U+ZQadgIg4UqeIBVfRVKm4ZKrInSCT4uMriGDDJuvQl\n"\
"NXBv9PIX8WCaPWpG2rcsOFsi0mNBcImVHMAhc2LHAoGBAIk6k8Ku5opMWhT9J0Fg\n"\
"mZSzZMk5pMSZXXcv8pBv4gVRKMTYNhb4oixAQlQZqsBq8bJEMS51tb9l+4i8d5nF\n"\
"/WrqkLp5ngBeLV13PMGvSsOu2jCW4jx6PXirpBL6XKYSzDihwjZRheLaJvrosLwF\n"\
"E0E0K0A+y7xWnM5DrmK49nd3AoGAQdp70F24yZMDp8nXdu07F6/EWwZKfxQh6UQe\n"\
"RsWkhtqQF66ikJ0xI0DPdBIolwWYcGJAfVzfKhMqQv1vbTMYrJZWHjOrod+KhyN9\n"\
"P+3dzp1IiAzig3uCEmlP+fK95z1PljRFuvFZgNqTqnNpl9+1RMulo0rM1CUg1p/u\n"\
"JCTuLZ8CgYEAwUsBplIARj4i+hhyVWC1WOR+I4AqgRAauCwliO9gvToJzjkQsc5q\n"\
"RZxPbXLbm907wy6/7P/tKT20WhZ0b7szZsXchdeX20xSfAa4N0bJTZy3OiS0CYtF\n"\
"3+Xw1Mlu3Gihr89X9QeSot0tH9m6QZky032aLW/8jbNT3Eb49niqQZA=\n"\
"-----END RSA PRIVATE KEY-----";

There are a number of ways to change the format including manual editing.

For this lesson I put the keys into a file called “aws_config.h”  There are bunches of bad stories out there about people finding encryption keys on the internet (like these).  I am going to disable the keys as soon as this workshop is over, but you should be careful with yours.

Here is my aws_config.h

/* Change device certificate and device private key based on your account */
// certificate
const char SSL_CLIENTCERT_PEM[] =  \
"-----BEGIN CERTIFICATE-----\n\
MIIDWjCCAkKgAwIBAgIVAJ4u/gU7NxWiV2NwBRTFJk/mPPkvMA0GCSqGSIb3DQEB\n\
CwUAME0xSzBJBgNVBAsMQkFtYXpvbiBXZWIgU2VydmljZXMgTz1BbWF6b24uY29t\n\
IEluYy4gTD1TZWF0dGxlIFNUPVdhc2hpbmd0b24gQz1VUzAeFw0xOTA3MDMxMTM5\n\
MTZaFw00OTEyMzEyMzU5NTlaMB4xHDAaBgNVBAMME0FXUyBJb1QgQ2VydGlmaWNh\n\
dGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCxEvKkPa6Fk1mC188C\n\
5fwzdkU1hwFT8Ee6Ub0ImsRMrCaNc5WPWX1T9OW7en+EYgUSGuRuq3VIa88SPzHt\n\
TmTIKkl17UmiM5tbibZLlPta5fF3klBj6upaTKu/Cn/Uc8UtEHefc6ikZpRycRnB\n\
6CwzOdRyyL6RnCtQOSoFYUhBsQQqNbxTNnGqZuY1IKwlBccK/04+sp16KwGeRCul\n\
GRPPi5IbfJkjqMbtpzHdRmQxxzoO8OhtvPw+gGKxmrglwq2HClB2JXVF1LhqoJzT\n\
Crkm/sO+GJ+o1ys4rc9P7ORyTzaOogJUN3nw2RjpjRREjKqM8I88iW0JusAH/67j\n\
Wuu1AgMBAAGjYDBeMB8GA1UdIwQYMBaAFIJyQZprW3SRNIXe+X22/uZ6sdO9MB0G\n\
A1UdDgQWBBT+al32O2+dH5qoClpyEWRRoMk6oTAMBgNVHRMBAf8EAjAAMA4GA1Ud\n\
DwEB/wQEAwIHgDANBgkqhkiG9w0BAQsFAAOCAQEAoRg4QETRC1rMKJytxthLuGDj\n\
WtB9Zow3TONnq1HZ4yMUQFQeflYCZ96khBuWu91cj2iZengikEJkl+6HGCL+9ZKA\n\
ybWPRyQNvJQ13ju/iQyl89ZIbYh5UGmGrk6G8ryyvPezVP8AxpB5usz9YxTTz/m6\n\
eQMs38zUK5Dv4297NBJ6Xxwoqb8ivJkHEmnkDxKeErd4Qb7Q9ukWYlo5ksjqib8F\n\
NYTv8fhcx4S3UFhbNB+z/iaGtJXk7WIzgGpSX/CiRtGcV776c94LdgIfIZ9DpLgi\n\
GWSrCjsCr6yNtc708vwC+0jKoBcrk3HdVII2N4ciNuzJaoGyuHi+YuiXnO8kbg==\n\
-----END CERTIFICATE-----";

// Private key
const char SSL_CLIENTKEY_PEM[] = \
"-----BEGIN RSA PRIVATE KEY-----\n"\
"MIIEpQIBAAKCAQEAsRLypD2uhZNZgtfPAuX8M3ZFNYcBU/BHulG9CJrETKwmjXOV\n"\
"j1l9U/Tlu3p/hGIFEhrkbqt1SGvPEj8x7U5kyCpJde1JojObW4m2S5T7WuXxd5JQ\n"\
"Y+rqWkyrvwp/1HPFLRB3n3OopGaUcnEZwegsMznUcsi+kZwrUDkqBWFIQbEEKjW8\n"\
"UzZxqmbmNSCsJQXHCv9OPrKdeisBnkQrpRkTz4uSG3yZI6jG7acx3UZkMcc6DvDo\n"\
"bbz8PoBisZq4JcKthwpQdiV1RdS4aqCc0wq5Jv7DvhifqNcrOK3PT+zkck82jqIC\n"\
"VDd58NkY6Y0URIyqjPCPPIltCbrAB/+u41rrtQIDAQABAoIBAQCQAhvprOxpX+u1\n"\
"OLP35HjWlYI1xSU0Ub7T7bPx8oRg4sS711uz6JC/nfTUIwzf6iO7lLlgs/q/OkZ+\n"\
"zXxaRZ47GAEEckWnL5dSu83Q7En7o/RcTVcp25xace5fgTdy3fBm9PSEbjih83cZ\n"\
"F5heFecUhhyceVxa6YpkRQlCtNph6SwFfsY1BmfhHISHh4U7XYityUatdalQfOtL\n"\
"Vf79TRIE/X1aX/H/xXYsqNQPCSGUzNj1BU5aHOM93B+hNg8q1t+Ze5w6Q/HkMBTb\n"\
"5VPI8fabvaAV6WUrrWtEl8ErcokutYWNRadXH0ieCGiuI2uoK0qqgrBb2MwZwv81\n"\
"ZtzCm3QlAoGBAOj5zlZtcWN5Mfb9PHonPSwFPE4pud2fZCJjO9btQWb6ELYwp9qW\n"\
"2aRaoaQm9bA7pTwMaSRQYGC9eKPu8hu+cqPXJZ1gg1GrYP+IfJMChDAM0x7Faks4\n"\
"B7ZDjfQlRGUJGAkqLQ4/4+k2y75KtZdnri5Z40myh4f0+PuiDDRwPcGjAoGBAMKS\n"\
"2VuNefMvzqKW7g/drRLzChudUL96r2BWI8nkl7S9UChtKVgdqJcrNSiKXbTv0wLn\n"\
"pWsnlOIxPVJAi1u5we045U+ZQadgIg4UqeIBVfRVKm4ZKrInSCT4uMriGDDJuvQl\n"\
"NXBv9PIX8WCaPWpG2rcsOFsi0mNBcImVHMAhc2LHAoGBAIk6k8Ku5opMWhT9J0Fg\n"\
"mZSzZMk5pMSZXXcv8pBv4gVRKMTYNhb4oixAQlQZqsBq8bJEMS51tb9l+4i8d5nF\n"\
"/WrqkLp5ngBeLV13PMGvSsOu2jCW4jx6PXirpBL6XKYSzDihwjZRheLaJvrosLwF\n"\
"E0E0K0A+y7xWnM5DrmK49nd3AoGAQdp70F24yZMDp8nXdu07F6/EWwZKfxQh6UQe\n"\
"RsWkhtqQF66ikJ0xI0DPdBIolwWYcGJAfVzfKhMqQv1vbTMYrJZWHjOrod+KhyN9\n"\
"P+3dzp1IiAzig3uCEmlP+fK95z1PljRFuvFZgNqTqnNpl9+1RMulo0rM1CUg1p/u\n"\
"JCTuLZ8CgYEAwUsBplIARj4i+hhyVWC1WOR+I4AqgRAauCwliO9gvToJzjkQsc5q\n"\
"RZxPbXLbm907wy6/7P/tKT20WhZ0b7szZsXchdeX20xSfAa4N0bJTZy3OiS0CYtF\n"\
"3+Xw1Mlu3Gihr89X9QeSot0tH9m6QZky032aLW/8jbNT3Eb49niqQZA=\n"\
"-----END RSA PRIVATE KEY-----";


const char SSL_CA_PEM[] = \
"-----BEGIN CERTIFICATE-----\n"\
"MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\n"\
"ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n"\
"b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\n"\
"MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n"\
"b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\n"\
"ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n"\
"9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\n"\
"IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\n"\
"VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n"\
"93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\n"\
"jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n"\
"AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\n"\
"A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\n"\
"U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\n"\
"N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\n"\
"o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\n"\
"5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\n"\
"rqXRfboQnoZsG4q5WTP468SQvvG5\n"\
"-----END CERTIFICATE-----";

Create and Code awsThread.h

Now create a new header file for the awsThread:

This file just contains the thread function prototype.

#ifndef AWS_THREAD_H
#define AWS_THREAD_H

void awsThread(void);


#endif

Create and Code awsThread.cpp

Now create the awsThread.cpp file for the aws implementation:

The basis for this code is again taken from a code example that is provided by Cypress on GitHub. You can find that code example here:

github.com/cypresssemiconductorco/aws-iot

The awsThread starts with my evil extern.

Then I declare a function which is called when a message comes in from the MQTT broker.  I ASSUME that the message is an ASCII message formatted as %2.1f and I use sscanf to convert it to a float.  Many bugs have been made with this EXACT scheme.  It is expedient way to show people but it is a horrible habit.  I then send it to the temperature controller.

The main awsThread starts by initializing the AWS Client library, then connecting to AWS.  Lastly it subscribes to the setPoint topic.  Then it goes into an infinite loop calling the yield function which is responsible to listening to the aws MQTT socket and doing something with the data… specifically calling the callback.

#include "mbed.h"
#include "aws_client.h"
#include "aws_config.h"
#include "temperatureThread.h"

#define AWSIOT_ENDPOINT_ADDRESS             "a1c0l0bpd6pon3-ats.iot.us-east-2.amazonaws.com"


extern WiFiInterface *wifi;

static void messageArrived(aws_iot_message_t& md)
{
    float setPoint; 
    aws_message_t &message = md.message;
    sscanf((char*)message.payload,"%f",&setPoint);
    tempSendUpdateSetpointF(setPoint);
}

void awsThread(void)
{
    AWSIoTClient client;
    AWSIoTEndpoint* ep = NULL;

    /* Initialize AWS Client library */
    AWSIoTClient AWSClient(wifi, "thermostat" , SSL_CLIENTKEY_PEM, strlen(SSL_CLIENTKEY_PEM), SSL_CLIENTCERT_PEM, strlen(SSL_CLIENTCERT_PEM));

    aws_connect_params conn_params = { 0,0,NULL,NULL,NULL,NULL,NULL };
    ep = AWSClient.create_endpoint(AWS_TRANSPORT_MQTT_NATIVE, AWSIOT_ENDPOINT_ADDRESS, 8883, SSL_CA_PEM, strlen(SSL_CA_PEM));

    /* set MQTT connection parameters */
    conn_params.username = NULL;
    conn_params.password = NULL;
    conn_params.keep_alive = 60;
    conn_params.peer_cn = (uint8_t*) AWSIOT_ENDPOINT_ADDRESS;
    conn_params.client_id = (uint8_t*)"thermostat";

    /* connect to an AWS endpoint */
    AWSClient.connect (ep, conn_params);
 
    AWSClient.subscribe(ep, "setPoint", AWS_QOS_ATMOST_ONCE, messageArrived);
    while(1)
    {
        AWSClient.yield(1000);
    }
}

Update main.cpp

The last step is to start up the awsThread in main:

#include "mbed.h"
#include "blinkThread.h"
#include "displayThread.h"
#include "temperatureThread.h"
#include "capsenseThread.h"
#include "ntpThread.h"
#include "awsThread.h"

Thread blinkThreadHandle;
Thread displayThreadHandle;
Thread temperatureThreadHandle;
Thread capsenseThreadHandle;
Thread ntpThreadHandle;
Thread awsThreadHandle;

WiFiInterface *wifi;

int main()
{

    printf("Started System\n");

    int ret;
    wifi = WiFiInterface::get_default_instance();

    do {
            ret = wifi->connect("CYFI_IOT_EXT", "cypresswicedwifi101", NSAPI_SECURITY_WPA_WPA2);
            if (ret != 0) {
            ThisThread::sleep_for(2000); // If for some reason it doesnt work wait 2s and try again
            }
    } while(ret !=0);


    ntpThreadHandle.start(ntpThread);
    blinkThreadHandle.start(blinkThread);
    displayThreadHandle.start(displayThread);
    temperatureThreadHandle.start(temperatureThread);
    capsenseThreadHandle.start(capsenseThread);
    awsThreadHandle.start(awsThread);
    
}

Build, Program and Test

When you build and program the development kit.  You will be able to publish messages to the “setPoint” topic which will then change the setPoint on your system.  Remember that my parser is really not safe so just send %2.1f.

In the case below you can see that I publish 51.1:

Which changes the setPoint on my board.

 

Mouser PSoC 6 WiFi/BT MBED: L9 Amazon AWS MQTT Thread – Part2

IoT Design with Cypress PSoC® 6 MCUs and Wi-Fi/Bluetooth using Arm® Mbed™

# Lesson GitHub Project
0 Introduction
1 Developer Resources
2 Your First Project & The Blinking Thread https://github.com/iotexpert/mouser-mbed-02.git
3 Display Thread https://github.com/iotexpert/mouser-mbed-03.git
4 Temperature Thread https://github.com/iotexpert/mouser-mbed-04.git
5 CapSense Thread https://github.com/iotexpert/mouser-mbed-05.git
6 WiFi & NTP Thread https://github.com/iotexpert/mouser-mbed-06.git
7 The CY8CKIT-062-WiFi-BT https://github.com/iotexpert/mouser-mbed-07.git 
8 Amazon AWS MQTT Thread - Part1 https://github.com/iotexpert/mouser-mbed-08.git
9 Amazon AWS MQTT Thread - Part2 https://github.com/iotexpert/mouser-mbed-09.git

You can “mbed import https://github.com/iotexpert/mouser-mbed-09.git“ to make a copy of the project in your workspace.

The final architecture of the thermostat looks like this.

 

Summary

In the last lesson we added an MQTT subscription that let’s the AWS servers control the setPoint.  In this lesson we will update the system to send out the current temperature every second – which I know is excessive.

To make the temperature publish changes I need to modify the awsThread to have a queue/command scheme just like the displayThread and the temperatureThread.  Then I will modify the temperatureThread to send out temperature changes to the awsThread which will take care of publishing changes.

To implement this I will:

  1. Import Lesson 08
  2. Modify awsThread.h
  3. Modify awsThread.cpp
  4. Modify temperatureThread.cpp
  5. Build, Program and Test

Import Lesson 09

Start by importing Lesson 08.

https://github.com/iotexpert/mouser-mbed-08.git

Modify awsThread.h

Add the function prototype for the other threads to queue temperature changes.

#ifndef AWS_THREAD_H
#define AWS_THREAD_H

void awsThread(void);
void awsSendUpdateTemperature(float temperature);

#endif

Modify awsThread.cpp

I will copy the scheme I built for the other threads.  There will be only one command, the one to send the temperature.

typedef enum {
    CMD_sendTemperature,
} command_t;

typedef struct {
    command_t cmd;
    float    value;   /* AD result of measured voltage */
} msg_t;


static Queue<msg_t, 32> queue;
static MemoryPool<msg_t, 16> mpool;

void awsSendUpdateTemperature(float temperature)
{
    msg_t *message = mpool.alloc();
    if(message)
    {
        message->cmd = CMD_sendTemperature;
        message->value = temperature;
        queue.put(message);
    }
}

Now I modify the main loop of the awsThread.  The AWSClient.yield function will time out every 1000ms which gives you the opportunity to do something.  That “something” is that I completely empty queue and then send the LAST update.

 bool doPublish=false;
    float currentTemp;
    while(1)
    {
        AWSClient.yield(1000);
        while(!queue.empty())
        {
            osEvent evt = queue.get(0);
            if (evt.status == osEventMessage) {
                msg_t *message = (msg_t*)evt.value.p;
                switch(message->cmd)
                {
                    case CMD_sendTemperature:
                        doPublish = true;
                        currentTemp = message->value;
                    break;

                }
                mpool.free(message);
            }
        }
        if(doPublish)
        {
            char buffer[128];
            sprintf(buffer,"%2.1f",currentTemp);
            AWSClient.publish(ep,"currentTemp", buffer, strlen(buffer),  publish_params);
        }
        doPublish = false;
    }

Here is the whole awsThread.cpp:

#include "mbed.h"
#include "aws_client.h"
#include "aws_config.h"
#include "temperatureThread.h"

#define AWSIOT_ENDPOINT_ADDRESS             "a1c0l0bpd6pon3-ats.iot.us-east-2.amazonaws.com"


extern WiFiInterface *wifi;

typedef enum {
    CMD_sendTemperature,
} command_t;

typedef struct {
    command_t cmd;
    float    value;   /* AD result of measured voltage */
} msg_t;


static Queue<msg_t, 32> queue;
static MemoryPool<msg_t, 16> mpool;

void awsSendUpdateTemperature(float temperature)
{
    msg_t *message = mpool.alloc();
    if(message)
    {
        message->cmd = CMD_sendTemperature;
        message->value = temperature;
        queue.put(message);
    }
}

void messageArrived(aws_iot_message_t& md)
{
    float setPoint; 
    aws_message_t &message = md.message;
    sscanf((char*)message.payload,"%f",&setPoint);
    tempSendUpdateSetpointF(setPoint);
}

void awsThread(void)
{
    AWSIoTClient client;
    AWSIoTEndpoint* ep = NULL;

    /* Initialize AWS Client library */
    AWSIoTClient AWSClient(wifi, "thermostat" , SSL_CLIENTKEY_PEM, strlen(SSL_CLIENTKEY_PEM), SSL_CLIENTCERT_PEM, strlen(SSL_CLIENTCERT_PEM));

    aws_connect_params conn_params = { 0,0,NULL,NULL,NULL,NULL,NULL };
    ep = AWSClient.create_endpoint(AWS_TRANSPORT_MQTT_NATIVE, AWSIOT_ENDPOINT_ADDRESS, 8883, SSL_CA_PEM, strlen(SSL_CA_PEM));

    /* set MQTT connection parameters */
    conn_params.username = NULL;
    conn_params.password = NULL;
    conn_params.keep_alive = 60;
    conn_params.peer_cn = (uint8_t*) AWSIOT_ENDPOINT_ADDRESS;
    conn_params.client_id = (uint8_t*)"thermostat";

    /* connect to an AWS endpoint */
    AWSClient.connect (ep, conn_params);
 
    AWSClient.subscribe(ep, "setPoint", AWS_QOS_ATMOST_ONCE, messageArrived);
    aws_publish_params publish_params = { AWS_QOS_ATMOST_ONCE };
    
    bool doPublish=false;
    float currentTemp;
    while(1)
    {
        AWSClient.yield(1000);
        while(!queue.empty())
        {
            osEvent evt = queue.get(0);
            if (evt.status == osEventMessage) {
                msg_t *message = (msg_t*)evt.value.p;
                switch(message->cmd)
                {
                    case CMD_sendTemperature:
                        doPublish = true;
                        currentTemp = message->value;
                    break;

                }
                mpool.free(message);
            }
        }
        if(doPublish)
        {
            char buffer[128];
            sprintf(buffer,"%2.1f",currentTemp);
            AWSClient.publish(ep,"currentTemp", buffer, strlen(buffer),  publish_params);
        }
        doPublish = false;
    }
}

Modify temperatureThread.cpp

In order to use the new functionality in the awsThread you need to modify the temperatureThread.

First add the #include “awsThread.h” (so that it has access to the awsThread public interface):

#include "mbed.h"
#include "temperatureThread.h"
#include "displayThread.h"
#include "awsThread.h"

Each time the temperature changes send the awsThread the update.

        {
            readTemp();

            // Control the HVAC system with +- 0.5 degree of Hystersis
            if(temperatureF < setPoint - 0.5)
                displaySendUpdateMode(-1.0);
            else if (temperatureF > setPoint + 0.5)
                displaySendUpdateMode(1.0);
            else
                displaySendUpdateMode(0.0);

            displaySendUpdateTemp(temperatureF);
            awsSendUpdateTemperature(temperatureF);
        }

Build, Program and Test

In order to test this I log into the AWS Test Console, and subscribe to the currentTemp topic.  Notice that temperatures keep coming!

 

Mouser PSoC 6 WiFi/BT MBED: L0 Introduction

Summary

Register for my Virtual Training Workshop – IoT Design with Cypress PSoC® 6 MCUs and Wi-Fi/Bluetooth using Arm® Mbed™ on December, 9 at 1:00PM Eastern Time!  I will be live-streaming a complete IoT Design using PSoC and WiFi – live for your entertainment … and hopefully education.

Hello everyone.  This is lesson 0 of a series of 10 lessons about creating an IoT application using the Cypress PSoC 6 MCU & WiFi radios.  For this class I will be building the solution using the Arm Mbed OS development platform.  This will include Mbed Studio and Mbed OS.  And I will be using the CY8CPROTO-062-4343W dev kit which you can purchase from Mouser.  As always I will be writing this code live … no powerpoint.  This is always an adventure and should make for good fun for everyone.

I am going to start by showing you the development kit and demonstrating how to use it.  Then we will build up a complete IoT application that acts like a IoT-ified thermostat by reading and controlling temperature, plus connecting to the internet.

I will attempt to go slowly enough for you to follow along, but if I go too fast, don’t worry you should be able to follow along with the instructions on this website.  I have attempted to put in screen shots and step-by-step instructions.

Today’s virtual workshop has this agenda table which will show also show up on every page.  The links will work to take you through the different lessons.

IoT Design with Cypress PSoC® 6 MCUs and Wi-Fi/Bluetooth using Arm® Mbed™

# Lesson GitHub Project
0 Introduction
1 Developer Resources
2 Your First Project & The Blinking Thread https://github.com/iotexpert/mouser-mbed-02.git
3 Display Thread https://github.com/iotexpert/mouser-mbed-03.git
4 Temperature Thread https://github.com/iotexpert/mouser-mbed-04.git
5 CapSense Thread https://github.com/iotexpert/mouser-mbed-05.git
6 WiFi & NTP Thread https://github.com/iotexpert/mouser-mbed-06.git
7 The CY8CKIT-062-WiFi-BT https://github.com/iotexpert/mouser-mbed-07.git 
8 Amazon AWS MQTT Thread - Part1 https://github.com/iotexpert/mouser-mbed-08.git
9 Amazon AWS MQTT Thread - Part2 https://github.com/iotexpert/mouser-mbed-09.git

You can “mbed import https://github.com/iotexpert/mouser-mbed-09.git“ to make a copy of the project in your workspace.

The final architecture of the thermostat looks like this.

 

You will need a few things for this class:

CY8CPROTO-062-4343W

This development kit (which you can buy from Mouser here) has a bunch of features which we will be using including:

  1. Programmer
  2. PSoC 6 MCU
  3. CYW4343W (WiFi Bluetooth combo chip)
  4. CapSense
  5. Thermistor
  6. LED

ModusToolbox 2.0

ModusToolbox 2.0 is the Cypress multi-platform development tool suite.  It includes several IDEs, Graphical Chip configurators, SDKs for Bluetooth, WiFi and PSoC 6 MCUs.

Mbed Studio

Arm Mbed Studio is an IDE for developing Mbed OS programs that target the Cypress PSoC 6 MCU.

Mbed Command Line Interface

The Mbed CLI is a command line interface for building and programming Mbed OS projects onto the PSoC 6 MCU.

Live Like You Are Dying

Sunday is the memorial service for my friend, Jeff Wagner, who died last week from Pancreatic Cancer.  He is an electrical engineer who has a daughter that is the same age as mine.  We got to know each other as our kids “orbits” bounced together over the years.  I remember the first time that we met at the band concert at the high school a number of years ago.  He asked what I did … “engineer” I said… “Oh, so am I,” he said.  We then chatted a bit about our jobs… I said that I worked for chip company.  He told me he worked as a process engineer at a manufacturing company.  A few weeks later when I saw him again he said “Hey, you didn’t tell me that you were EVP of Software at Cypress!”… I then joked that I was embarrassed to spend more time writing powerpoint than doing any real engineering – like him.  Later I got into the habit of bringing him Cypress development kits to try.  He was very interested in embedded development and actively used PSoC Creator, PSoC, CapSense and Bluetooth.  Jeff was interested in machine learning and had bought a number of development kits to make ML systems to do different things both for his work and for his real life.  You see, Jeff was a real engineer.

Over the last several years, as our daughters went off to college, we didn’t see each other very much.  Occasionally we met for a meal to catch up… or I would send him a new development kit… or we would trade email about how things were going.  Earlier this year I sent him a note that I was teaching  a class for Mouser and invited him to join.  He responded that things had changed quite a lot since we had last talked and that he had pancreatic cancer.  What is crazy is that the focus of the email was more about a project that he was working on and hardly at all about the diagnosis.

From the beginning of May – the date of the email – until his death last week, he was a constant source of inspiration for me.  How you can be struck down in the prime of life and still keep an awesome attitude, I just don’t know.  He said “Fuck cancer.”  Over the last months, we saw each other quite a few times … unfortunately, mostly in the hospital.  Although he was weak and in pain I would bring a computer and we would write or talk about software together.  I showed him beta software, he tried MBED OS on PSoC, we talked about machine learning, we talked about bikes, our kids and all manner of other stuff.

Jeff WAS STILL ACTIVELY LEARNING DAYS BEFORE HIS FREAKING DEATH.  He still cared and it was awesome to behold for me.  He was a model for what an engineer should be and I will always be inspired by that.

One of the last times that I saw him, we talked about the choices we make in life.  He still thought that things were going to work out.  He talked about the things he would do after he got clear of the cancer.  I’m sure that he was talking to me as much to himself, because I always seem to make the most self destructive choices.  In fact, I was sleeping in a hotel the night he died.

I have to assume he was a good father and husband.  All the evidence suggests that to be true as he has great kids and a cool wife.  But, I know he was a great engineer; he loved to learn and build.

He was often one of the first people to read this website.  And I am sad for that to no longer be the case.

I am sad for Sandy, Anna and the rest of his family.  I can’t begin to imagine how hard that it will be.

And even though I know it is selfish, I am sad for myself.

Rest in peace, my friend.

 

Mouser Bluetooth Mesh: L7 Modifying the Dimmable Light Code to Add Another Light Element

How To Design With Bluetooth Mesh


You can "git" a workspace will all of these files at https://github.com/iotexpert/MouserVTWBluetoothMesh or git@github.com:iotexpert/MouserVTWBluetoothMesh.git

Summary

In this project I will create a Bluetooth Mesh Node that has two Light Lightness Server elements, one to control the red LED and one to control the Green LED.  The purpose is to show an example adding elements to a node.

I will start with the BLE_Mesh_LightDimmable and then modify it to add the ability to control the Green LED as well.

To implement this lesson I will follow these steps:

  1. Make New Application with BLE_Mesh_LightDimmable Template
  2. Program it to make sure that things are still working
  3. Modify led_control.h to handle two LEDs
  4. Modify led_control.c to handle two LEDs
  5. Modify light_dimmable.c to update the Bluetooth Mesh Configuration
  6. Test

Make New Project with BLE_Mesh_LightDimmable Template

Use File->New->ModusToobox IDE Application

Pick the “CYBT-213043-MESH”

Choose “BLE_Mesh_LightDimmable” and name your project “VTW_RedGreen”

Click “Finish”

Program it to make sure that things are still working by clicking “Launches -> VTW_RedGreen Build + Program”

After it is downloaded you can see that it boots.

Press “Start Scan”.  After a while you will see that it found the device.

Now press “Provision and configure”

Modify led_control.h

The files led_control.h and .c are used to manage the LED.  I will extend the led_control_set_brightness_level by adding the ability to control two LEDs – a Red and a Green

typedef enum {
	RED,
	GREEN,
} led_control_t;

void led_control_init(void);
void led_control_set_brighness_level(led_control_t whichLED, uint8_t brightness_level);

Modify led_control.c

Now I need to hook up a PWM to the Green LED.  I will use PWM1 (notice that I also change the PWM_CHANNEL to _RED and _GREEN)

#define PWM_CHANNEL_RED           PWM0
#define PWM_CHANNEL_GREEN         PWM1

The led_control_init function is used to setup the hardware.  Specifically to configure the two PWMs to be attached to the correct pins.

void led_control_init(void)
{
    pwm_config_t pwm_config;

    /* configure PWM */
    wiced_hal_gpio_select_function(WICED_GPIO_PIN_LED_2, WICED_PWM0);
    wiced_hal_gpio_select_function(WICED_GPIO_PIN_LED_3, WICED_PWM1);

    wiced_hal_aclk_enable(PWM_INP_CLK_IN_HZ, ACLK1, ACLK_FREQ_24_MHZ);
    wiced_hal_pwm_get_params(PWM_INP_CLK_IN_HZ, 0, PWM_FREQ_IN_HZ, &pwm_config);
    wiced_hal_pwm_start(PWM_CHANNEL_RED, PMU_CLK, pwm_config.toggle_count, pwm_config.init_count, 1);
    wiced_hal_pwm_start(PWM_CHANNEL_GREEN, PMU_CLK, pwm_config.toggle_count, pwm_config.init_count, 1);
}

Finally, I modify the …set_brightness… function to handle Red and Green.

void led_control_set_brighness_level(led_control_t whichLED, uint8_t brightness_level)
{
    pwm_config_t pwm_config;

    WICED_BT_TRACE("set brightness:%d\n", brightness_level);

    // ToDo.  For some reason, setting brightness to 100% does not work well on 20719B1 platform. For now just use 99% instead of 100.
    if (brightness_level == 100)
        brightness_level = 99;

    wiced_hal_pwm_get_params(PWM_INP_CLK_IN_HZ, brightness_level, PWM_FREQ_IN_HZ, &pwm_config);
    switch(whichLED)
    {
    case RED:
        wiced_hal_pwm_change_values(PWM_CHANNEL_RED, pwm_config.toggle_count, pwm_config.init_count);

    break;
    case GREEN:
        wiced_hal_pwm_change_values(PWM_CHANNEL_GREEN, pwm_config.toggle_count, pwm_config.init_count);

    	break;
    }
}

Modify light_dimmable.c

Now I need to add a new element to the mesh_elements array.  Before I can do that, it need to create new element array called “mesh_element2_models” with the model that I need.  Notice that I also change the original code to have _RED

wiced_bt_mesh_core_config_model_t   mesh_element1_models[] =
{
    WICED_BT_MESH_DEVICE,
    WICED_BT_MESH_MODEL_USER_PROPERTY_SERVER,
    WICED_BT_MESH_MODEL_LIGHT_LIGHTNESS_SERVER,
};
#define MESH_APP_NUM_MODELS_RED  (sizeof(mesh_element1_models) / sizeof(wiced_bt_mesh_core_config_model_t))




wiced_bt_mesh_core_config_property_t mesh_element1_properties[] =
{
    {
        .id          = WICED_BT_MESH_PROPERTY_DEVICE_FIRMWARE_REVISION,
        .type        = WICED_BT_MESH_PROPERTY_TYPE_USER,
        .user_access = WICED_BT_MESH_PROPERTY_ID_READABLE,
        .max_len     = WICED_BT_MESH_PROPERTY_LEN_DEVICE_FIRMWARE_REVISION,
        .value       = mesh_prop_fw_version
    },
};
#define MESH_APP_NUM_PROPERTIES (sizeof(mesh_element1_properties) / sizeof(wiced_bt_mesh_core_config_property_t))

wiced_bt_mesh_core_config_model_t   mesh_element2_models[] =
{
    WICED_BT_MESH_MODEL_LIGHT_LIGHTNESS_SERVER,
};
#define MESH_APP_NUM_MODELS_GREEN  (sizeof(mesh_element2_models) / sizeof(wiced_bt_mesh_core_config_model_t))

#define MESH_LIGHT_LIGHTNESS_SERVER_ELEMENT_INDEX_RED   0
#define MESH_LIGHT_LIGHTNESS_SERVER_ELEMENT_INDEX_GREEN   1

wiced_bt_mesh_core_config_element_t mesh_elements[] =
{
    {
        .location = MESH_ELEM_LOC_MAIN,                                 // location description as defined in the GATT Bluetooth Namespace Descriptors section of the Bluetooth SIG Assigned Numbers
        .default_transition_time = MESH_DEFAULT_TRANSITION_TIME_IN_MS,  // Default transition time for models of the element in milliseconds
        .onpowerup_state = WICED_BT_MESH_ON_POWER_UP_STATE_RESTORE,     // Default element behavior on power up
        .default_level = 0,                                             // Default value of the variable controlled on this element (for example power, lightness, temperature, hue...)
        .range_min = 1,                                                 // Minimum value of the variable controlled on this element (for example power, lightness, temperature, hue...)
        .range_max = 0xffff,                                            // Maximum value of the variable controlled on this element (for example power, lightness, temperature, hue...)
        .move_rollover = 0,                                             // If true when level gets to range_max during move operation, it switches to min, otherwise move stops.
        .properties_num = MESH_APP_NUM_PROPERTIES,                      // Number of properties in the array models
        .properties = mesh_element1_properties,                         // Array of properties in the element.
        .sensors_num = 0,                                               // Number of sensors in the sensor array
        .sensors = NULL,                                                // Array of sensors of that element
        .models_num = MESH_APP_NUM_MODELS_RED,                          // Number of models in the array models
        .models = mesh_element1_models,                                 // Array of models located in that element. Model data is defined by structure wiced_bt_mesh_core_config_model_t
    },
    {
        .location = MESH_ELEM_LOC_MAIN,                                 // location description as defined in the GATT Bluetooth Namespace Descriptors section of the Bluetooth SIG Assigned Numbers
        .default_transition_time = MESH_DEFAULT_TRANSITION_TIME_IN_MS,  // Default transition time for models of the element in milliseconds
        .onpowerup_state = WICED_BT_MESH_ON_POWER_UP_STATE_RESTORE,     // Default element behavior on power up
        .default_level = 0,                                             // Default value of the variable controlled on this element (for example power, lightness, temperature, hue...)
        .range_min = 1,                                                 // Minimum value of the variable controlled on this element (for example power, lightness, temperature, hue...)
        .range_max = 0xffff,                                            // Maximum value of the variable controlled on this element (for example power, lightness, temperature, hue...)
        .move_rollover = 0,                                             // If true when level gets to range_max during move operation, it switches to min, otherwise move stops.
        .properties_num = 0,                                            // Number of properties in the array models
        .properties = NULL,                                             // Array of properties in the element.
        .sensors_num = 0,                                               // Number of sensors in the sensor array
        .sensors = NULL,                                                // Array of sensors of that element
        .models_num = MESH_APP_NUM_MODELS_GREEN,                              // Number of models in the array models
        .models = mesh_element2_models,                                 // Array of models located in that element. Model data is defined by structure wiced_bt_mesh_core_config_model_t
    },

};

In the mesh_app_init function you need to initialize the new light lightness server model. We will use the same message handler for both light lightness server models.

    wiced_bt_mesh_model_light_lightness_server_init(MESH_LIGHT_LIGHTNESS_SERVER_ELEMENT_INDEX_GREEN, mesh_app_message_handler, is_provisioned);

Search and replace all of the “led_control_set_brighness_level(attention_brightness);” to be “led_control_set_brighness_level(RED,attention_brightness);”

The last step is to update the mesh_app_process_set_level callback to handle the two cases.

/*
 * Command from the level client is received to set the new level
 */
void mesh_app_process_set_level(uint8_t element_idx, wiced_bt_mesh_light_lightness_status_t *p_status)
{
    WICED_BT_TRACE("mesh light srv set level element:%d present actual:%d linear:%d remaining_time:%d\n",
        element_idx, p_status->lightness_actual_present, p_status->lightness_linear_present, p_status->remaining_time);

    last_known_brightness = (uint8_t)((uint32_t)p_status->lightness_actual_present * 100 / 65535);
    if(element_idx == MESH_LIGHT_LIGHTNESS_SERVER_ELEMENT_INDEX_RED)
    		led_control_set_brighness_level(RED,last_known_brightness);

    if(element_idx == MESH_LIGHT_LIGHTNESS_SERVER_ELEMENT_INDEX_GREEN)
    		led_control_set_brighness_level(GREEN,last_known_brightness);

    // If we were alerting user, stop it.
    wiced_stop_timer(&attention_timer);
}

Test

Do a “Node Reset” to remove the old node from the network.  It doesn’t really matter… but I find it less confusing to have it gone.

Program the kit using the launch “VTW_RedGreen Build + Program”

Press “Scan Unprovisioned”

Press “Provision and configure”.  It takes a minute.  When it is done you will see “…done” in the trace window.

Now I can “Control” the two LEDs.  Notice that the address of the first one is 0002 and the second is 0003.

When I do “Set” I can turn on the Green LED.

Mouser Bluetooth Mesh: L6 The Dimmable Light Code

How To Design With Bluetooth Mesh


You can "git" a workspace will all of these files at https://github.com/iotexpert/MouserVTWBluetoothMesh or git@github.com:iotexpert/MouserVTWBluetoothMesh.git

Summary

This is a programming class.  So let’s take a closer look at the Light Dimmable Project.  It is not very hard.  The project is broken up into three main files

  1. led_control.h – which is the public interface to control the LED.
  2. led_control.c – the actual functions to control the LED.
  3. light_dimmable.c – the user application part of the Bluetooth Mesh.

We will dig through these files one at a time.

led_control.h

This file is the public API for controlling the LED.  There are two functions, the first “led_control_init” which must setup the hardware for the LED.  And a function to set the brightness level.  OK that is simple enough.

#ifndef __LED_CONTROL__H
#define __LED_CONTROL__H

#ifdef __cplusplus
extern "C" {
#endif

void led_control_init(void);
void led_control_set_brighness_level(uint8_t brightness_level);

#ifdef __cplusplus
}
#endif

#endif

led_control.c

The two functions in this file are led_control_init which just sets up a PWM to control the LED.  We are using the CYW20819A1.  The wiced_had_gpio_select_function just tells the pin to connect the pin mux to PWM0.

void led_control_init(void)
{
    pwm_config_t pwm_config;

    /* configure PWM */
#ifdef CYW20719B1
    wiced_hal_pwm_configure_pin(led_pin, PWM_CHANNEL);
#endif

#ifdef CYW20819A1
    wiced_hal_gpio_select_function(WICED_GPIO_PIN_LED_2, WICED_PWM0);
#endif
    wiced_hal_aclk_enable(PWM_INP_CLK_IN_HZ, ACLK1, ACLK_FREQ_24_MHZ);
    wiced_hal_pwm_get_params(PWM_INP_CLK_IN_HZ, 0, PWM_FREQ_IN_HZ, &pwm_config);
    wiced_hal_pwm_start(PWM_CHANNEL, PMU_CLK, pwm_config.toggle_count, pwm_config.init_count, 1);
}

You can see the API documentation by clicking Help->ModusToolbox API Reference–>WICED API Reference

I look down through the documentation until I find the wiced_hal_gpio_select_function

The led_control_set_brighness_level takes an input level from 0-100 and picks out the right PWM duty cycle.

void led_control_set_brighness_level(uint8_t brightness_level)
{
    pwm_config_t pwm_config;

    WICED_BT_TRACE("set brightness:%d\n", brightness_level);

    // ToDo.  For some reason, setting brightness to 100% does not work well on 20719B1 platform. For now just use 99% instead of 100.
    if (brightness_level == 100)
        brightness_level = 99;

    wiced_hal_pwm_get_params(PWM_INP_CLK_IN_HZ, brightness_level, PWM_FREQ_IN_HZ, &pwm_config);
    wiced_hal_pwm_change_values(PWM_CHANNEL, pwm_config.toggle_count, pwm_config.init_count);
}

light_dimmable.c

There are 6 sections of the Bluetooth Mesh User Application firmware.

  1. Mesh Element/Model
  2. Mesh Core Configuration
  3. Mesh Application Callbacks
  4. mesh_app_init
  5. Attention Handler (a new concept)
  6. Light Server Handler

light_dimmable.c – Mesh Element/Model

This project will have one element, which holds three models including a property server (which has one property)

wiced_bt_mesh_core_config_model_t   mesh_element1_models[] =
{
    WICED_BT_MESH_DEVICE,
    WICED_BT_MESH_MODEL_USER_PROPERTY_SERVER,
    WICED_BT_MESH_MODEL_LIGHT_LIGHTNESS_SERVER,
};
#define MESH_APP_NUM_MODELS  (sizeof(mesh_element1_models) / sizeof(wiced_bt_mesh_core_config_model_t))

wiced_bt_mesh_core_config_property_t mesh_element1_properties[] =
{
    {
        .id          = WICED_BT_MESH_PROPERTY_DEVICE_FIRMWARE_REVISION,
        .type        = WICED_BT_MESH_PROPERTY_TYPE_USER,
        .user_access = WICED_BT_MESH_PROPERTY_ID_READABLE,
        .max_len     = WICED_BT_MESH_PROPERTY_LEN_DEVICE_FIRMWARE_REVISION,
        .value       = mesh_prop_fw_version
    },
};
#define MESH_APP_NUM_PROPERTIES (sizeof(mesh_element1_properties) / sizeof(wiced_bt_mesh_core_config_property_t))


#define MESH_LIGHT_LIGHTNESS_SERVER_ELEMENT_INDEX   0

wiced_bt_mesh_core_config_element_t mesh_elements[] =
{
    {
        .location = MESH_ELEM_LOC_MAIN,                                 // location description as defined in the GATT Bluetooth Namespace Descriptors section of the Bluetooth SIG Assigned Numbers
        .default_transition_time = MESH_DEFAULT_TRANSITION_TIME_IN_MS,  // Default transition time for models of the element in milliseconds
        .onpowerup_state = WICED_BT_MESH_ON_POWER_UP_STATE_RESTORE,     // Default element behavior on power up
        .default_level = 0,                                             // Default value of the variable controlled on this element (for example power, lightness, temperature, hue...)
        .range_min = 1,                                                 // Minimum value of the variable controlled on this element (for example power, lightness, temperature, hue...)
        .range_max = 0xffff,                                            // Maximum value of the variable controlled on this element (for example power, lightness, temperature, hue...)
        .move_rollover = 0,                                             // If true when level gets to range_max during move operation, it switches to min, otherwise move stops.
        .properties_num = MESH_APP_NUM_PROPERTIES,                      // Number of properties in the array models
        .properties = mesh_element1_properties,                         // Array of properties in the element.
        .sensors_num = 0,                                               // Number of sensors in the sensor array
        .sensors = NULL,                                                // Array of sensors of that element
        .models_num = MESH_APP_NUM_MODELS,                              // Number of models in the array models
        .models = mesh_element1_models,                                 // Array of models located in that element. Model data is defined by structure wiced_bt_mesh_core_config_model_t
    },
};

light_dimmable.c – Mesh Core Config

This configuration structure is read by the Bluetooth Mesh stack automatically.  It just tells the stack how to behave.

wiced_bt_mesh_core_config_t  mesh_config =
{
    .company_id         = MESH_COMPANY_ID_CYPRESS,                  // Company identifier assigned by the Bluetooth SIG
    .product_id         = MESH_PID,                                 // Vendor-assigned product identifier
    .vendor_id          = MESH_VID,                                 // Vendor-assigned product version identifier
    .replay_cache_size  = MESH_CACHE_REPLAY_SIZE,                   // Number of replay protection entries, i.e. maximum number of mesh devices that can send application messages to this device.
    .features           = WICED_BT_MESH_CORE_FEATURE_BIT_FRIEND | WICED_BT_MESH_CORE_FEATURE_BIT_RELAY | WICED_BT_MESH_CORE_FEATURE_BIT_GATT_PROXY_SERVER,   // In Friend mode support friend, relay
    .friend_cfg         =                                           // Configuration of the Friend Feature(Receive Window in Ms, messages cache)
    {
        .receive_window        = 200,                               // Receive Window value in milliseconds supported by the Friend node.
        .cache_buf_len         = 300,                               // Length of the buffer for the cache
        .max_lpn_num           = 4                                  // Max number of Low Power Nodes with established friendship. Must be > 0 if Friend feature is supported. 
    },
    .low_power          =                                           // Configuration of the Low Power Feature
    {
        .rssi_factor           = 0,                                 // contribution of the RSSI measured by the Friend node used in Friend Offer Delay calculations.
        .receive_window_factor = 0,                                 // contribution of the supported Receive Window used in Friend Offer Delay calculations.
        .min_cache_size_log    = 0,                                 // minimum number of messages that the Friend node can store in its Friend Cache.
        .receive_delay         = 0,                                 // Receive delay in 1 ms units to be requested by the Low Power node.
        .poll_timeout          = 0                                  // Poll timeout in 100ms units to be requested by the Low Power node.
    },
    .gatt_client_only          = WICED_FALSE,                       // Can connect to mesh over GATT or ADV
    .elements_num  = (uint8_t)(sizeof(mesh_elements) / sizeof(mesh_elements[0])),   // number of elements on this device
    .elements      = mesh_elements                                  // Array of elements for this device
};

light_dimmable.c – Mesh Application Callbacks

The Bluetooth Mesh stack  interacts with your application via callbacks.  This structure tells the stack when you are interested in being called.

/*
 * Mesh application library will call into application functions if provided by the application.
 */
wiced_bt_mesh_app_func_table_t wiced_bt_mesh_app_func_table =
{
    mesh_app_init,          // application initialization
    NULL,                   // Default SDK platform button processing
    NULL,                   // GATT connection status
    mesh_app_attention,     // attention processing
    NULL,                   // notify period set
    NULL,                   // WICED HCI command
    NULL,                   // LPN sleep
    NULL                    // factory reset
};

light_dimmable.c – mesh_app_init

This function set’s up things after the stack starts.  Specifically it configures data for the property server. It also sets up the Scan Response packet.  Finally it turns on the servers for the Property and Light Lightness Servers and registers a callback function when messages are received for the light lightness server model.

/******************************************************
 *               Function Definitions
 ******************************************************/
void mesh_app_init(wiced_bool_t is_provisioned)
{
#if 0
    extern uint8_t wiced_bt_mesh_model_trace_enabled;
    wiced_bt_mesh_model_trace_enabled = WICED_TRUE;
#endif
    wiced_bt_cfg_settings.device_name = (uint8_t *)"Dimmable Light";
    wiced_bt_cfg_settings.gatt_cfg.appearance = APPEARANCE_LIGHT_CEILING;

    mesh_prop_fw_version[0] = 0x30 + (WICED_SDK_MAJOR_VER / 10);
    mesh_prop_fw_version[1] = 0x30 + (WICED_SDK_MAJOR_VER % 10);
    mesh_prop_fw_version[2] = 0x30 + (WICED_SDK_MINOR_VER / 10);
    mesh_prop_fw_version[3] = 0x30 + (WICED_SDK_MINOR_VER % 10);
    mesh_prop_fw_version[4] = 0x30 + (WICED_SDK_REV_NUMBER / 10);
    mesh_prop_fw_version[5] = 0x30 + (WICED_SDK_REV_NUMBER % 10);
    mesh_prop_fw_version[6] = 0x30 + (WICED_SDK_BUILD_NUMBER / 10);
    mesh_prop_fw_version[7] = 0x30 + (WICED_SDK_BUILD_NUMBER % 10);

    // Adv Data is fixed. Spec allows to put URI, Name, Appearance and Tx Power in the Scan Response Data.
    if (!is_provisioned)
    {
        wiced_bt_ble_advert_elem_t  adv_elem[3];
        uint8_t                     buf[2];
        uint8_t                     num_elem = 0;

        adv_elem[num_elem].advert_type = BTM_BLE_ADVERT_TYPE_NAME_COMPLETE;
        adv_elem[num_elem].len         = (uint16_t)strlen((const char*)wiced_bt_cfg_settings.device_name);
        adv_elem[num_elem].p_data      = wiced_bt_cfg_settings.device_name;
        num_elem++;

        adv_elem[num_elem].advert_type = BTM_BLE_ADVERT_TYPE_APPEARANCE;
        adv_elem[num_elem].len         = 2;
        buf[0]                         = (uint8_t)wiced_bt_cfg_settings.gatt_cfg.appearance;
        buf[1]                         = (uint8_t)(wiced_bt_cfg_settings.gatt_cfg.appearance >> 8);
        adv_elem[num_elem].p_data      = buf;
        num_elem++;

        wiced_bt_mesh_set_raw_scan_response_data(num_elem, adv_elem);
    }
    led_control_init();

    wiced_init_timer(&attention_timer, attention_timer_cb, 0, WICED_SECONDS_PERIODIC_TIMER);

    // Initialize Light Lightness Server and register a callback which will be executed when it is time to change the brightness of the bulb
    wiced_bt_mesh_model_light_lightness_server_init(MESH_LIGHT_LIGHTNESS_SERVER_ELEMENT_INDEX, mesh_app_message_handler, is_provisioned);

    // Initialize the Property Server.  We do not need to be notified when Property is set, because our only property is readonly
    wiced_bt_mesh_model_property_server_init(MESH_LIGHT_LIGHTNESS_SERVER_ELEMENT_INDEX, NULL, is_provisioned);
}

light_dimmable.c – Attention

There are situations where the stack might want to get the user’s attention.   In the callback we setup the stack to call mesh_app_attention when it wants the user’s attention.  The parameter is how long the stack wants to alert the user.   This function starts a timer to blink the RED LED. Once the attention time has expired, the timer stops.

*
 * Mesh library requests to alert user for "time" seconds.
 */
void mesh_app_attention(uint8_t element_idx, uint8_t time)
{
    WICED_BT_TRACE("dimmable light attention:%d sec\n", time);

    // If time is zero, stop alerting and restore the last known brightness
    if (time == 0)
    {
        wiced_stop_timer(&attention_timer);
        led_control_set_brighness_level(last_known_brightness);
        return;
    }
    wiced_start_timer(&attention_timer, 1);
    attention_time = time;
    attention_brightness = (last_known_brightness != 0) ? 0 : 100;
    led_control_set_brighness_level(attention_brightness);
}

/*
 * Attention timer callback is executed every second while user needs to be alerted.
 * Just switch brightness between 0 and 100%
 */
void attention_timer_cb(TIMER_PARAM_TYPE arg)
{
    WICED_BT_TRACE("dimmable light attention timeout:%d\n", attention_time);

    if (--attention_time == 0)
    {
        wiced_stop_timer(&attention_timer);
        led_control_set_brighness_level(last_known_brightness);
        return;
    }
    attention_brightness = (attention_brightness == 0) ? 100 : 0;
    led_control_set_brighness_level(attention_brightness);
}

light_dimmable.c – Light Server Handler

When the Node receives a message that has been published to it to change the Light Lightness value, this function is called.  Basically it just calls the hardware API to change the LED brightness.

/*
 * Process event received from the models library.
 */
void mesh_app_message_handler(uint8_t element_idx, uint16_t event, void *p_data)
{
    switch (event)
    {
    case WICED_BT_MESH_LIGHT_LIGHTNESS_SET:
        mesh_app_process_set_level(element_idx, (wiced_bt_mesh_light_lightness_status_t *)p_data);
        break;

    default:
        WICED_BT_TRACE("dimmable light unknown msg:%d\n", event);
        break;
    }
}

/*
 * Command from the level client is received to set the new level
 */
void mesh_app_process_set_level(uint8_t element_idx, wiced_bt_mesh_light_lightness_status_t *p_status)
{
    WICED_BT_TRACE("mesh light srv set level element:%d present actual:%d linear:%d remaining_time:%d\n",
        element_idx, p_status->lightness_actual_present, p_status->lightness_linear_present, p_status->remaining_time);

    last_known_brightness = (uint8_t)((uint32_t)p_status->lightness_actual_present * 100 / 65535);
    led_control_set_brighness_level(last_known_brightness);

    // If we were alerting user, stop it.
    wiced_stop_timer(&attention_timer);
}

 

Mouser Bluetooth Mesh: L5 Bluetooth Mesh Fundamentals

How To Design With Bluetooth Mesh


You can "git" a workspace will all of these files at https://github.com/iotexpert/MouserVTWBluetoothMesh or git@github.com:iotexpert/MouserVTWBluetoothMesh.git

Summary

This lesson walks you through the fundamentals of Bluetooth Mesh Networking.  There is actually quite a bit going on from a detail standpoint to make Bluetooth Mesh work as documented in the 713 pages of Bluetooth Mesh specifications:

However, Cypress has abstracted a significant amount of this complexity into our stack so as to ease your journey.

Specifically in this lesson we will talk about:

  1. Topology
  2. Elements
  3. Addressing
  4. Messaging
  5. Subscribe & Publish
  6. Models
  7. State
  8. Complexity
  9. Security
  10. Provisioning & Configuration
  11. Bluetooth Mesh Stack

Bluetooth Mesh Topology

Let’s start by examining a prototypical Bluetooth Mesh network:

 

Standard Node
The standard node functionality involves sending and receiving mesh messages. Every node in the network must be able to act as a standard node.
Relay Node
Relay nodes can receive a message for the network and then retransmit it to other devices in range. This is the method by which mesh networks can cover larger distances than the range of any single device. For a network to operate, every node must be within range of at least one relay so that its messages can be forwarded on to nodes that it cannot directly communicate with.
It is common for all except low power nodes to implement a relay feature in order to maximize the possible paths through a mesh network.
Relay nodes keep a cache of messages to prevent messages from cycling.  Each mesh message also has a TTL (time to live) to prevent cycling.
GATT Proxy Node
Many existing BLE devices support traditional BLE GATT communication but not mesh communication. Most smartphones and tablets fall into this category. Since you may want to interact with a mesh network from one of those devices, the GATT proxy was created. A GATT proxy node has both a mesh interface and a GATT interface. The GATT interface is used to communicate with BLE devices that don’t possess a mesh stack and then relay those messages to/from the mesh network. That is, the GATT proxy acts as a bridge between the mesh network and the traditional BLE GATT device.
Friend and Low Power Nodes
Friend and Low Power Nodes are used to optimize power consumption for constrained devices. Devices that are power constrained (e.g. a battery powered device) are designated as low power nodes. Every low power node in the network must be associated with exactly one friend node. Friend nodes are devices which are not power constrained (e.g. a device plugged into AC power) that support 1 or more low power nodes depending on its capabilities (e.g. available RAM).
When a low power node is added to a mesh network it broadcasts a request for a friend. Each friend in range that can handle a new low power node replies and the low power node selects the best friend based on how many messages the friend can store; the RSSI and the timing accuracy.
Once the relationship is established, the friend node will receive and store messages for any low power nodes that it is associated with. The low power node will periodically ask the friend node for any messages that the friend has stored for it. In this way, the low power node does not need to listen continuously for mesh packets. Instead, it can be in a low power mode most of the time and can wake up only periodically for a very short time.
For example, consider a battery powered mesh connected thermostat. It will measure the actual temperature and may publish a mesh message with the temperature once per minute. This can be done with very low power consumption since the device can be sleeping all the time except for a short period each minute to send the value. However, it must also be possible to change the set point of the thermostat. In this case, instead of sending messages, the thermostat must be listening for messages. If it listens constantly for messages the power consumption will be unacceptably high, but if it only listens occasionally for messages it will likely miss messages. By making the thermostat a low power node we get the best of both worlds – it can send messages once a minute and receive any stored messages regarding the set point from its friend node. No messages are missed even though the thermostat is awake only a very small percentage of the time.

Bluetooth Mesh Elements

An Element is just a “Thing” in the network.  For instance a light bulb or a light switch or a temperature sensor.  A physical node can be built up of multiple elements.  Think of a ceiling fan that also has a light bulb.

Each Element in the network will have an address and be uniquely addressable.  This means that a Node may have multiple Bluetooth Mesh addresses, but it will have only one Bluetooth MAC address.

Bluetooth Mesh Addressing

Mesh messages have a source address and a destination address. Both addresses are 16-bit values. There are three types of addresses defined for messages. They are:
1. Unicast
2. Group
3. Virtual

Address Type Address Range Number of Addresses
Unassigned 0b0000000000000000 1
Unicast 0b0xxxxxxxxxxxxxxx 32767
Group 0b11xxxxxxxxxxxxxx 16384
Virtual 0b10xxxxxxxxxxxxxx 16384 hash values

Unicast
A unicast address is used to communicate with a single element in a single node. Each element in a network must have a unicast address that is unique to that network. During provisioning, the primary element in a node is assigned a unique unicast address and each additional element in the node uses the next address.
The source address in any message must be a unicast address. That is, the message must specify the specific element that message was sent by. Group and Virtual addresses are not allowed as the source address.

Group
As the name implies, a group address is used to communicate with one or more elements. Group addresses are either defined by the Bluetooth SIG (known as fixed group addresses) or are assigned dynamically for a given mesh network. There are 16K total group addresses available. The SIG has reserved space for 256 of them to be fixed while the rest can be dynamically chosen by the network.
Of the 256 group addresses that the SIG has reserved for fixed addresses, currently only 4 of them are assigned specific purposes. The rest are reserved for future use. They are:

Fixed Group Address Name
0b1111111100000000 – 0b1111111111111011 Reserved
0b1111111111111100 all-proxies
0b1111111111111101 all-friends
0b1111111111111110 all-relays
0b1111111111111111 all-nodes

Other group addresses can be assigned for any logical group in the network. For example, room names such as kitchen, bedroom or living room could be identified as group names to control multiple elements at once. As another example, you can have one switch turn on/off multiple bulbs at the same time with a single message to a group address.

Virtual
A virtual address is assigned to one or more elements across one or more nodes. A virtual address takes the form of a 128-bit UUID that any element can be assigned to, like a label. This 128-bit address is used in calculating the message integrity check.
The 14 LSBs of the virtual address are set to a hash of the label UUID such that each hash represents many label UUIDs. When an access message is received for a virtual address with a matching hash, each corresponding label UUID is compared as part of the authentication to see if there is a matching element.
This may be used by a manufacturer to allow mesh networks including those devices to send messages to all similar devices at one time.

Bluetooth Mesh Messaging

The Bluetooth Mesh communication happens using BLE Advertising packets. There are two classes of messages in the Bluetooth Mesh, Control and Access.  By and large the control messages are used for network control, and you never see them as they are handled by the stack.  The Access messages are ones that matter to us as application developers.

We all know that the BLE advertising  packet is only 31 bytes long.  This makes things difficult as most of the packet is used up by network protocol overhead leaving only a few bytes for the actual message.  The good news is that the stack handles splitting up your payload into as many as 32 packets (called segmented) and getting them re-sequenced automatically.

I’m not very good at limits … but this is what you have to live with in BLE Mesh:

Message Type Max Payload Size (Octets)
Unsegmented Control or Access 11
Segmented Control 256
Segmented Access 376 or 380

Acknowledged vs. Unacknowledged
As the name suggests, acknowledged messages require a response from the node that it is addressed to. The response confirms that the message was received and it may also return data back to the sender (e.g. in response to a GET). If a sender does not receive the expected response from a message it may resend it. Messages must be idempotent so that a message received more than once is no different than if it had only been received once.

GET, SET, STATUS
All access messages are of the three broad types of GET, SET, and STATUS.
GET messages request a state value from one or more nodes. A GET message is always acknowledged.
SET messages are used to change the value of a state. A SET message can be either acknowledged or unacknowledged.
STATUS messages are sent in response to a GET, and acknowledged SET, or may also be sent by a node independently, for example, periodically using a timer. A STATUS message is always unacknowledged. Therefore, if a node sends a GET message but never receives a STATUS return message, it may resend the GET message.

BLE Mesh Publishing

From the BLE Mesh Spec “All communication within a mesh network is accomplished by sending messages. Messages operate on states. For each state, there is a defined set of messages that a server supports and a client may use to request a value of a state or to change a state. A server may also transmit unsolicited messages carrying information about states and/or changing states.”

The BLE Mesh Application communication scheme is based on the Publish/Subscribe & Client/Server paradigm and are embedded automatically into Access messages by the stack.  An Element may publish messages (either to a group or a unicast address).  And an Element may subscribe to messages from one or more groups.

Models

An Element is not very interesting without a mechanism to interact with it.  A Model is exactly that, specifically it is the Bluetooth SIG defined behavior and data/state of an Element.  Each Element in your Node will have one or more Models that are attached to it that you can think of as Servers which hold, send and receive data.

Models fall into three categories. Servers, Clients and Control (hybrid Server/Control)

Server:  Contains data and sends it out in response to Client GET requests or can update the data based on Client SET requests or may send Status based on changes in the Element.

Clients: Send GET requests to Servers or Send SET requests to Servers.

Control: A Hybrid Model that acts both as a Client and a Server.

Here is an example picture from the AN227069 – Getting Started with Bluetooth Mesh

To make this system work the Bluetooth SIG has also defined the standard behavior for a bunch of different models including:

States

From the Bluetooth Spec “A state is a value representing a condition of an element.”  States are associated with a particular server model. That is, the spec defines the states that apply to each server model and how they behave.

Complexity

There is quite a bit more going on in the Bluetooth Mesh specifications including the abilities to handle:

  • Scenes (e.g. go in a room and have all the lights, sound, hvac go to the right levels)
  • State binding – Multiple states are bound together such that when one changes the others change (e.g. you turn the volume so low that it becomes off)
  • State transition times (e.g. Fade the lights up or down over a set time period)
  • Alerts (e.g. notify the user with a blinking light)

Security

There are three levels of security in a Bluetooth Mesh network and access is governed by three keys.

NetKey
All nodes in a mesh network must possess the network key. In fact, possession of the NetKey is what makes a node a member of a given mesh network. The NetKey allows a node to decrypt and authenticate messages at the network Layer. The mesh packet header and address are encrypted and authenticated with the network key. This allows a node to perform relay functions, but it does NOT allow the relay node to decrypt the application data that is stored in a message.
AppKey
The mesh packet payload is encrypted and authenticated with the application key. Therefore, data for a specific application can only be decrypted by nodes that have the AppKey for that application. The AppKeys are used by the upper transport layer to decrypt and authenticate messages before passing them to the access layer.
The existence of AppKeys allows multiple applications to share a mesh network (and therefore gain all the benefits of having more nodes such as increased reliability and range) without each node having access to all messages.
For example, consider a mesh network that has lights, HVAC, and home security devices on it. The light fixtures and light switches would share an AppKey for lighting; the thermostats, furnace, and air conditioner would share an AppKey for HVAC; the door locks and alarm system would share an AppKey for home security. In this way, home security messages can only be decrypted by devices that are part of the home security system, etc.
DevKey
Each device has its own unique device key known only to itself and the provisioner device. This key is used for secure communication during configuration.

Provisioning and Configuration

When a node turns on for the first time it barely knows its own name.  It definitely does not know any of the security keys, its addresses (unicast or group) or any of its model configuration information.  So what does it do?  Simple – it starts to send out a BLE Advertising packet in the format of a BLE Mesh Beacon.  Then it waits for a provisioning device to make a BLE GATT connection to provision the node with the network information.  The provisioning process assigns the netkey and the unicast address of the primary  element.

After the device is provisioned it will be able to hear and decode BLE Mesh packets.  But, it won’t know much else to do until the Elements have been configured.  So, the next step for the provisioning application is to send the rest of the configuration information. e.g. group subscriptions, application keys etc.

Bluetooth Mesh Stack

Mouser Bluetooth Mesh: L4 Integrating the Modus Toolbox Code Examples

How To Design With Bluetooth Mesh


You can "git" a workspace will all of these files at https://github.com/iotexpert/MouserVTWBluetoothMesh or git@github.com:iotexpert/MouserVTWBluetoothMesh.git

Summary

In this lesson I will show you how to find and use code examples.  I’ll then provision a motion sensor into my Bluetooth Mesh network and test.  Here are the steps:

  1. How to Find the Code Examples
  2. How to Import the Code Examples
  3. Explore the Code Examples
  4. How to Use a Code Example
  5. Provision the Motion Sensor Into the Network
  6. Test the Motion Sensor

How to Find the Code Examples

In the ModusToolbox Quick Panel pick “Search Online for Code Examples”

You will find yourself in a web browser on the cypressemiconductorco GitHub site.

Click on the link “Bluetooth SDK Examples”

You can look around on the website at the examples.  Or …

How to Import the Code Examples

Click the “Clone or download” button.  This will give you this window which has the SSH or HTTPS address of the site.

Copy the location of the github repo “git@github.com:cypresssemiconductorco/Code-Examples-BT-SDK-for-ModusToolbox.git”.  Then go back to the Workspace Explorer.  Right click and select “Import…”

Choose Git–>Projects from Git

Select “Clone URI” (so you can get the examples from the Internet)

Paste in the URI (which you copied from the web browser) Then press Next–>

Pick out the master branch.

You will now need to tell Eclipse where you want it to save the repository on your local machine.  I let it select the default location and press Next –>

To make things easy, you can then import that directory full of goodness into your Workspace.  An excellent developer I know says that Code is all of the documentation you need.  Which means that it is nice to be able to browse the code in a good editor.

Explore the Code Examples

Now you should have the folder “Code-Examples-BT-20819A1-1.0-for-ModusToolbox” inside of your Workspace.

If you double click the “BT-SDK-1.2-Code-Examples.pdf” you will get a description of them all

You can walk through them using the Workspace Explorer.  Notice that we created examples for many of the Bluetooth Mesh Models.

Example Peer Apps

We had a problem with Peer Apps in the SDK1.2 release and this was fixed by adding the Peer Apps to the Example Projects.

How to use a Code Example

To create a project from one of the code examples, start by choosing “File->New->ModusToolbox IDE Application”

Pick the correct development kit “CYBT-213043-MESH”

Select “Import”

Navigate to the file “modus.mk” of the example you want.  This will be in the directory on your computer where you cloned the GitHub repository.

After doing the Import you will have that example as a Starter Application.  Notice that they have an asterisks beside the name.  Type the name of your application if you want to change it.  In this case I pick “VTW_Mesh_SensorMotion”.  Then press “Next->”

Click “Finish”

Now you will have the application in the Workspace.

You can now press “VTW_Mesh_SensorMotion Build + Program”

And you will end up with this in your console after sweet Success!!!

Provision the Motion Sensor into the Network

I now have a board programmed with the Motion Sensor Application.  Seems like that it will be a good idea to provision it into the network and see if it is doing anything.  Press “Scan Unprovisioned”.

Once I see my device, I’ll stop scanning and then press “Provision and configure”.  After a minute our tool will tell you that it is done!

Test the Motion Sensor

You can request the state of the sensor by clicking “Control” and then picking the motion sensor.  Now press the “Get” button for the sensor and you will see that the sensor is graced with my presence.

Each time I click “Get” you can see the transaction happening on the development kit.

Mouser Bluetooth Mesh: L3 Making a Light Switch Using the Example Code

How To Design With Bluetooth Mesh


You can "git" a workspace will all of these files at https://github.com/iotexpert/MouserVTWBluetoothMesh or git@github.com:iotexpert/MouserVTWBluetoothMesh.git

Summary

In the first several lessons I showed you how to build a BLE Mesh Network using the Cypress Android App and the Windows Application.  I don’t know about you, but even though I live twenty years back in time in Kentucky I still have light switches.  In this lesson Ill show you how to add a light switch to your network.  And then we will have a good look a the code because we have run enough demo projects.

Given that I have a working network from the last lesson I will just add the switch to that network.  Meaning, I will use the Mesh Client to provision in the new switch.

For this exercise we will:

  1. Create the VTW_MeshOnOffSwitch Project
  2. Provision and Configure the Switch
  3. Firmware Architecture
  4. Examine the Code

Create the VTW_MeshOnOffSwitch Project

Start a new project with File–>New–>ModusToolbox IDE Application

Pick out the CYBT-213043-MESH hardware.

I will use the BLE_Mesh_OnOffSwitch starter application.  Notice that I named it “VTW_Mesh_OnOffSwitch”

Now press Finish to create the application in your workspace.

Now your Project Explorer should look like this.  Notice that in the quick panel there are the launches for this application.

So, press “VTW_MeshOnOffSwitch Build + Program”

Provision and Configure the Switch

Go back to the Mesh Client application.  Just as before you can add the Switch to the network by scanning for unprovisioned BLE Mesh Nodes.  When the node shows up, give it a name of SW1 and then press “Provision and configure”

After a while, your trace window will indicate success via the ever informative “done” message.

If you have a serial terminal attached to the development kit you will see what happens during the provision/configuration process.

Now test the button on your new Light Switch and notice that both LEDs turn on (both Dimmable and L2).  You can configure the Switch to control only L2 if you want.  On the “Use Device” drop down pick “SW1 (0004)” then select control “ONOFF” and pick the switch “L2”.

After applying the change you can see that switch only controls L2.  You can then switch it to “Dimmable” to control the other LED.

Firmware Architecture

Examine the Code

A Cypress Bluetooth Mesh project has several basic sections  Specifically you must configure:

  1. Elements & Models
  2. Mesh Config
  3. Application Function Pointers
  4. mesh_app_init

Plus for the switch application, you will need to configure the button management code (what happens when the button is pushed).

    Elements and Models

    The “Element” is a fundamental unit of thingness in a BLE Mesh.  For instance in the projects we have looked at, the LEDs are represented by an Element, and the switch is represented by another element.  Each element will have one or more models that represent the functionality of the element.  For instance, in a switch you will have a “WICED_BT_MESH_MODEL_ONOFF_CLIENT”.   If you look at on_off_switch.c you will see that we first define an array of models.  Then we define an array of elements.  The array mesh_element1_models will be associated with the the first element.

    wiced_bt_mesh_core_config_model_t   mesh_element1_models[] =
    {
        WICED_BT_MESH_DEVICE,
        WICED_BT_MESH_MODEL_ONOFF_CLIENT,
    };
    #define MESH_APP_NUM_MODELS  (sizeof(mesh_element1_models) / sizeof(wiced_bt_mesh_core_config_model_t))
    
    #define ONOFF_SWITCH_ELEMENT_INDEX   0
    
    wiced_bt_mesh_core_config_element_t mesh_elements[] =
    {
        {
            .location = MESH_ELEM_LOC_MAIN,                                 // location description as defined in the GATT Bluetooth Namespace Descriptors section of the Bluetooth SIG Assigned Numbers
            .default_transition_time = MESH_DEFAULT_TRANSITION_TIME_IN_MS,  // Default transition time for models of the element in milliseconds
            .onpowerup_state = WICED_BT_MESH_ON_POWER_UP_STATE_RESTORE,     // Default element behavior on power up
            .default_level = 0,                                             // Default value of the variable controlled on this element (for example power, lightness, temperature, hue...)
            .range_min = 1,                                                 // Minimum value of the variable controlled on this element (for example power, lightness, temperature, hue...)
            .range_max = 0xffff,                                            // Maximum value of the variable controlled on this element (for example power, lightness, temperature, hue...)
            .move_rollover = 0,                                             // If true when level gets to range_max during move operation, it switches to min, otherwise move stops.
            .properties_num = 0,                                            // Number of properties in the array models
            .properties = NULL,                                             // Array of properties in the element.
            .sensors_num = 0,                                               // Number of sensors in the sensor array
            .sensors = NULL,                                                // Array of sensors of that element
            .models_num = MESH_APP_NUM_MODELS,                              // Number of models in the array models
            .models = mesh_element1_models,                                 // Array of models located in that element. Model data is defined by structure wiced_bt_mesh_core_config_model_t
        },
    };
    

    Mesh Config

    The mesh config structure handles the basic setup for the Cypress Bluetooth Mesh.  This structure is loaded in by the stack when it starts.

    wiced_bt_mesh_core_config_t  mesh_config =
    {
        .company_id         = MESH_COMPANY_ID_CYPRESS,                  // Company identifier assigned by the Bluetooth SIG
        .product_id         = MESH_PID,                                 // Vendor-assigned product identifier
        .vendor_id          = MESH_VID,                                 // Vendor-assigned product version identifier
        .firmware_id        = MESH_FWID,                                // Vendor-assigned firmware version identifier
        .replay_cache_size  = MESH_CACHE_REPLAY_SIZE,                   // Number of replay protection entries, i.e. maximum number of mesh devices that can send application messages to this device.
    #if defined(LOW_POWER_NODE) && (LOW_POWER_NODE == 1)
        .features           = WICED_BT_MESH_CORE_FEATURE_BIT_LOW_POWER, // A bit field indicating the device features. In Low Power mode no Relay, no Proxy and no Friend
        .friend_cfg         =                                           // Empty Configuration of the Friend Feature
        {
            .receive_window = 0,                                        // Receive Window value in milliseconds supported by the Friend node.
            .cache_buf_len  = 0,                                        // Length of the buffer for the cache
            .max_lpn_num    = 0                                         // Max number of Low Power Nodes with established friendship. Must be > 0 if Friend feature is supported. 
        },
        .low_power          =                                           // Configuration of the Low Power Feature
        {
            .rssi_factor           = 2,                                 // contribution of the RSSI measured by the Friend node used in Friend Offer Delay calculations.
            .receive_window_factor = 2,                                 // contribution of the supported Receive Window used in Friend Offer Delay calculations.
            .min_cache_size_log    = 3,                                 // minimum number of messages that the Friend node can store in its Friend Cache.
            .receive_delay         = 100,                               // Receive delay in 1ms units to be requested by the Low Power node.
            .poll_timeout          = 36000                              // Poll timeout in 100ms units to be requested by the Low Power node.
        },
    #else
        .features           = 0,                                        // no, support for proxy, friend, or relay
        .friend_cfg         =                                           // Empty Configuration of the Friend Feature
        {
            .receive_window        = 0,                                 // Receive Window value in milliseconds supported by the Friend node.
            .cache_buf_len         = 0,                                 // Length of the buffer for the cache
            .max_lpn_num           = 0                                  // Max number of Low Power Nodes with established friendship. Must be > 0 if Friend feature is supported. 
        },
        .low_power          =                                           // Configuration of the Low Power Feature
        {
            .rssi_factor           = 0,                                 // contribution of the RSSI measured by the Friend node used in Friend Offer Delay calculations.
            .receive_window_factor = 0,                                 // contribution of the supported Receive Window used in Friend Offer Delay calculations.
            .min_cache_size_log    = 0,                                 // minimum number of messages that the Friend node can store in its Friend Cache.
            .receive_delay         = 0,                                 // Receive delay in 1 ms units to be requested by the Low Power node.
            .poll_timeout          = 0                                  // Poll timeout in 100ms units to be requested by the Low Power node.
        },
    #endif
        .gatt_client_only          = WICED_FALSE,                       // Can connect to mesh over GATT or ADV
        .elements_num  = (uint8_t)(sizeof(mesh_elements) / sizeof(mesh_elements[0])),   // number of elements on this device
        .elements      = mesh_elements                                  // Array of elements for this device
    };
    

    Application Function Pointers

    The Bluetooth Mesh stack interacts with your program via functions that it runs when something is happening.  You can either accept the default behavior, or you can write your own function.

    /*
     * Mesh application library will call into application functions if provided by the application.
     */
    wiced_bt_mesh_app_func_table_t wiced_bt_mesh_app_func_table =
    {
        mesh_app_init,          // application initialization
        mesh_app_hardware_init, // hardware initialization
        NULL,                   // GATT connection status
        NULL,                   // attention processing
        NULL,                   // notify period set
        NULL,                   // WICED HCI command
        NULL,                   // LPN sleep
        NULL                    // factory reset
    };

    mesh_app_init

    The function mesh_app_init is called by the stack when the stack starts.  It is your opportunity to get things going.   In the “if” we setup information which makes it easier to see what is happening when you provision by adding that information to the scan response packet.

    The last thing we do in mesh_app_init function is initialize the onoff client model. This should be done for any models that your device uses. The model initialization functions can accept the name of a function that we want the stack to call when it receives messages from the network for that model. In this case, we don’t need to do anything in our firmware for onoff client messages, so we specify NULL.

    /******************************************************
     *               Function Definitions
     ******************************************************/
    void mesh_app_init(wiced_bool_t is_provisioned)
    {
    #if 0
        extern uint8_t wiced_bt_mesh_model_trace_enabled;
        wiced_bt_mesh_model_trace_enabled = WICED_TRUE;
    #endif
        wiced_bt_cfg_settings.device_name = (uint8_t *)"Switch";
        wiced_bt_cfg_settings.gatt_cfg.appearance = APPEARANCE_CONTROL_DEVICE_SLIDER;
    
    #if defined(LOW_POWER_NODE) && (LOW_POWER_NODE == 1)
        WICED_BT_TRACE("LPN Switch init provisioned:%d\n", is_provisioned);
    #else
        WICED_BT_TRACE("Switch init provisioned:%d\n", is_provisioned);
    #endif
    
        // Adv Data is fixed. Spec allows to put URI, Name, Appearance and Tx Power in the Scan Response Data.
        if (!is_provisioned)
        {
            wiced_bt_ble_advert_elem_t  adv_elem[3];
            uint8_t                     buf[2];
            uint8_t                     num_elem = 0;
    
            adv_elem[num_elem].advert_type = BTM_BLE_ADVERT_TYPE_NAME_COMPLETE;
            adv_elem[num_elem].len         = (uint16_t)strlen((const char*)wiced_bt_cfg_settings.device_name);
            adv_elem[num_elem].p_data      = wiced_bt_cfg_settings.device_name;
            num_elem++;
    
            adv_elem[num_elem].advert_type = BTM_BLE_ADVERT_TYPE_APPEARANCE;
            adv_elem[num_elem].len         = 2;
            buf[0]                         = (uint8_t)wiced_bt_cfg_settings.gatt_cfg.appearance;
            buf[1]                         = (uint8_t)(wiced_bt_cfg_settings.gatt_cfg.appearance >> 8);
            adv_elem[num_elem].p_data      = buf;
            num_elem++;
    
            wiced_bt_mesh_set_raw_scan_response_data(num_elem, adv_elem);
        }
    
        // This application does not check result of the transmission or status event from the
        // target device.  Initialize OnOff client library not registering the callback.
        wiced_bt_mesh_model_onoff_client_init(ONOFF_SWITCH_ELEMENT_INDEX, NULL, is_provisioned);
    }
    

    Button Management Code

    Now you just need to setup the button hardware in the mesh_app_hardware_init function to generate an interrupt when the button is pressed or released..

    void mesh_app_hardware_init(void)
    {
        /* Configure buttons available on the platform */
    #if defined(CYW20706A2)
        wiced_hal_gpio_configure_pin(WICED_GPIO_BUTTON, WICED_GPIO_BUTTON_SETTINGS(GPIO_EN_INT_BOTH_EDGE), WICED_GPIO_BUTTON_DEFAULT_STATE);
        wiced_hal_gpio_register_pin_for_interrupt(WICED_GPIO_BUTTON, button_interrupt_handler, NULL);
    #elif (defined(CYW20735B0) || defined(CYW20719B0) || defined(CYW20721B0))
        wiced_hal_gpio_register_pin_for_interrupt(WICED_GPIO_PIN_BUTTON, button_interrupt_handler, NULL);
        wiced_hal_gpio_configure_pin(WICED_GPIO_PIN_BUTTON, WICED_GPIO_BUTTON_SETTINGS, GPIO_PIN_OUTPUT_LOW);
    #else
        wiced_platform_register_button_callback(WICED_PLATFORM_BUTTON_1, button_interrupt_handler, NULL, GPIO_EN_INT_BOTH_EDGE);
        button_previous_value = platform_button[WICED_PLATFORM_BUTTON_1].default_state;
    #endif
    }

    The interrupt handler will call the function wiced_bt_mesh_model_onoff_client_set to publish the button pressed message.

    /*
     * Process interrupts from the button.
     */
    void button_interrupt_handler(void* user_data, uint8_t pin)
    {
        uint32_t value = wiced_hal_gpio_get_pin_input_status(pin);
        uint32_t current_time = wiced_bt_mesh_core_get_tick_count();
        uint32_t button_pushed_duration;
    
        if (value == button_previous_value)
        {
            WICED_BT_TRACE("interrupt_handler: duplicate pin:%d value:%d current_time:%d\n", pin, value, current_time);
            return;
        }
        button_previous_value = value;
    
        WICED_BT_TRACE("interrupt_handler: pin:%d value:%d current_time:%d\n", pin, value, current_time);
    
        if (value == platform_button[WICED_PLATFORM_BUTTON_1].button_pressed_value)
        {
            button_pushed_time = current_time;
            return;
        }
        // button is released
        button_pushed_duration = current_time - button_pushed_time;
        if (button_pushed_duration < 15000)
        {
            process_button_push(ONOFF_SWITCH_ELEMENT_INDEX);
        }
        else
        {
            // More than 15 seconds means factory reset
            mesh_application_factory_reset();
        }
    }
    
    void process_button_push(uint8_t element_idx)
    {
        static uint8_t onoff = 0;
        wiced_bt_mesh_onoff_set_data_t set_data;
    
        onoff ^= 1;
    
        set_data.onoff           = onoff;
        set_data.transition_time = WICED_BT_MESH_TRANSITION_TIME_DEFAULT;
        set_data.delay           = 0;
    
        wiced_bt_mesh_model_onoff_client_set(element_idx, &set_data);
    }
    

     

    Mouser Bluetooth Mesh: L2B Building a Mesh Network Using the Mesh Client

    How To Design With Bluetooth Mesh


    You can "git" a workspace will all of these files at https://github.com/iotexpert/MouserVTWBluetoothMesh or git@github.com:iotexpert/MouserVTWBluetoothMesh.git

    Summary

    In this lesson I will show you how to use the Windows Application called “Mesh Client” to control your Bluetooth Mesh network.  The Mesh Client program attaches to the Bluetooth adaptor in your computer and lets you control Bluetooth Mesh networks (genius of a marketing name).  This includes provisioning, configuring, operation, etc.  This lesson will mirror the previous one in that I will setup two dimmable lights but this time I will control them via PC based software instead of the Android app.

    For this exercise I will take you through the following steps:

    1. Reset the Two Kits by Reprogramming
    2. Run the Mesh Client
    3. Create a New Bluetooth Mesh Network
    4. Provision your L1 Dimmable Light to the Network
    5. Control the L1 Dimmable Light
    6. Add the other Dimmable Light to the Network & Test

    Reset the Two Kits by Reprogramming

    Click on the “VTW_Mesh_LightDimmable” project.  Then select “VTW_Mesh_LightDimmable Build + Program”.  Do this to both of your boards so that we can start fresh.

    Run the Mesh Client

    The Mesh Client application can be found in your ModusToolbox installation directory.  Here is the path:

    Edit: There is a bug in this release.  You need to get the MeshClient from the code example … Lesson 4

    When you run the Mesh Client it will look like this:

    Create a New Network

    Before it will do anything, you need to create a new network by giving it a name such as “Net2” and pressing “Create”.  This will populate all of the configuration files required to control the network.

    After the network is created, press the “Open” button.

    After the network configuration files load you will see a trace window like this:

    Provision your L1 Dimmable Light to the Network

    Now you have a network with no nodes.  Press “Scan Unprovisioned” to find an unprovisioned device, which are just BLE devices that are advertising as mesh beacons.

    After a bit, the scanner will find your “Dimmable Light”. You will see the device show up in “Provision UUID” with a name.   You can click Stop Scanning so that it doesn’t keep searching for other devices. Right here, I would recommend you change the device name.  Unfortunately I forgot when I captured these screens.  Oh well.  I hope when I do this live I won’t forget.  Now press “Provision and configure”.  After a some time that process will complete.

    While the provision and configuration process is going on you will see the console of the Dimmable light reacting.

    Control the L1 Dimmable Light

    Now that you have a provisioned node you can use the Mesh Client to control it.  Start by selecting which Node to control in the drop down dialog box.  In this case I chose “Dimmable light” which makes sense as it is the only device in my network.

    Now select “On” and “Set” which will turn on the LED.

    Add the other Dimmable Light to the Network & Test

    Unplug the Dimmable Light and plug it into something other than your computer.  Plug in the other unprovisioned board.  Back in the Mesh Client application press “Scan Unprovisioned”  When it comes back having found the new board,  give it a name “L2” and then press “Provision and configure”.  When I do this live during the workshop I will name the first board “L1” and the second “L2”.  But I’m not going to recapture these screen shots.

    Once L2 is provisioned into the network.  I can control it the same way I did with “L1”.  Select “L2” (and notice that I called the first light “Dimmable Light” rather than giving it a sensible name.

    Now select “On” and then press “Set”

    Finally, test and make sure that the first Dimmable Light is still working by turning it off:

    Mouser Bluetooth Mesh: L1 Developer Resources

    How To Design With Bluetooth Mesh


    You can "git" a workspace will all of these files at https://github.com/iotexpert/MouserVTWBluetoothMesh or git@github.com:iotexpert/MouserVTWBluetoothMesh.git

    Summary

    This lesson has a bunch of links to useful documentation about Bluetooth Mesh.  It includes links to all of the Cypress software and application notes.  It also includes links to the Bluetooth Special Interest Group website for the BLE Mesh Specs.

    Cypress Resources

    1. Cypress Bluetooth Mesh Landing Page
    2. AN227069 – Getting Started with Bluetooth Mesh
    3. EZ-BT Mesh Evaluation Kit Landing Page
    4. EZ-BT Mesh Evaluation Kit QuickStart
    5. Modus Toolbox
    6. Cypress Bluetooth Community
    7. Bluetooth SDK 1.2
    8. Modus Toolbox Bluetooth SDK Examples @ github
    9. Cypress WICED Bluetooth 101 – Class
    10. Mesh Client
    11. Mesh Client Documentation

    Bluetooth Sig Resources

    1. Bluetooth SIG Mesh Specs
    2. Bluetooth SIG Mesh Profile Spec
    3. Bluetooth SIG Mesh Model Spec
    4. Bluetooth SIG Mesh Device Properties
    5. Introducing Bluetooth Mesh Networking
    6. Intro Bluetooth Mesh Part 1
    7. Intro Bluetooth Mesh Part 2
    8. Bluetooth Mesh Fundamental Concepts of BT Mesh Networking Part1
    9. Bluetooth Mesh Fundamental Concepts of BT Mesh Networking Part2
    10. Bluetooth Mesh Networking: Friendship
    11. Management of Devices in a Bluetooth Mesh Network
    12. In-Market Bluetooth Low Energy Devices and Bluetooth Mesh Networking
    13. Bluetooth Mesh Security Overview
    14. Provisioning a Bluetooth Mesh Network Part 1
    15. Provisioning a Bluetooth Mesh Network Part 2

    Other Useful Links

    1. Wikipedia Bluetooth Mesh

    Cypress Bluetooth Mesh Landing Page

    AN227069 – Getting Started with Bluetooth Mesh

    EZ-BT Mesh Evaluation Kit Landing Page

    EZ-BT Mesh Evaluation Kit QuickStart

    Modus Toolbox

    Cypress Bluetooth Community

    Bluetooth SDK 1.2

    Modus Toolbox Bluetooth SDK Examples @ github

    Cypress WICED Bluetooth 101 – Class

    Mesh Client

    Mesh Client Documentation

    Bluetooth SIG Mesh Specs

    Bluetooth SIG Mesh Profile Spec

    Bluetooth SIG Mesh Model Spec

    Bluetooth SIG Mesh Device Properties

    Introducing Bluetooth Mesh Networking

    Intro Bluetooth Mesh Part 1

    Intro Bluetooth Mesh Part 2

    Bluetooth Mesh Fundamental Concepts of BT Mesh Networking Part1

    Bluetooth Mesh Fundamental Concepts of BT Mesh Networking Part2

    Bluetooth Mesh Networking: Friendship

    Management of Devices in a Bluetooth Mesh Network

    In-Market Bluetooth Low Energy Devices and Bluetooth Mesh Networking

    Bluetooth Mesh Security Overview

    Provisioning a Bluetooth Mesh Network Part 1

    Provisioning a Bluetooth Mesh Network Part 2