Friday, September 4, 2020

Build an OpenThread Sleepy End Device Doorlock Prototype using Simplicity Studio v5 and EFR32XG21

The following steps show you how to build an OpenThread Sleepy End Device Doorlock Prototype using Simplicity Studio v5 and EFR32XG21

1. Start Simplicity Studio v5 and connect EFR32XG21 BRD4180A. Form Launcher tab, select sleepy-demo-mtd to create the project.


2. In Simplicity IDE tab, open sleepy-demo-mtd.slcp and click "FORCE GENERATION" button to generate related codes.


3. Change related network settings in setNetworkConfiguration function (main.c) so the device can join matched OpenThread Border Router network (refer to here to setup OpenThread Border Router).


4. Implement handleNetifStateChanged and call "otSetStateChangedCallback(instance, handleNetifStateChanged, instance);" after setNetworkConfiguration to setup sleepy end device doing polling.

void handleNetifStateChanged(uint32_t aFlags, void *aContext)
{
    otLinkModeConfig config;

    if ((aFlags & OT_CHANGED_THREAD_ROLE) != 0)
    {
        otDeviceRole changedRole = otThreadGetDeviceRole(aContext);

        switch (changedRole)
        {
        case OT_DEVICE_ROLE_LEADER:
        case OT_DEVICE_ROLE_ROUTER:
            break;

        case OT_DEVICE_ROLE_CHILD:
            config.mRxOnWhenIdle       = 0;
            config.mSecureDataRequests = true;
            config.mDeviceType         = 0;
            config.mNetworkData        = 0;
            otThreadSetLinkMode(instance, config);
            sAllowSleep = true;
            break;

        case OT_DEVICE_ROLE_DETACHED:
        case OT_DEVICE_ROLE_DISABLED:
            break;
        }
    }
}



5. Create coap server with resource URI "ddl/state".

5.1 Add the following includes, defines and global variables in main.c

#include <openthread/coap.h>
#include "utils/code_utils.h"

#define DDL_STATE_URI     "ddl/state"
#define DDL_STATE_LOCK    "lock"
#define DDL_STATE_UNLOCK  "unlock"
otCoapResource mResource_DDL_STATE;
const char mDDLStateUriPath[]=DDL_STATE_URI;
static uint8_t ddlState[15] = DDL_STATE_UNLOCK;

5.2 Add the following coap handler function ddl_state_coapHandler.

static void ddl_state_coapHandler(void *aContext, otMessage *aMessage,
                             const otMessageInfo *aMessageInfo)
{
    otError error = OT_ERROR_NONE;
    otMessage *responseMessage;
    otCoapCode responseCode = OT_COAP_CODE_CHANGED;
    otCoapCode messageCode = otCoapMessageGetCode(aMessage);
    otCoapType messageType = otCoapMessageGetType(aMessage);

    responseMessage = otCoapNewMessage((otInstance*)aContext, NULL);
    otEXPECT_ACTION(responseMessage != NULL, error = OT_ERROR_NO_BUFS);

    otCoapMessageInitResponse(responseMessage, aMessage, OT_COAP_TYPE_ACKNOWLEDGMENT, responseCode);
    otCoapMessageSetToken(responseMessage, otCoapMessageGetToken(aMessage),
                         otCoapMessageGetTokenLength(aMessage));
    otCoapMessageSetPayloadMarker(responseMessage);

    if(OT_COAP_CODE_GET == messageCode)
    {
        error = otMessageAppend(responseMessage, ddlState,
                                strlen((const char*)ddlState));
        otEXPECT(OT_ERROR_NONE == error);

        error = otCoapSendResponse((otInstance*)aContext, responseMessage,
                                   aMessageInfo);
        otEXPECT(OT_ERROR_NONE == error);
    }
    else if(OT_COAP_CODE_POST == messageCode)
    {
        char data[32];
        uint16_t offset = otMessageGetOffset(aMessage);
        uint16_t read = otMessageRead(aMessage, offset, data, sizeof(data) - 1);
        data[read] = '\0';

        /* process message */
        if(strcmp(DDL_STATE_LOCK, data) == 0)
        {
            /* update the attribute state */
            strcpy((char *)ddlState, DDL_STATE_LOCK);
        }
        else if(strcmp(DDL_STATE_UNLOCK, data) == 0)
        {
            /* update the attribute state */
            strcpy((char *)ddlState, DDL_STATE_UNLOCK);
        }
        else
        {
            /* no valid body, fail without response */
            otEXPECT_ACTION(false, error = OT_ERROR_NO_BUFS);
        }

        if (OT_COAP_TYPE_CONFIRMABLE == messageType)
        {
            error = otMessageAppend(responseMessage, ddlState,
                                    strlen((const char*)ddlState));
            otEXPECT(OT_ERROR_NONE == error);

            error = otCoapSendResponse((otInstance*)aContext,
                                       responseMessage, aMessageInfo);
            otEXPECT(OT_ERROR_NONE == error);
        }
    }

exit:

    if (error != OT_ERROR_NONE && responseMessage != NULL)
    {
        otMessageFree(responseMessage);
    }
}

