前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >LoRa节点开发——LoRaWAN节点入网代码详解

LoRa节点开发——LoRaWAN节点入网代码详解

作者头像
ManInRoad
发布2020-09-27 14:17:01
2.6K0
发布2020-09-27 14:17:01
举报
文章被收录于专栏:物联网思考物联网思考

本文主要结合LoRaNode SDK v4.4.2和LoRaWAN规范1.0.3来展开。

1、入网(激活)方式

可以看出,两种入网(激活)方式:

OTAA(Over-The-Air Activation):空中激活

ABP(Activation By Personalization):手动激活

2、空中激活

空中激活的过程,其实就是和服务器数据交换的过程,且当上行或下行消息丢失时,需要重新交换一次数据。

入网的过程,设备需要以下3个参数:

DevEUI:设备ID;

AppEUI:应用ID;

AppKey:128位的跟密钥,用于产生网络会话密钥NwkSKey 和应用会话密钥AppSKey。

2.1、入网过程

从节点的角度看,入网过程是与服务器的两次数据交换,分别是入网请求入网回复

2.2、入网请求信息

入网信息包含AppEUI、DevEUI、DevNone,DevNone是一个随机值 、入网请求是不加密的。

2.3、入网回复信息

如果设备被允许入网,网络服务器将会回复一个“入网回复”信息给到“入网请求”信息。

“入网回复”信息包括AppNonce、NetID、DevAddr、DLSettings、RxDelay、CFList字段。

AppNonce是服务器产生的随机值或者是以某种形式产生的唯一ID,用于终端设备计算NwkSKey和AppSKey。

3、手动激活

手动激活方式,没有“入网请求”和“入网回复”的过程。DevAddr、NwkSKey、AppSKey直接存储在终端设备中。手动激活的时候必须确保NwkSKey和AppSkey是唯一的。

4、代码分析

我们很容易看出,SDK工程用状态机在调度。

定义了6种状态,如下:

代码语言:javascript
复制
static enum eDeviceState
{
    DEVICE_STATE_RESTORE,
    DEVICE_STATE_START,
    DEVICE_STATE_JOIN,
    DEVICE_STATE_SEND,
    DEVICE_STATE_CYCLE,
    DEVICE_STATE_SLEEP
}DeviceState;

由于代码量很大,我们只截取main中while(1)中的代码:

