本文来源微信公众号【物联网思考】
LoRaWAN规范中有不同的数据包,通过MType
字段区分,MType
是3位的,总共可以表示8种不同类型的数据,其中前六种是不同的数据包,分别是“入网请求”、“入网回复”、“不需要确认上行数据包”、“需要确认上行数据包”、“不需要确认下行数据包”、“需要确认下行数据包”,后面两个一个是预留(RFU),一个开放给用户自定义(Proprietary)。
其中“入网请求”、“入网回复”,主要是用于OTAA入网的,在前面的LoRa节点开发——代码详解 LoRaWAN节点入网文章已经分析过了。
“不需要确认上行数据包”、“需要确认上行数据包”:主要用于用户上报数据。这里说一下不需要确认和需要确认,“需要确认”:就是发送数据后需要服务器回复一个ack,表明已经收到数据了,如果没有回复ack,那么还会重复发,一般用于紧急重要的数据上报;“不需要确认”:就是不管服务器有没有收到数据,发一次就不管了,一般用于非紧急不重要数据上报。
“不需要确认下行数据包”、“需要确认下行数据包”:主要服务器下发数据。不需要确认和需要确认同上面。服务器发送“需要确认”数据包时,需要节点回复ack给服务器。
/*!
* \brief Prepares the payload of the frame
*/
static void PrepareTxFrame( uint8_t port )
{
switch( port )
{
case 2:
{
AppDataSizeBackup = AppDataSize = 1;
AppDataBuffer[0] = AppLedStateOn;
}
break;
case 224:
if( ComplianceTest.LinkCheck == true )
{
ComplianceTest.LinkCheck = false;
AppDataSize = 3;
AppDataBuffer[0] = 5;
AppDataBuffer[1] = ComplianceTest.DemodMargin;
AppDataBuffer[2] = ComplianceTest.NbGateways;
ComplianceTest.State = 1;
}
else
{
switch( ComplianceTest.State )
{
case 4:
ComplianceTest.State = 1;
break;
case 1:
AppDataSize = 2;
AppDataBuffer[0] = ComplianceTest.DownLinkCounter >> 8;
AppDataBuffer[1] = ComplianceTest.DownLinkCounter;
break;
}
}
break;
default:
break;
}
}
可以看到,在PrepareTxFrame
这个函数中,应用只需要在AppDataBuffer
中填充相应的数据以及设置数据长度AppDataSize
即可。
static bool SendFrame( void )
{
McpsReq_t mcpsReq;
LoRaMacTxInfo_t txInfo;
if( LoRaMacQueryTxPossible( AppDataSize, &txInfo ) != LORAMAC_STATUS_OK )
{
// Send empty frame in order to flush MAC commands
mcpsReq.Type = MCPS_UNCONFIRMED;
mcpsReq.Req.Unconfirmed.fBuffer = NULL;
mcpsReq.Req.Unconfirmed.fBufferSize = 0;
mcpsReq.Req.Unconfirmed.Datarate = LORAWAN_DEFAULT_DATARATE;
}
else
{
if( IsTxConfirmed == false )
{
mcpsReq.Type = MCPS_UNCONFIRMED;
mcpsReq.Req.Unconfirmed.fPort = AppPort;
mcpsReq.Req.Unconfirmed.fBuffer = AppDataBuffer;
mcpsReq.Req.Unconfirmed.fBufferSize = AppDataSize;
mcpsReq.Req.Unconfirmed.Datarate = LORAWAN_DEFAULT_DATARATE;
}
else
{
mcpsReq.Type = MCPS_CONFIRMED;
mcpsReq.Req.Confirmed.fPort = AppPort;
mcpsReq.Req.Confirmed.fBuffer = AppDataBuffer;
mcpsReq.Req.Confirmed.fBufferSize = AppDataSize;
mcpsReq.Req.Confirmed.NbTrials = 8;
mcpsReq.Req.Confirmed.Datarate = LORAWAN_DEFAULT_DATARATE;
}
}
// Update global variable
AppData.MsgType = ( mcpsReq.Type == MCPS_CONFIRMED ) ? LORAMAC_HANDLER_CONFIRMED_MSG : LORAMAC_HANDLER_UNCONFIRMED_MSG;
AppData.Port = mcpsReq.Req.Unconfirmed.fPort;
AppData.Buffer = mcpsReq.Req.Unconfirmed.fBuffer;
AppData.BufferSize = mcpsReq.Req.Unconfirmed.fBufferSize;
LoRaMacStatus_t status;
status = LoRaMacMcpsRequest( &mcpsReq );
printf( "\r\n###### ===== MCPS-Request ==== ######\r\n" );
printf( "STATUS : %s\r\n", MacStatusStrings[status] );
if( status == LORAMAC_STATUS_OK )
{
return false;
}
return true;
}
一些紧急重要数据可以发送“需要确认数据包”,从SendFrame
这个函数中,可以看出需要发送“需要确认数据包”的时候,只需把IsTxConfirmed
这个参数设置true即可。
应用只需设置以上3个参数即可发送,数据准备好之后,就是协议栈组包了,LoRaMacMcpsRequest( &mcpsReq )
这个函数正是发送数据组包的函数,组包之后就是加密,最后就是射频发送了。
过程刚好和发送数据相反(上行数据),先是射频接收,接收到数据之后解密,用户应用数据处理。
查看static void ProcessRadioRxDone( void )
函数,可以看到使用switch case语句,通过macHdr.Bits.MType
字段对接收到的数据包进行了区分,FRAME_TYPE_DATA_CONFIRMED_DOWN
和FRAME_TYPE_DATA_UNCONFIRMED_DOWN
正是服务器的下行数据。
LoRaWAN协议栈在处理的时候,使用了设置标志位,然后回调函数的方法来处理。若有下发数据,则将 MacCtx.MacFlags.Bits.McpsInd
设置为1,如下:
// Provide always an indication, skip the callback to the user application,
// in case of a confirmed downlink retransmission.
MacCtx.MacFlags.Bits.McpsInd = 1;
协议栈中,也给了英文注释,跳转到应用回调函数。
在LoRaWAN协议栈初始化的时候,注册了几个函数,然后在满足条件的时候回调。
int main( void )
{
…………//代码过长,部分代码未截取
macPrimitives.MacMcpsConfirm = McpsConfirm;
macPrimitives.MacMcpsIndication = McpsIndication;
macPrimitives.MacMlmeConfirm = MlmeConfirm;
macPrimitives.MacMlmeIndication = MlmeIndication;
macCallbacks.GetBatteryLevel = BoardGetBatteryLevel;
macCallbacks.GetTemperatureLevel = NULL;
macCallbacks.NvmContextChange = NvmCtxMgmtEvent;
macCallbacks.MacProcessNotify = OnMacProcessNotify;
LoRaMacInitialization( &macPrimitives, &macCallbacks, ACTIVE_REGION );
…………//代码过长,部分代码未截取
}
其中,MlmeIndication
就是下发回调函数。
查看MlmeIndication
函数,如下:
static void McpsIndication( McpsIndication_t *mcpsIndication )
{
printf( "\r\n###### ===== MCPS-Indication ==== ######\r\n" );
printf( "STATUS : %s\r\n", EventInfoStatusStrings[mcpsIndication->Status] );
if( mcpsIndication->Status != LORAMAC_EVENT_INFO_STATUS_OK )
{
return;
}
switch( mcpsIndication->McpsIndication )
{
case MCPS_UNCONFIRMED:
{
break;
}
case MCPS_CONFIRMED:
{
break;
}
case MCPS_PROPRIETARY:
{
break;
}
case MCPS_MULTICAST:
{
break;
}
default:
break;
}
// Check Multicast
// Check Port
// Check Datarate
// Check FramePending
if( mcpsIndication->FramePending == true )
{
// The server signals that it has pending data to be sent.
// We schedule an uplink as soon as possible to flush the server.
OnTxNextPacketTimerEvent( NULL );
}
// Check Buffer
// Check BufferSize
// Check Rssi
// Check Snr
// Check RxSlot
if( ComplianceTest.Running == true )
{
ComplianceTest.DownLinkCounter++;
}
if( mcpsIndication->RxData == true )
{
switch( mcpsIndication->Port )
{
case 1: // The application LED can be controlled on port 1 or 2
case 2:
if( mcpsIndication->BufferSize == 1 )
{
AppLedStateOn = mcpsIndication->Buffer[0] & 0x01;
}
break;
case 224:
if( ComplianceTest.Running == false )
{
// Check compliance test enable command (i)
if( ( mcpsIndication->BufferSize == 4 ) &&
( mcpsIndication->Buffer[0] == 0x01 ) &&
( mcpsIndication->Buffer[1] == 0x01 ) &&
( mcpsIndication->Buffer[2] == 0x01 ) &&
( mcpsIndication->Buffer[3] == 0x01 ) )
{
IsTxConfirmed = false;
AppPort = 224;
AppDataSizeBackup = AppDataSize;
AppDataSize = 2;
ComplianceTest.DownLinkCounter = 0;
ComplianceTest.LinkCheck = false;
ComplianceTest.DemodMargin = 0;
ComplianceTest.NbGateways = 0;
ComplianceTest.Running = true;
ComplianceTest.State = 1;
MibRequestConfirm_t mibReq;
mibReq.Type = MIB_ADR;
mibReq.Param.AdrEnable = true;
LoRaMacMibSetRequestConfirm( &mibReq );
#if defined( REGION_EU868 ) || defined( REGION_RU864 ) || defined( REGION_CN779 ) || defined( REGION_EU433 )
LoRaMacTestSetDutyCycleOn( false );
#endif
}
}
else
{
ComplianceTest.State = mcpsIndication->Buffer[0];
switch( ComplianceTest.State )
{
case 0: // Check compliance test disable command (ii)
IsTxConfirmed = LORAWAN_CONFIRMED_MSG_ON;
AppPort = LORAWAN_APP_PORT;
AppDataSize = AppDataSizeBackup;
ComplianceTest.DownLinkCounter = 0;
ComplianceTest.Running = false;
MibRequestConfirm_t 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
break;
case 1: // (iii, iv)
AppDataSize = 2;
break;
case 2: // Enable confirmed messages (v)
IsTxConfirmed = true;
ComplianceTest.State = 1;
break;
case 3: // Disable confirmed messages (vi)
IsTxConfirmed = false;
ComplianceTest.State = 1;
break;
case 4: // (vii)
AppDataSize = mcpsIndication->BufferSize;
AppDataBuffer[0] = 4;
for( uint8_t i = 1; i < MIN( AppDataSize, LORAWAN_APP_DATA_MAX_SIZE ); i++ )
{
AppDataBuffer[i] = mcpsIndication->Buffer[i] + 1;
}
break;
case 5: // (viii)
{
MlmeReq_t mlmeReq;
mlmeReq.Type = MLME_LINK_CHECK;
LoRaMacStatus_t status = LoRaMacMlmeRequest( &mlmeReq );
printf( "\r\n###### ===== MLME-Request - MLME_LINK_CHECK ==== ######\r\n" );
printf( "STATUS : %s\r\n", MacStatusStrings[status] );
}
break;
case 6: // (ix)
{
// Disable TestMode and revert back to normal operation
IsTxConfirmed = LORAWAN_CONFIRMED_MSG_ON;
AppPort = LORAWAN_APP_PORT;
AppDataSize = AppDataSizeBackup;
ComplianceTest.DownLinkCounter = 0;
ComplianceTest.Running = false;
MibRequestConfirm_t 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
JoinNetwork( );
}
break;
case 7: // (x)
{
if( mcpsIndication->BufferSize == 3 )
{
MlmeReq_t mlmeReq;
mlmeReq.Type = MLME_TXCW;
mlmeReq.Req.TxCw.Timeout = ( uint16_t )( ( mcpsIndication->Buffer[1] << 8 ) | mcpsIndication->Buffer[2] );
LoRaMacStatus_t status = LoRaMacMlmeRequest( &mlmeReq );
printf( "\r\n###### ===== MLME-Request - MLME_TXCW ==== ######\r\n" );
printf( "STATUS : %s\r\n", MacStatusStrings[status] );
}
else if( mcpsIndication->BufferSize == 7 )
{
MlmeReq_t mlmeReq;
mlmeReq.Type = MLME_TXCW_1;
mlmeReq.Req.TxCw.Timeout = ( uint16_t )( ( mcpsIndication->Buffer[1] << 8 ) | mcpsIndication->Buffer[2] );
mlmeReq.Req.TxCw.Frequency = ( uint32_t )( ( mcpsIndication->Buffer[3] << 16 ) | ( mcpsIndication->Buffer[4] << 8 ) | mcpsIndication->Buffer[5] ) * 100;
mlmeReq.Req.TxCw.Power = mcpsIndication->Buffer[6];
LoRaMacStatus_t status = LoRaMacMlmeRequest( &mlmeReq );
printf( "\r\n###### ===== MLME-Request - MLME_TXCW1 ==== ######\r\n" );
printf( "STATUS : %s\r\n", MacStatusStrings[status] );
}
ComplianceTest.State = 1;
}
break;
case 8: // Send DeviceTimeReq
{
MlmeReq_t mlmeReq;
mlmeReq.Type = MLME_DEVICE_TIME;
LoRaMacStatus_t status = LoRaMacMlmeRequest( &mlmeReq );
printf( "\r\n###### ===== MLME-Request - MLME_DEVICE_TIME ==== ######\r\n" );
printf( "STATUS : %s\r\n", MacStatusStrings[status] );
}
break;
default:
break;
}
}
break;
default:
break;
}
}
// Switch LED 2 ON for each received downlink
GpioWrite( &Led2, 1 );
TimerStart( &Led2Timer );
const char *slotStrings[] = { "1", "2", "C", "C Multicast", "B Ping-Slot", "B Multicast Ping-Slot" };
printf( "\r\n###### ===== DOWNLINK FRAME %lu ==== ######\r\n", mcpsIndication->DownLinkCounter );
printf( "RX WINDOW : %s\r\n", slotStrings[mcpsIndication->RxSlot] );
printf( "RX PORT : %d\r\n", mcpsIndication->Port );
if( mcpsIndication->BufferSize != 0 )
{
printf( "RX DATA : \r\n" );
PrintHexBuffer( mcpsIndication->Buffer, mcpsIndication->BufferSize );
}
printf( "\r\n" );
printf( "DATA RATE : DR_%d\r\n", mcpsIndication->RxDatarate );
printf( "RX RSSI : %d\r\n", mcpsIndication->Rssi );
printf( "RX SNR : %d\r\n", mcpsIndication->Snr );
printf( "\r\n" );
}
应用可以在这里获取服务器下发的数据,也可以获取到下发信号的RSSI和SNR等。
至此,如何上报数据,下发接收数据分析完成。
——————END———————
推荐阅读:
LoRa节点开发——初识SDK
LoRa节点开发——构建keil工程
LoRa节点开发——SDK整体设计思路
LoRa节点开发——SDK整体设计思路