本文來源微信公衆號【物聯網思考】
本文主要結合LoRaNode SDK v4.4.2和LoRaWAN規範1.0.3來展開。
1、數據包類型
LoRaWAN規範中有不同的數據包,通過MType
字段區分,MType
是3位的,總共可以表示8種不同類型的數據,其中前六種是不同的數據包,分別是“入網請求”、“入網回覆”、“不需要確認上行數據包”、“需要確認上行數據包”、“不需要確認下行數據包”、“需要確認下行數據包”,後面兩個一個是預留(RFU),一個開放給用戶自定義(Proprietary)。
其中“入網請求”、“入網回覆”,主要是用於OTAA入網的,在前面的LoRa節點開發——代碼詳解 LoRaWAN節點入網文章已經分析過了。
“不需要確認上行數據包”、“需要確認上行數據包”:主要用於用戶上報數據。這裏說一下不需要確認和需要確認,“需要確認”:就是發送數據後需要服務器回覆一個ack,表明已經收到數據了,如果沒有回覆ack,那麼還會重複發,一般用於緊急重要的數據上報;“不需要確認”:就是不管服務器有沒有收到數據,發一次就不管了,一般用於非緊急不重要數據上報。
“不需要確認下行數據包”、“需要確認下行數據包”:主要服務器下發數據。不需要確認和需要確認同上面。服務器發送“需要確認”數據包時,需要節點回復ack給服務器。
2、源碼分析
2.1上行數據
/*!
* \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 )
這個函數正是發送數據組包的函數,組包之後就是加密,最後就是射頻發送了。
2.2下行數據
過程剛好和發送數據相反(上行數據),先是射頻接收,接收到數據之後解密,用戶應用數據處理。
查看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整體設計思路
歡迎關注公衆號:“物聯網思考”,獲取更多開發資料、經驗。