5.3 Add the following code to start coap service and resource right after calling "otThreadSetEnabled(instance, true);" in main function.

    otCoapStart(instance,OT_DEFAULT_COAP_PORT);
    mResource_DDL_STATE.mUriPath = mDDLStateUriPath;
    mResource_DDL_STATE.mContext = instance;
    mResource_DDL_STATE.mHandler = &ddl_state_coapHandler;
    strncpy(mDDLStateUriPath, DDL_STATE_URI, sizeof(DDL_STATE_URI));
    otCoapAddResource(instance,&mResource_DDL_STATE);

 

6. Add the capability to output IPv6 addree of Sleepy End Device.

6.1 Add the following two global variables in main.c

char str[256];
int str_idx;

6.2 Add the following funtions in main.c

inline uint16_t Swap16(uint16_t v)
{
    return (((v & 0x00ffU) << 8) & 0xff00) | (((v & 0xff00U) >> 8) & 0x00ff);
}

inline uint16_t HostSwap16(uint16_t v)
{
    return Swap16(v);
}

void printIPv6Addr(void)
{
  const otNetifAddress *unicastAddrs;

  str_idx=0;
  memset(str,0x0,256);
  sprintf(str,"OpenThread Doorlock COAP Demo\r\nipaddr\r\n");
  str_idx=strlen(str);
  unicastAddrs = otIp6GetUnicastAddresses(instance);
  const otNetifAddress *addr = unicastAddrs;
  for (; addr; addr = addr->mNext){
      str_idx=strlen(str);
      sprintf(&str[str_idx],"%x:%x:%x:%x:%x:%x:%x:%x\r\n",
              HostSwap16(addr->mAddress.mFields.m16[0]), HostSwap16(addr->mAddress.mFields.m16[1]),
              HostSwap16(addr->mAddress.mFields.m16[2]), HostSwap16(addr->mAddress.mFields.m16[3]), HostSwap16(addr->mAddress.mFields.m16[4]),
              HostSwap16(addr->mAddress.mFields.m16[5]), HostSwap16(addr->mAddress.mFields.m16[6]), HostSwap16(addr->mAddress.mFields.m16[7])
             );
  }
  otPlatUartSend(str,sizeof(str));
}

 

6.3 Add calling printIPv6Addr in sl_button_on_change to print IPv6 address when button 1 is pressed.



7. Build and download sleepy-demo-mtd.hex into your EFR32XG21 BRD4180A.

 


8. Reset EFR32XG21 BRD4180A to join OpenThread Border Router and press PB1 on BRD4001A to output IPv6 address of Sleepy End Device.



9. Now, you can use libcoap to get and set doorlock state through coap server running on EFR32XG21 BRD4180A Sleepy End Device.



P.S. You can use Simplicity Studio Energy Profiler to check power consumption of Sleepy End Device and make sure it goes to sleep mode when not doing polling.

 



1 comment:

  1. Is it work for Non confirmable message?
    Can you provide any reference to Multicast CoAp

    ReplyDelete