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——————
本文分享自微信公众号 - 物联网思考(everythinglink),作者:everythinglink
原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。
原始发表时间:2019-12-27
本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。
我来说两句