代码语言:javascript
复制
while( 1 )
{
     // Process Radio IRQ
     if( Radio.IrqProcess != NULL )
     {
         Radio.IrqProcess( );
     }
     // Processes the LoRaMac events
     LoRaMacProcess( );
     switch( DeviceState )
     {
            case DEVICE_STATE_RESTORE:
            {
                // Try to restore from NVM and query the mac if possible.
                if( NvmCtxMgmtRestore( ) == NVMCTXMGMT_STATUS_SUCCESS )  //1.1.x以后才支持存储管理
                {
                    printf( "\r\n###### ===== CTXS RESTORED ==== ######\r\n\r\n" );
                }
                else
                {
#if( OVER_THE_AIR_ACTIVATION == 0 )  //不使用otaa
                    // Tell the MAC layer which network server version are we connecting too.
                    mibReq.Type = MIB_ABP_LORAWAN_VERSION;
                    mibReq.Param.AbpLrWanVersion.Value = ABP_ACTIVATION_LRWAN_VERSION;
                    LoRaMacMibSetRequestConfirm( &mibReq );
#endif

#if( ABP_ACTIVATION_LRWAN_VERSION == ABP_ACTIVATION_LRWAN_VERSION_V10x )
                    mibReq.Type = MIB_GEN_APP_KEY;
                    mibReq.Param.GenAppKey = GenAppKey;
                    LoRaMacMibSetRequestConfirm( &mibReq );
#else
                    mibReq.Type = MIB_APP_KEY;
                    mibReq.Param.AppKey = AppKey;
                    LoRaMacMibSetRequestConfirm( &mibReq );
#endif

                    mibReq.Type = MIB_NWK_KEY;
                    mibReq.Param.NwkKey = NwkKey;
                    LoRaMacMibSetRequestConfirm( &mibReq );

                    // Initialize LoRaMac device unique ID if not already defined in Commissioning.h
                    if( ( devEui[0] == 0 ) && ( devEui[1] == 0 ) &&
                        ( devEui[2] == 0 ) && ( devEui[3] == 0 ) &&
                        ( devEui[4] == 0 ) && ( devEui[5] == 0 ) &&
                        ( devEui[6] == 0 ) && ( devEui[7] == 0 ) )
                    {
                        BoardGetUniqueId( devEui );
                    }

                    mibReq.Type = MIB_DEV_EUI;
                    mibReq.Param.DevEui = devEui;
                    LoRaMacMibSetRequestConfirm( &mibReq );

                    mibReq.Type = MIB_JOIN_EUI;
                    mibReq.Param.JoinEui = joinEui;
                    LoRaMacMibSetRequestConfirm( &mibReq );

#if( OVER_THE_AIR_ACTIVATION == 0 ) //ABP方式,使用终端和服务器约定好的参数
                    // Choose a random device address if not already defined in Commissioning.h
                    if( DevAddr == 0 )
                    {
                        // Random seed initialization
                        srand1( BoardGetRandomSeed( ) );

                        // Choose a random device address
                        DevAddr = randr( 0, 0x01FFFFFF );
                    }

                    mibReq.Type = MIB_NET_ID;
                    mibReq.Param.NetID = LORAWAN_NETWORK_ID;
                    LoRaMacMibSetRequestConfirm( &mibReq );

                    mibReq.Type = MIB_DEV_ADDR;
                    mibReq.Param.DevAddr = DevAddr;
                    LoRaMacMibSetRequestConfirm( &mibReq );

                    mibReq.Type = MIB_F_NWK_S_INT_KEY;
                    mibReq.Param.FNwkSIntKey = FNwkSIntKey;
                    LoRaMacMibSetRequestConfirm( &mibReq );

                    mibReq.Type = MIB_S_NWK_S_INT_KEY;
                    mibReq.Param.SNwkSIntKey = SNwkSIntKey;
                    LoRaMacMibSetRequestConfirm( &mibReq );

                    mibReq.Type = MIB_NWK_S_ENC_KEY;
                    mibReq.Param.NwkSEncKey = NwkSEncKey;
                    LoRaMacMibSetRequestConfirm( &mibReq );

                    mibReq.Type = MIB_APP_S_KEY;
                    mibReq.Param.AppSKey = AppSKey;
                    LoRaMacMibSetRequestConfirm( &mibReq );
#endif
                }
                DeviceState = DEVICE_STATE_START;
                break;
            }

            case DEVICE_STATE_START:
            {
                TimerInit( &TxNextPacketTimer, OnTxNextPacketTimerEvent );

                TimerInit( &Led1Timer, OnLed1TimerEvent );
                TimerSetValue( &Led1Timer, 25 );

                TimerInit( &Led2Timer, OnLed2TimerEvent );
                TimerSetValue( &Led2Timer, 25 );

                mibReq.Type = MIB_PUBLIC_NETWORK;
                mibReq.Param.EnablePublicNetwork = LORAWAN_PUBLIC_NETWORK;
                LoRaMacMibSetRequestConfirm( &mibReq );

                mibReq.Type = MIB_ADR;
                mibReq.Param.AdrEnable = LORAWAN_ADR_ON;
                LoRaMacMibSetRequestConfirm( &mibReq );

#if defined( REGION_EU868 ) || defined( REGION_RU864 ) || defined( REGION_CN779 ) || defined( REGION_EU433 )
                LoRaMacTestSetDutyCycleOn( LORAWAN_DUTYCYCLE_ON );
#endif
                mibReq.Type = MIB_SYSTEM_MAX_RX_ERROR;
                mibReq.Param.SystemMaxRxError = 20;
                LoRaMacMibSetRequestConfirm( &mibReq );

                LoRaMacStart( );

                mibReq.Type = MIB_NETWORK_ACTIVATION;
                status = LoRaMacMibGetRequestConfirm( &mibReq );

                if( status == LORAMAC_STATUS_OK )
                {
                    if( mibReq.Param.NetworkActivation == ACTIVATION_TYPE_NONE ) //没有激活
                    {
                        DeviceState = DEVICE_STATE_JOIN;
                    }
                    else
                    {
                        DeviceState = DEVICE_STATE_SEND;
                        NextTx = true;
                    }
                }
                break;
            }
            case DEVICE_STATE_JOIN:
            {
                mibReq.Type = MIB_DEV_EUI;
                LoRaMacMibGetRequestConfirm( &mibReq );
                printf( "DevEui      : %02X", mibReq.Param.DevEui[0] );
                for( int i = 1; i < 8; i++ )
                {
                    printf( "-%02X", mibReq.Param.DevEui[i] );
                }
                printf( "\r\n" );
                mibReq.Type = MIB_JOIN_EUI; //其实就是appeui
                LoRaMacMibGetRequestConfirm( &mibReq );
                printf( "AppEui      : %02X", mibReq.Param.JoinEui[0] );
                for( int i = 1; i < 8; i++ )
                {
                    printf( "-%02X", mibReq.Param.JoinEui[i] );
                }
                printf( "\r\n" );
                printf( "AppKey      : %02X", NwkKey[0] );
                for( int i = 1; i < 16; i++ )
                {
                    printf( " %02X", NwkKey[i] );
                }
                printf( "\n\r\n" );
#if( OVER_THE_AIR_ACTIVATION == 0 )
                printf( "###### ===== JOINED ==== ######\r\n" );
                printf( "\r\nABP\r\n\r\n" );
                printf( "DevAddr     : %08lX\r\n", DevAddr );
                printf( "NwkSKey     : %02X", FNwkSIntKey[0] );
                for( int i = 1; i < 16; i++ )
                {
                    printf( " %02X", FNwkSIntKey[i] );
                }
                printf( "\r\n" );
                printf( "AppSKey     : %02X", AppSKey[0] );
                for( int i = 1; i < 16; i++ )
                {
                    printf( " %02X", AppSKey[i] );
                }
                printf( "\n\r\n" );

                mibReq.Type = MIB_NETWORK_ACTIVATION;
                mibReq.Param.NetworkActivation = ACTIVATION_TYPE_ABP;
                LoRaMacMibSetRequestConfirm( &mibReq );

                DeviceState = DEVICE_STATE_SEND;
#else
                JoinNetwork( );    //入网操作
#endif
                break;
            }
            case DEVICE_STATE_SEND:
            {
                if( NextTx == true )
                {
                    PrepareTxFrame( AppPort );

                    NextTx = SendFrame( );
                }
                DeviceState = DEVICE_STATE_CYCLE;
                break;
            }
            case DEVICE_STATE_CYCLE:
            {
                DeviceState = DEVICE_STATE_SLEEP;
                if( ComplianceTest.Running == true )
                {
                    // Schedule next packet transmission
                    TxDutyCycleTime = 5000; // 5000 ms
                }
                else
                {
                    // Schedule next packet transmission
                    TxDutyCycleTime = APP_TX_DUTYCYCLE + randr( -APP_TX_DUTYCYCLE_RND, APP_TX_DUTYCYCLE_RND );
                }

                // Schedule next packet transmission
                TimerSetValue( &TxNextPacketTimer, TxDutyCycleTime );
                TimerStart( &TxNextPacketTimer );
                break;
            }
            case DEVICE_STATE_SLEEP:
            {
                if( NvmCtxMgmtStore( ) == NVMCTXMGMT_STATUS_SUCCESS )
                {
                    printf( "\r\n###### ===== CTXS STORED ==== ######\r\n" );
                }

                CRITICAL_SECTION_BEGIN( );
                if( IsMacProcessPending == 1 )
                {
                    // Clear flag and prevent MCU to go into low power modes.
                    IsMacProcessPending = 0;
                }
                else
                {
                    // The MCU wakes up through events
                    BoardLowPowerHandler( );
                }
                CRITICAL_SECTION_END( );
                break;
            }
            default:
            {
                DeviceState = DEVICE_STATE_START;
                break;
            }
        }

依照上面的主循环里面的代码,我们画了一个流程图,如下:

可以看出:OTAA入网需要执行DEVICE_STATE_JOIN这个过程,入网之后上报数据;ABP是没有入网过程的,直接就上报数据了。最终在3个状态之间切换:

我们跟踪一下DEVICE_STATE_JOIN这个状态,看一下这个入网的过程:

函数体太长,我们仅列出函数名:

JoinNetwork——>LoRaMacMlmeRequest——>SendReJoinReq——>ScheduleTx——>SecureFrame——>LoRaMacCryptoPrepareJoinRequest——>LoRaMacSerializerJoinRequest——>SendFrameOnChannel——>Radio.Send

至此,入网请求消息,就通过射频发送出去了。

上述做了一系列的操作,其实就是封装数据包、加密数据,由此也可以看出LoRaWAN就是纯软件层面的东西。

我们重点看一下,LoRaMacSerializerJoinRequest这个函数:我们列出函数的原型如下:

代码语言:javascript
复制
LoRaMacSerializerStatus_t LoRaMacSerializerJoinRequest( LoRaMacMessageJoinRequest_t* macMsg )
{
    if( ( macMsg == 0 ) || ( macMsg->Buffer == 0 ) )
    {
        return LORAMAC_SERIALIZER_ERROR_NPE;
    }
    uint16_t bufItr = 0;
    // Check macMsg->BufSize
    if( macMsg->BufSize < LORAMAC_JOIN_REQ_MSG_SIZE )
    {
        return LORAMAC_SERIALIZER_ERROR_BUF_SIZE;
    }
    macMsg->Buffer[bufItr++] = macMsg->MHDR.Value;
    memcpyr( &macMsg->Buffer[bufItr], macMsg->JoinEUI, LORAMAC_JOIN_EUI_FIELD_SIZE );
    bufItr += LORAMAC_JOIN_EUI_FIELD_SIZE;
    memcpyr( &macMsg->Buffer[bufItr], macMsg->DevEUI, LORAMAC_DEV_EUI_FIELD_SIZE );
    bufItr += LORAMAC_DEV_EUI_FIELD_SIZE;
    macMsg->Buffer[bufItr++] = macMsg->DevNonce & 0xFF;
    macMsg->Buffer[bufItr++] = ( macMsg->DevNonce >> 8 ) & 0xFF;
    macMsg->Buffer[bufItr++] = macMsg->MIC & 0xFF;
    macMsg->Buffer[bufItr++] = ( macMsg->MIC >> 8 ) & 0xFF;
    macMsg->Buffer[bufItr++] = ( macMsg->MIC >> 16 ) & 0xFF;
    macMsg->Buffer[bufItr++] = ( macMsg->MIC >> 24 ) & 0xFF;
    macMsg->BufSize = bufItr;
    return LORAMAC_SERIALIZER_SUCCESS;
}

在这个函数里面实现了数据的封装,我们看到了MHDR、JoinEUI(特别说明一下JoinEUI和APPEUI是同一个东西)、DevEUI、DevNonce、MIC字段,MHDR是Mac数据头、MIC是数据一致性校验,剩下的3个字段,与我们上面从LoRaWAN规范join request一节中看到的一样。

我们再来看看,LoRaWAN规范里面讲的MAC消息格式:

这个图中小蓝框框起来的地方,正是我们这函数中数据封装的各个字段。

至此我们可以总结一下,入网请求数据包的格式:

MHDR

JoinEUI

DevEUI

DevNone

MIC

1byte

8byte

8byte

2byte

4byte

可以看出,入网请求包长度是1+8+8+2+4=23byte。

关于“入网回复”,我们先不管机制是怎么样的,我们暂时只查看相应的数据解包过程,我们类比发包的过程:先封包再发送,收包刚好和这个相反,收到数据包,再解包,查看每个字段。

static void ProcessRadioRxDone( void )这个函数就是对射频接收到的数据的处理了,可以看到使用switch case语句,通过macHdr.Bits.MType字段对接收到的数据包进行了区分,FRAME_TYPE_JOIN_ACCEPT这个类型的包,正是我们的“入网回复”数据,顺着往下跟踪

LoRaMacCryptoHandleJoinAccept——>LoRaMacParserJoinAccept,

正是在LoRaMacParserJoinAccept这个函数里面解析“入网回复”数据的,我们列出这个函数的原型如下:

代码语言:javascript
复制
LoRaMacParserStatus_t LoRaMacParserJoinAccept( LoRaMacMessageJoinAccept_t* macMsg )
{
    if( ( macMsg == 0 ) || ( macMsg->Buffer == 0 ) )
    {
        return LORAMAC_PARSER_ERROR_NPE;
    }

    uint16_t bufItr = 0;

    macMsg->MHDR.Value = macMsg->Buffer[bufItr++];
    memcpy1( macMsg->JoinNonce, &macMsg->Buffer[bufItr], 3 );
    bufItr = bufItr + 3;
    memcpy1( macMsg->NetID, &macMsg->Buffer[bufItr], 3 );
    bufItr = bufItr + 3;

    macMsg->DevAddr = ( uint32_t ) macMsg->Buffer[bufItr++];
    macMsg->DevAddr |= ( ( uint32_t ) macMsg->Buffer[bufItr++] << 8 );
    macMsg->DevAddr |= ( ( uint32_t ) macMsg->Buffer[bufItr++] << 16 );
    macMsg->DevAddr |= ( ( uint32_t ) macMsg->Buffer[bufItr++] << 24 );

    macMsg->DLSettings.Value = macMsg->Buffer[bufItr++];
    macMsg->RxDelay = macMsg->Buffer[bufItr++];
    if( ( macMsg->BufSize - LORAMAC_MIC_FIELD_SIZE - bufItr ) == LORAMAC_C_FLIST_FIELD_SIZE )
    {
        memcpy1( macMsg->CFList, &macMsg->Buffer[bufItr], LORAMAC_C_FLIST_FIELD_SIZE );
        bufItr = bufItr + LORAMAC_C_FLIST_FIELD_SIZE;
    }
    else if( ( macMsg->BufSize - LORAMAC_MIC_FIELD_SIZE - bufItr ) > 0 )
    {
        return LORAMAC_PARSER_FAIL;
    }
    macMsg->MIC = ( uint32_t ) macMsg->Buffer[bufItr++];
    macMsg->MIC |= ( ( uint32_t ) macMsg->Buffer[bufItr++] << 8 );
    macMsg->MIC |= ( ( uint32_t ) macMsg->Buffer[bufItr++] << 16 );
    macMsg->MIC |= ( ( uint32_t ) macMsg->Buffer[bufItr++] << 24 );

    return LORAMAC_PARSER_SUCCESS;
}

我们依次可以看到MHDR、JoinNonce(和规范中的AppNonce是同一个东西)、NetID、DevAddr、DLSettings、RxDelay、CFList、MIC字段,这也与我们上面在LoRaWAN规范里面看到的数据包吻合。

我们现在来总结一下数据入网回复数据包的格式:

MHDR

JoinNonce

NetID

DevAddr

DLSettings

RxDelay

CFList

MIC

1byte

3byte

3byte

4byte

1byte

1byte

nbyte

4byte

其中CFList这个字段是可选的,如果没有的话,就是0。

我们测试一下,看看“入网请求”和“入网回复”,如下:

入网请求的数据帧为:

00 01 00 00 00 00 00 00 00 DF 46 00 00 10 FF FF FF 0F A6 C4 38 42 A7

刚好是23个字节,可以看出,入网请求是没有加密的,每个代表字段含义如下:

00:MHDR

01 00 00 00 00 00 00 00:JoinEUI

DE 46 00 00 10 FF FF FF:DevEUI

0F A6:DevNone

C4 38 42 A7:MIC

入网回复的数据帧为:

20 69 e1 e7 2b 3e 0b 52 c9 c5 de 36 4f e2 69 41 25

是17+(0或16)个字节,20是MHDR,之后的数据是经过加密的,经过解密之后的数据如下:

7d c4 83 03 02 01 92 c5 f1 07 00 00 e4 5b 50 1b

7d c4 83 :JoinNonce

03 02 01 :NetID

92 c5 f1 07:DevAddr

00 :DLSettings

00 :RxDelay

e4 5b 50 1b:MIC

5、总结

OTAA入网:有入网过程,入网之后服务器分配DevAddr,节点计算出NwkSKey、AppSKey两个加密密钥。

ABP入网:无入网过程,DevAddr、NwkSKey、AppSKey直接存储在终端设备中(说直白一点就是,节点和服务器提前约定好了参数)。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 物联网思考 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
腾讯云代码分析
腾讯云代码分析(内部代号CodeDog)是集众多代码分析工具的云原生、分布式、高性能的代码综合分析跟踪管理平台,其主要功能是持续跟踪分析代码,观测项目代码质量,支撑团队传承代码文化。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档