send GPS uplinks in Cayenne LPP format
Dependencies: lib_gps lorawan1v1
radio chip selection
Radio chip driver is not included, because two options are available.
If you're using SX1272 or SX1276, then import sx127x driver into your program.
if you're using SX1261 or SX1262, then import sx126x driver into your program.
If you're using NAmote72 or Murata discovery, then you must import only sx127x driver.
See LoRaWAN library page for provisioning onto network: Commissioning.h
Connection To GPS Receiver
This demonstration depends on GPS receiver sending NMEA into one of the UART on your target CPU.
Such as GP-20U7.
Sends uplink in cayenne LPP format.
Connection to NUCLEO form factor
Jumper for auto-uplink: PC6->PC8 = CN10-2->CN10-4
name | connector pin | CPU pin |
Vdd | CN7-5 | |
Gnd | CN7-8 | |
UART | CN7-2 | PC11 |
Connection to DISCO_L072CZ_LRWAN1
Jumper for auto-uplink: PA11->PA12 = CN3-14->CN3-15
name | connector pin | CPU pin |
Vdd | CN3-5 | |
Gnd | CN3-6 | |
UART | CN3-21 | PA10 |
main.cpp
- Committer:
- Wayne Roberts
- Date:
- 2018-08-14
- Revision:
- 3:2f57b7856222
- Parent:
- 2:fdd36ff9b181
File content as of revision 3:2f57b7856222:
/* / _____) _ | | ( (____ _____ ____ _| |_ _____ ____| |__ \____ \| ___ | (_ _) ___ |/ ___) _ \ _____) ) ____| | | || |_| ____( (___| | | | (______/|_____)_|_|_| \__)_____)\____)_| |_| (C)2018 Semtech Description: LoRaMac classB device implementation License: Revised BSD License, see LICENSE.TXT file include in the project */ #include "LoRaMac1v1.h" #include "LoRaMacString.h" #include "gps.h" /*! * Defines the application data transmission duty cycle. 5s, value in [ms]. */ #define APP_TX_DUTYCYCLE_us 8000000 /*! * Defines a random delay for application data transmission duty cycle. 1s, * value in [ms]. */ #define APP_TX_DUTYCYCLE_RND_us 2000000 /*! * Default datarate */ #if defined( USE_BAND_ARIB_8CH ) #define LORAWAN_DEFAULT_DATARATE DR_3 #else #define LORAWAN_DEFAULT_DATARATE DR_0 #endif /*! * LoRaWAN confirmed messages */ #define LORAWAN_CONFIRMED_MSG_ON false /*! * LoRaWAN Adaptive Data Rate * * \remark Please note that when ADR is enabled the end-device should be static */ #define LORAWAN_ADR_ON 1 #if defined( USE_BAND_868 ) /*! * LoRaWAN ETSI duty cycle control enable/disable * * \remark Please note that ETSI mandates duty cycled transmissions. Use only for test purposes */ #define LORAWAN_DUTYCYCLE_ON false #define USE_SEMTECH_DEFAULT_CHANNEL_LINEUP 1 #if( USE_SEMTECH_DEFAULT_CHANNEL_LINEUP == 1 ) #define LC4 { 867100000, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } #define LC5 { 867300000, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } #define LC6 { 867500000, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } #define LC7 { 867700000, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } #define LC8 { 867900000, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } #define LC9 { 868800000, { ( ( DR_7 << 4 ) | DR_7 ) }, 2 } #define LC10 { 868300000, { ( ( DR_6 << 4 ) | DR_6 ) }, 1 } #endif #endif /*! * LoRaWAN application port */ #define LORAWAN_APP_PORT 2 /*! * User application data buffer size */ #define LORAWAN_APP_DATA_SIZE 3 #ifdef LORAWAN_JOIN_EUI static uint8_t DevEui[] = LORAWAN_DEVICE_EUI; static const uint8_t JoinEui[] = LORAWAN_JOIN_EUI; static const uint8_t NwkKey[] = LORAWAN_ROOT_NWKKEY; #ifdef LORAWAN_ROOT_APPKEY static const uint8_t AppKey[] = LORAWAN_ROOT_APPKEY; #endif #else static const uint8_t FNwkSIntKey[] = LORAWAN_FNwkSIntKey; static const uint8_t AppSKey[] = LORAWAN_APPSKEY; static const uint32_t DevAddr = LORAWAN_DEVICE_ADDRESS; #if defined(LORAWAN_SNwkSIntKey) && defined(LORAWAN_NwkSEncKey) static const uint8_t SNwkSIntKey[] = LORAWAN_SNwkSIntKey; static const uint8_t NwkSEncKey[] = LORAWAN_NwkSEncKey; #endif #endif #ifdef TARGET_DISCO_L072CZ_LRWAN1 #define GREEN LED1 #define RED2 LED2 #define BLUE LED3 #define RED4 LED4 DigitalOut blue(BLUE); #define LED_ON 1 #define LED_OFF 0 #endif /* TARGET_DISCO_L072CZ_LRWAN1 */ /*! * Application port */ static uint8_t AppPort = LORAWAN_APP_PORT; /*! * User application data size */ static uint8_t gAppDataSize = LORAWAN_APP_DATA_SIZE; /*! * User application data buffer size */ #define LORAWAN_APP_DATA_MAX_SIZE 64 /*! * User application data */ static uint8_t AppData[LORAWAN_APP_DATA_MAX_SIZE]; /*! * Indicates if the node is sending confirmed or unconfirmed messages */ static uint8_t gIsTxConfirmed = LORAWAN_CONFIRMED_MSG_ON; /*! * Timer to handle the application data transmission duty cycle * */ LowPowerTimeout tx_timeout; /*! * Indicates if a new packet can be sent */ static volatile struct { uint8_t startup : 1; uint8_t ackDownlink : 1; } flags; /*! * Device states */ volatile enum eDevicState { /* 0 */ DEVICE_STATE_INIT = 0, /* 1 */ DEVICE_STATE_SEND, /* 2 */ DEVICE_STATE_SLEEP, #ifdef LORAWAN_JOIN_EUI /* 3 */ DEVICE_STATE_JOIN, /* 4 */ DEVICE_STATE_JOIN_OK #endif /* LORAWAN_JOIN_EUI */ } DeviceState, WakeUpState; #if defined(TARGET_MOTE_L152RC) && !defined(TARGET_FF_ARDUINO) #define TARGET_FF_ARDUINO #endif #if defined(TARGET_FF_ARDUINO) #ifdef TARGET_DISCO_L072CZ_LRWAN1 #define LED_GREEN LED1 #define LED_RED2 LED2 // next to LD7 #define LED_BLUE LED3 #define LED_RED4 LED4 InterruptIn button_pin(USER_BUTTON); #define BUTTON_PRESSED 0 DigitalOut extLed(LED_RED4); #else InterruptIn button_pin(D8); #define BUTTON_PRESSED 1 DigitalOut extLed(D15); #endif #endif /* TARGET_FF_ARDUINO */ #define JUMPER_ENABLE #if defined(JUMPER_ENABLE) #define TX_INTERVAL_US 15000000 #if defined(TARGET_FF_MORPHO) #ifdef TARGET_DISCO_L072CZ_LRWAN1 DigitalOut jumper_out(PA_11); InterruptIn jumper_in(PA_12); #else DigitalOut jumper_out(PC_8); InterruptIn jumper_in(PC_6); #endif #endif /* TARGET_FF_MORPHO */ #endif /* JUMPER_ENABLE */ uint8_t c_ch; us_timestamp_t buttonStartAt; #ifdef TARGET_FF_MORPHO /* https://www.sparkfun.com/products/13740 */ #ifdef TARGET_DISCO_L072CZ_LRWAN1 GPS gps(PA_9, PA_10, NC); #else GPS gps(PC_10, PC_11, NC); #endif #endif /* TARGET_FF_MORPHO */ /*! * LoRaWAN compliance tests support data */ struct ComplianceTest_s { bool Running; uint8_t State; bool IsTxConfirmed; uint8_t AppPort; uint8_t AppDataSize; uint8_t *AppDataBuffer; uint16_t DownLinkCounter; bool LinkCheck; uint8_t DemodMargin; uint8_t NbGateways; }ComplianceTest; #ifdef JUMPER_ENABLE void autoUplink() { if (jumper_in.read() == 1) { tx_timeout.attach_us(autoUplink, TX_INTERVAL_US); } c_ch = 0xff; DeviceState = DEVICE_STATE_SEND; } void jumper_callback() { tx_timeout.attach_us(autoUplink, TX_INTERVAL_US); } #endif /* JUMPER_ENABLE */ #define LPP_DIGITAL_INPUT 0 // 1 byte #define LPP_DIGITAL_OUTPUT 1 // 1 byte #define LPP_ANALOG_INPUT 2 // 2 bytes, 0.01 signed #define LPP_ANALOG_OUTPUT 3 // 2 bytes, 0.01 signed #define LPP_LUMINOSITY 101 // 2 bytes, 1 lux unsigned #define LPP_PRESENCE 102 // 1 byte, 1 #define LPP_TEMPERATURE 103 // 2 bytes, 0.1°C signed #define LPP_RELATIVE_HUMIDITY 104 // 1 byte, 0.5% unsigned #define LPP_ACCELEROMETER 113 // 2 bytes per axis, 0.001G #define LPP_BAROMETRIC_PRESSURE 115 // 2 bytes 0.1 hPa Unsigned #define LPP_GYROMETER 134 // 2 bytes per axis, 0.01 °/s #define LPP_GPS 136 // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01m // Data ID + Data Type + Data Size #define LPP_DIGITAL_INPUT_SIZE 3 #define LPP_DIGITAL_OUTPUT_SIZE 3 #define LPP_ANALOG_INPUT_SIZE 4 #define LPP_ANALOG_OUTPUT_SIZE 4 #define LPP_LUMINOSITY_SIZE 4 #define LPP_PRESENCE_SIZE 3 #define LPP_TEMPERATURE_SIZE 4 #define LPP_RELATIVE_HUMIDITY_SIZE 3 #define LPP_ACCELEROMETER_SIZE 8 #define LPP_BAROMETRIC_PRESSURE_SIZE 4 #define LPP_GYROMETER_SIZE 8 #define LPP_GPS_SIZE 11 #define CAYENNE_CH_GPS 5 /*! * \brief Prepares the payload of the frame */ static void PrepareTxFrame( uint8_t port ) { int32_t lat, lon, alt; while (button_pin.read() == BUTTON_PRESSED) { us_timestamp_t duration = LoRaMacReadTimer() - buttonStartAt; if (duration > 1000000) { gAppDataSize = 0; AppData[gAppDataSize++] = 0; return; } } gps.service(); lat = gps.Latitude * 10000; lon = gps.Longitude * 10000; alt = atoi(gps.NmeaGpsData.NmeaAltitude) * 100; gAppDataSize = 0; AppData[gAppDataSize++] = CAYENNE_CH_GPS; AppData[gAppDataSize++] = LPP_GPS; AppData[gAppDataSize++] = lat >> 16; AppData[gAppDataSize++] = lat >> 8; AppData[gAppDataSize++] = lat; AppData[gAppDataSize++] = lon >> 16; AppData[gAppDataSize++] = lon >> 8; AppData[gAppDataSize++] = lon; AppData[gAppDataSize++] = alt >> 16; AppData[gAppDataSize++] = alt >> 8; AppData[gAppDataSize++] = alt; } /*! * \brief Prepares the payload of the frame * * \retval [0: frame could be send, 1: error] */ static LoRaMacStatus_t SendFrame(bool IsTxConfirmed, uint8_t AppDataSize) { LoRaMacStatus_t status; char str[64]; 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.fBuffer = NULL; mcpsReq.Req.fBufferSize = 0; mcpsReq.Req.Datarate = LORAWAN_DEFAULT_DATARATE; } else { if( IsTxConfirmed == false ) { mcpsReq.Type = MCPS_UNCONFIRMED; mcpsReq.Req.fPort = AppPort; mcpsReq.Req.fBuffer = AppData; mcpsReq.Req.fBufferSize = AppDataSize; mcpsReq.Req.Datarate = LORAWAN_DEFAULT_DATARATE; } else { mcpsReq.Type = MCPS_CONFIRMED; mcpsReq.Req.fPort = AppPort; mcpsReq.Req.fBuffer = AppData; mcpsReq.Req.fBufferSize = AppDataSize; mcpsReq.Req.Datarate = LORAWAN_DEFAULT_DATARATE; } } status = LoRaMacMcpsRequest( &mcpsReq ); if (status == LORAMAC_STATUS_OK) { APP_PRINTF("sendFrame() OK "); if (mcpsReq.Req.fBufferSize > 0) { APP_PRINTF("FPort%u ", mcpsReq.Req.fPort); print_buf(AppData, mcpsReq.Req.fBufferSize, "uplink"); } } else { LoRaMacStatus_to_string(status, str); APP_PRINTF("sendFrame() \e[31m%s rx%d\r\n", str, LoRaMacGetRxSlot()); } return status; } // ..SendFrame() #define ACK_DEFER_US 800000 void ack_isr() { SendFrame(false, 0); } /*! * \brief MCPS-Confirm event function * * \param [IN] mcpsConfirm - Pointer to the confirm structure, * containing confirm attributes. */ static void McpsConfirm( const McpsConfirm_t *mcpsConfirm ) { char str[64]; APP_PRINTF("McpsConfirm up:%" PRIu32 "hz ", mcpsConfirm->UpLinkFreqHz); LoRaMacEventInfoStatus_to_string(mcpsConfirm->Status, str); if (mcpsConfirm->AckReceived) APP_PRINTF("Ack "); APP_PRINTF("dr%u FCntUp:%" PRIu32 " ", mcpsConfirm->Datarate, mcpsConfirm->UpLinkCounter); if (mcpsConfirm->Status == LORAMAC_EVENT_INFO_STATUS_OK) { APP_PRINTF("%s\r\n", str); } else { APP_PRINTF("\e[31m%s\e[0m\r\n", str); } if (mcpsConfirm->Status == LORAMAC_EVENT_INFO_STATUS_OK) { switch( mcpsConfirm->McpsRequest ) { case MCPS_UNCONFIRMED: { // Check Datarate // Check TxPower break; } case MCPS_CONFIRMED: { // Check Datarate // Check TxPower // Check AckReceived // Check NbTrials //LoRaMacUplinkStatus.Acked = mcpsConfirm->AckReceived; break; } case MCPS_PROPRIETARY: { break; } default: break; } DeviceState = DEVICE_STATE_SLEEP; } else { /* fail */ DeviceState = DEVICE_STATE_SLEEP; #if 0 if (flags.startup && mcpsConfirm->Status == LORAMAC_EVENT_INFO_STATUS_RX2_TIMEOUT) { } #endif /* if 0 */ } } // ..McpsConfirm() /*! * \brief MCPS-Indication event function * * \param [IN] mcpsIndication - Pointer to the indication structure, * containing indication attributes. */ static void McpsIndication( const McpsIndication_t *mcpsIndication ) { char str[64]; int8_t Snr; APP_PRINTF("McpsIndication rx%d ADR_ACK_CNT:%u ", mcpsIndication->RxSlot, mcpsIndication->ADR_ACK_CNT); if (mcpsIndication->Snr & 0x80) { // The SNR sign bit is 1 // Invert and divide by 4 Snr = ( ( ~mcpsIndication->Snr + 1 ) & 0xFF ) >> 2; Snr = -Snr; } else { // Divide by 4 Snr = ( mcpsIndication->Snr & 0xFF ) >> 2; } if (mcpsIndication->Status != LORAMAC_EVENT_INFO_STATUS_OK) { LoRaMacEventInfoStatus_to_string(mcpsIndication->Status, str); APP_PRINTF("\e[31m%s\e[0m\r\n", str); return; } APP_PRINTF("OK Snr:%d FCntDown:%u\r\n", Snr, mcpsIndication->receivedFCntDown); switch( mcpsIndication->McpsIndication ) { case MCPS_UNCONFIRMED: { break; } case MCPS_CONFIRMED: { if (flags.ackDownlink) { tx_timeout.attach_us(ack_isr, ACK_DEFER_US); APP_PRINTF("confAck "); } break; } case MCPS_PROPRIETARY: { break; } case MCPS_MULTICAST: { break; } default: break; } // Check Multicast // Check Port // Check Datarate // Check FramePending // Check Buffer // Check BufferSize // Check Rssi // Check Snr // Check RxSlot if( ComplianceTest.Running == true ) { ComplianceTest.DownLinkCounter++; } if (mcpsIndication->RxData) { APP_PRINTF("FPort%u ", mcpsIndication->Port); print_buf(mcpsIndication->Buffer, mcpsIndication->BufferSize, ""); #ifdef TARGET_DISCO_L072CZ_LRWAN1 if (mcpsIndication->Buffer[0]) blue = LED_ON; else blue = LED_OFF; #endif /* TARGET_DISCO_L072CZ_LRWAN1 */ switch( mcpsIndication->Port ) { case 1: // The application LED can be controlled on port 1 or 2 case 2: 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 ) ) { gIsTxConfirmed = false; AppPort = 224; gAppDataSize = 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( USE_BAND_868 ) DutyCycleOn = false; #endif } } else { ComplianceTest.State = mcpsIndication->Buffer[0]; switch( ComplianceTest.State ) { case 0: // Check compliance test disable command (ii) gIsTxConfirmed = LORAWAN_CONFIRMED_MSG_ON; AppPort = LORAWAN_APP_PORT; gAppDataSize = LORAWAN_APP_DATA_SIZE; ComplianceTest.DownLinkCounter = 0; ComplianceTest.Running = false; MibRequestConfirm_t mibReq; mibReq.Type = MIB_ADR; mibReq.Param.AdrEnable = LORAWAN_ADR_ON; LoRaMacMibSetRequestConfirm( &mibReq ); #if defined( USE_BAND_868 ) DutyCycleOn = LORAWAN_DUTYCYCLE_ON; #endif break; case 1: // (iii, iv) gAppDataSize = 2; break; case 2: // Enable confirmed messages (v) gIsTxConfirmed = true; ComplianceTest.State = 1; break; case 3: // Disable confirmed messages (vi) gIsTxConfirmed = false; ComplianceTest.State = 1; break; case 4: // (vii) gAppDataSize = mcpsIndication->BufferSize; AppData[0] = 4; for( uint8_t i = 1; i < gAppDataSize; i++ ) { AppData[i] = mcpsIndication->Buffer[i] + 1; } break; case 5: // (viii) { MlmeReq_t mlmeReq; mlmeReq.Type = MLME_LINK_CHECK; LoRaMacMlmeRequest( &mlmeReq ); } break; case 6: // (ix) { #ifdef LORAWAN_JOIN_EUI MlmeReq_t mlmeReq = {}; // Disable TestMode and revert back to normal operation gIsTxConfirmed = LORAWAN_CONFIRMED_MSG_ON; AppPort = LORAWAN_APP_PORT; gAppDataSize = LORAWAN_APP_DATA_SIZE; ComplianceTest.DownLinkCounter = 0; ComplianceTest.Running = false; MibRequestConfirm_t mibReq; mibReq.Type = MIB_ADR; mibReq.Param.AdrEnable = LORAWAN_ADR_ON; LoRaMacMibSetRequestConfirm( &mibReq ); #if defined( USE_BAND_868 ) DutyCycleOn = LORAWAN_DUTYCYCLE_ON; #endif mlmeReq.Type = MLME_JOIN; mlmeReq.Req.Join.DevEui = DevEui; mlmeReq.Req.Join.JoinEui = JoinEui; mlmeReq.Req.Join.NwkKey = NwkKey; #ifdef LORAWAN_ROOT_APPKEY mlmeReq.Req.Join.AppKey = AppKey; #endif /* LORAWAN_ROOT_APPKEY */ LoRaMacMlmeRequest( &mlmeReq ); #endif /* LORAWAN_JOIN_EUI */ DeviceState = DEVICE_STATE_SLEEP; } break; case 7: // Switch end device Class { MlmeReq_t mlmeReq; mlmeReq.Type = MLME_SWITCH_CLASS; // CLASS_A = 0, CLASS_B = 1, CLASS_C = 2 mlmeReq.Req.SwitchClass.Class = ( DeviceClass_t )mcpsIndication->Buffer[1]; LoRaMacMlmeRequest( &mlmeReq ); PrepareTxFrame( AppPort ); /*status =*/ SendFrame(gIsTxConfirmed, gAppDataSize); } break; case 8: // Send PingSlotInfoReq { MlmeReq_t mlmeReq; mlmeReq.Type = MLME_PING_SLOT_INFO; mlmeReq.Req.PingSlotInfo.Value = mcpsIndication->Buffer[1]; LoRaMacMlmeRequest( &mlmeReq ); PrepareTxFrame( AppPort ); /*status =*/ SendFrame(gIsTxConfirmed, gAppDataSize); } break; case 9: // Send BeaconTimingReq { MlmeReq_t mlmeReq; mlmeReq.Type = MLME_BEACON_TIMING; LoRaMacMlmeRequest( &mlmeReq ); PrepareTxFrame( AppPort ); /*status =*/ SendFrame(gIsTxConfirmed, gAppDataSize); } break; default: break; } } break; default: break; } } } // ..McpsIndication() #ifdef LORAWAN_JOIN_EUI void join(uint8_t tries) { char str[64]; LoRaMacStatus_t status; MlmeReq_t mlmeReq = { }; mlmeReq.Type = MLME_JOIN; #ifdef LORAWAN_ROOT_APPKEY mlmeReq.Req.Join.AppKey = AppKey; #endif mlmeReq.Req.Join.DevEui = DevEui; mlmeReq.Req.Join.JoinEui = JoinEui; mlmeReq.Req.Join.NwkKey = NwkKey; mlmeReq.Req.Join.NbTrials = tries; status = LoRaMacMlmeRequest( &mlmeReq ); if (status != LORAMAC_STATUS_OK) { LoRaMacStatus_to_string(status, str); } else { extLed = 1; flags.startup = 1; } } #endif /* LORAWAN_JOIN_EUI */ /*! * \brief MLME-Confirm event function * * \param [IN] mlmeConfirm - Pointer to the confirm structure, * containing confirm attributes. */ static void MlmeConfirm( const MlmeConfirm_t *mlmeConfirm ) { char str[64]; static uint8_t failCnt = 0; Mlme_to_string(mlmeConfirm->MlmeRequest, str); APP_PRINTF("MlmeConfirm %s ", str); LoRaMacEventInfoStatus_to_string(mlmeConfirm->Status, str); if (mlmeConfirm->Status != LORAMAC_EVENT_INFO_STATUS_OK) { APP_PRINTF("\e[31m%s \e[0m\r\n", str); } else { APP_PRINTF("%s\r\n", str); } #if defined(LORAWAN_ROOT_APPKEY) && defined(LORAWAN_JOIN_EUI) /* 1v1 joinNonce is incrementing non-volatile value */ if (mlmeConfirm->MlmeRequest == MLME_JOIN) { vt.APP_PRINTF(" rxJoinNonce:%u vs %u", mlmeConfirm->fields.join.rxJoinNonce, mlmeConfirm->fields.join.myJoinNonce ); } #endif /* LORAWAN_ROOT_APPKEY */ if (mlmeConfirm->Status == LORAMAC_EVENT_INFO_STATUS_OK) { failCnt = 0; switch (mlmeConfirm->MlmeRequest) { #ifdef LORAWAN_JOIN_EUI case MLME_JOIN: { // Status is OK, node has joined the network /* collect any mac cmds from server until expected channel mask */ extLed = 0; DeviceState = DEVICE_STATE_JOIN_OK; break; } #endif /* LORAWAN_JOIN_EUI*/ case MLME_LINK_CHECK: { // Check DemodMargin // Check NbGateways if( ComplianceTest.Running == true ) { ComplianceTest.LinkCheck = true; ComplianceTest.DemodMargin = mlmeConfirm->fields.link.DemodMargin; ComplianceTest.NbGateways = mlmeConfirm->fields.link.NbGateways; } break; } case MLME_TIME_REQ: break; default: /* TODO: handle unknown MLME request */ DeviceState = DEVICE_STATE_SLEEP; break; } } else // not ok... { failCnt++; #ifdef LORAWAN_JOIN_EUI if (failCnt > 5) { join(1); return; } #endif switch( mlmeConfirm->MlmeRequest ) { #ifdef LORAWAN_JOIN_EUI case MLME_JOIN: { // Join failed, restart join procedure break; } #endif /* LORAWAN_JOIN_EUI */ case MLME_LINK_CHECK: DeviceState = DEVICE_STATE_SLEEP; break; #ifdef LORAWAN_JOIN_EUI case MLME_REJOIN_0: break; case MLME_REJOIN_2: break; case MLME_TIME_REQ: break; #endif /* LORAWAN_JOIN_EUI */ default: DeviceState = DEVICE_STATE_SLEEP; break; } } } // ..MlmeConfirm static void MlmeIndication( const MlmeIndication_t *MlmeIndication ) { char str[48]; MibRequestConfirm_t mibReq; Mlme_to_string(MlmeIndication->MlmeIndication, str); APP_PRINTF("MlmeIndication %s ", str); LoRaMacEventInfoStatus_to_string(MlmeIndication->Status, str); if (MlmeIndication->Status != LORAMAC_EVENT_INFO_STATUS_OK) { APP_PRINTF("\e[31m%s \e[0m\r\n", str); } else { APP_PRINTF("%s\r\n", str); } switch( MlmeIndication->MlmeIndication ) { case MLME_SWITCH_CLASS: { /* mac gave up on beacon */ mibReq.Type = MIB_DEVICE_CLASS; mibReq.Param.Class = CLASS_A; LoRaMacMibSetRequestConfirm( &mibReq ); // Switch to class A again DeviceState = DEVICE_STATE_SLEEP; // class-B manual switch break; } case MLME_BEACON: { LoRaMacEventInfoStatus_to_string(MlmeIndication->Status, str); break; } #ifdef LORAWAN_JOIN_EUI case MLME_JOIN: APP_PRINTF("%" PRIu32 "hz try%u ", MlmeIndication->freqHz, MlmeIndication->JoinRequestTrials); break; #endif /* !LORAWAN_JOIN_EUI */ default: break; } } // ..MlmeIndication() uint8_t periodicity; void SerialRxProcess( void ) { char str[48]; MibRequestConfirm_t mibReq; LoRaMacStatus_t status; MlmeReq_t mlmeReq; #ifndef LORAWAN_JOIN_EUI static uint8_t icnt = 0; #endif if( pc.readable( ) == true ) { char ch = pc.getc(); #ifndef LORAWAN_JOIN_EUI if (ch == 'I') { if (++icnt == 3) { APP_PRINTF("reset-fcnts\r\n"); eeprom_clear(EEPROM_AFCNTDWN); eeprom_clear(EEPROM_NFCNTDWN); eeprom_clear(EEPROM_FCNTUP); } } else icnt = 0; #endif /* !LORAWAN_JOIN_EUI */ if ( ch >= '0' && ch <= '9') { c_ch = ch - '0'; DeviceState = DEVICE_STATE_SEND; return; } switch( ch ) { case 'L': mlmeReq.Type = MLME_LINK_CHECK; status = LoRaMacMlmeRequest( &mlmeReq ); if (status == LORAMAC_STATUS_OK) SendFrame(false, 0); else { LoRaMacStatus_to_string(status, str); APP_PRINTF("%s\r\n", str); } break; #ifdef LORAWAN_JOIN_EUI case 'j': DeviceState = DEVICE_STATE_JOIN; break; #endif case 'a': flags.ackDownlink ^= 1; APP_PRINTF("ackDownlink:%u\r\n", flags.ackDownlink); break; case '.': uint16_t altitudeGps; print_buf(DevEui, 8, "DevEui"); mibReq.Type = MIB_DEV_ADDR; LoRaMacMibGetRequestConfirm( &mibReq ); APP_PRINTF("DevAddr:%08lx ", mibReq.Param.DevAddr); mibReq.Type = MIB_RX2_CHANNEL; LoRaMacMibGetRequestConfirm( &mibReq ); APP_PRINTF(" rx2 %uhz dr%u, ackDownlink:%u\r\n", mibReq.Param.Rx2Channel.FrequencyHz, mibReq.Param.Rx2Channel.Datarate, flags.ackDownlink); LoRaMacPrintStatus(); gps.service(); str[0] = gps.NmeaGpsData.NmeaUtcTime[0]; // hour str[1] = gps.NmeaGpsData.NmeaUtcTime[1]; str[2] = ':'; str[3] = gps.NmeaGpsData.NmeaUtcTime[2]; // minute str[4] = gps.NmeaGpsData.NmeaUtcTime[3]; str[5] = ':'; str[6] = gps.NmeaGpsData.NmeaUtcTime[4]; // second str[7] = gps.NmeaGpsData.NmeaUtcTime[5]; str[8] = 'Z'; str[9] = 0; APP_PRINTF("%s ", str); altitudeGps = atoi( gps.NmeaGpsData.NmeaAltitude ); APP_PRINTF("gps %f, %f, %d\r\n", gps.Latitude, gps.Longitude, altitudeGps); break; case '?': APP_PRINTF("C start classC\r\n"); APP_PRINTF("L send link check\r\n"); APP_PRINTF("j send joinReq\r\n"); APP_PRINTF("a toggle ackDownlink\r\n"); APP_PRINTF(". print status\r\n"); break; default: break; } } } static void button_isr() { c_ch = 0xff; DeviceState = DEVICE_STATE_SEND; buttonStartAt = LoRaMacReadTimer(); } static const LoRaMacPrimitives_t LoRaMacPrimitives = { McpsConfirm, McpsIndication, MlmeConfirm, MlmeIndication }; static const LoRaMacCallback_t LoRaMacCallbacks = { BoardGetBatteryLevel, NULL }; /** * Main application entry point. */ int main() { LoRaMacStatus_t status; MibRequestConfirm_t mibReq; DeviceState = DEVICE_STATE_INIT; if (sleep_manager_can_deep_sleep()) sleep_manager_lock_deep_sleep(); // prevent deep sleep #ifdef JUMPER_ENABLE jumper_out = 1; jumper_in.mode(PullDown); jumper_in.rise(jumper_callback); // Q: does InterruptIn.rise() call immediately if already high? if (jumper_in.read()) jumper_callback(); // A: probably not #endif /* JUMPER_ENABLE */ gps.init(); while( 1 ) { SerialRxProcess( ); switch( DeviceState ) { case DEVICE_STATE_INIT: { status = LoRaMacInitialization( &LoRaMacPrimitives, &LoRaMacCallbacks ); if (LORAMAC_STATUS_OK != status) { char str[48]; LoRaMacStatus_to_string(status, str); APP_PRINTF("MacInit: %s\r\n", str); for (;;) asm("nop"); } c_ch = 0xff; button_pin.mode(PullDown); #ifdef TARGET_DISCO_L072CZ_LRWAN1 button_pin.fall(button_isr); #else button_pin.rise(button_isr); #endif mibReq.Type = MIB_ADR; mibReq.Param.AdrEnable = LORAWAN_ADR_ON; LoRaMacMibSetRequestConfirm( &mibReq ); mibReq.Type = MIB_PUBLIC_NETWORK; mibReq.Param.EnablePublicNetwork = LORAWAN_PUBLIC_NETWORK; LoRaMacMibSetRequestConfirm( &mibReq ); #if defined( USE_BAND_868 ) DutyCycleOn = LORAWAN_DUTYCYCLE_ON ; #if( USE_SEMTECH_DEFAULT_CHANNEL_LINEUP == 1 ) LoRaMacChannelAdd( 3, ( ChannelParams_t )LC4 ); LoRaMacChannelAdd( 4, ( ChannelParams_t )LC5 ); LoRaMacChannelAdd( 5, ( ChannelParams_t )LC6 ); LoRaMacChannelAdd( 6, ( ChannelParams_t )LC7 ); LoRaMacChannelAdd( 7, ( ChannelParams_t )LC8 ); LoRaMacChannelAdd( 8, ( ChannelParams_t )LC9 ); LoRaMacChannelAdd( 9, ( ChannelParams_t )LC10 ); mibReq.Type = MIB_RX2_CHANNEL; mibReq.Param.Rx2Channel = ( Rx2ChannelParams_t ){ 869525000, DR_3 }; LoRaMacMibSetRequestConfirm( &mibReq ); #endif #endif #ifdef LORAWAN_JOIN_EUI #ifndef SENETCO /* for senet, use network provided DevEUI */ // Initialize LoRaMac device unique ID HardwareIDtoDevEUI(DevEui); #ifdef LORAWAN_ROOT_APPKEY // inverted DevEui provisioned as v1.1 on server (non-inv = lorawan1v0) for (int i = 0; i < 8; i++) DevEui[i] ^= 0xff; #endif /* LORAWAN_ROOT_APPKEY */ #endif /* !SENETCO */ print_buf(DevEui, 8, "DevEui"); DeviceState = DEVICE_STATE_JOIN; #else /* ABP... */ mibReq.Type = MIB_DEV_ADDR; mibReq.Param.DevAddr = DevAddr; LoRaMacMibSetRequestConfirm( &mibReq ); SerialDisplayUpdateDevAddr(DevAddr); mibReq.Type = MIB_APP_SKEY; mibReq.Param.key = AppSKey; LoRaMacMibSetRequestConfirm( &mibReq ); SerialDisplayUpdateKey(ROW_AppSKey, AppSKey); #if defined(LORAWAN_SNwkSIntKey) && defined(LORAWAN_NwkSEncKey) /* lorawan 1v1 ABP */ mibReq.Type = MIB_NwkSEncKey; mibReq.Param.key = NwkSEncKey; LoRaMacMibSetRequestConfirm( &mibReq ); SerialDisplayUpdateKey(ROW_NwkSEncKey, NwkSEncKey); mibReq.Type = MIB_SNwkSIntKey; mibReq.Param.key = SNwkSIntKey; LoRaMacMibSetRequestConfirm( &mibReq ); SerialDisplayUpdateKey(ROW_SNwkSIntKey, SNwkSIntKey); mibReq.Type = MIB_FNwkSIntKey; mibReq.Param.key = FNwkSIntKey; LoRaMacMibSetRequestConfirm( &mibReq ); SerialDisplayUpdateKey(ROW_FNwkSIntKey, mibReq.Param.key); #else /* lorawan 1v0 ABP */ mibReq.Type = MIB_NwkSKey; mibReq.Param.key = FNwkSIntKey; LoRaMacMibSetRequestConfirm( &mibReq ); SerialDisplayUpdateKey(ROW_FNwkSIntKey, mibReq.Param.key); #endif DeviceState = DEVICE_STATE_SLEEP; #endif /* !LORAWAN_JOIN_EUI */ #if defined( USE_BAND_ARIB_8CH ) mibReq.Type = MIB_MAX_LISTEN_TIME; mibReq.Param.MaxListenTime = 7000000; LoRaMacMibSetRequestConfirm( &mibReq ); #endif /* USE_BAND_ARIB_8CH */ flags.ackDownlink = 1; flags.startup = 1; break; } #ifdef LORAWAN_JOIN_EUI case DEVICE_STATE_JOIN: { join(8); DeviceState = DEVICE_STATE_SLEEP; break; } case DEVICE_STATE_JOIN_OK: MibRequestConfirm_t mibReq; mibReq.Type = MIB_NETWORK_JOINED; LoRaMacMibGetRequestConfirm( &mibReq ); if (mibReq.Param.IsNetworkJoined) APP_PRINTF("joined "); mibReq.Type = MIB_DEV_ADDR; LoRaMacMibGetRequestConfirm( &mibReq ); APP_PRINTF("DevAddr:%08" PRIx32 "\r\n", mibReq.Param.DevAddr); mibReq.Type = MIB_FNwkSIntKey; LoRaMacMibGetRequestConfirm( &mibReq ); print_buf(mibReq.Param.key, 16, "FNwkSIntKey"); mibReq.Type = MIB_APP_SKEY; LoRaMacMibGetRequestConfirm( &mibReq ); print_buf(mibReq.Param.key, 16, "AppSKey"); #ifdef LORAWAN_ROOT_APPKEY mibReq.Type = MIB_SNwkSIntKey; LoRaMacMibGetRequestConfirm( &mibReq ); SerialDisplayUpdateKey(ROW_SNwkSIntKey, mibReq.Param.key); mibReq.Type = MIB_NwkSEncKey; LoRaMacMibGetRequestConfirm( &mibReq ); SerialDisplayUpdateKey(ROW_NwkSEncKey, mibReq.Param.key); #endif /* LORAWAN_ROOT_APPKEY */ DeviceState = DEVICE_STATE_SLEEP; break; #endif /* LORAWAN_JOIN_EUI */ case DEVICE_STATE_SEND: PrepareTxFrame( AppPort ); status = SendFrame(gIsTxConfirmed, gAppDataSize); if (status == LORAMAC_STATUS_OK) { /* McpsConfirm or McpsIndication callback will continue */ } DeviceState = DEVICE_STATE_SLEEP; break; case DEVICE_STATE_SLEEP: { // Wake up through events sleep_manager_sleep_auto(); break; } default: DeviceState = DEVICE_STATE_INIT; break; } // ..switch( DeviceState ) gps.service(); LoRaMacUserContext(); } // ..while( 1 ) }