Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: SX127x sx12xx_hal TSL2561
mac/LoRaMac.cpp
- Committer:
- dudmuck
- Date:
- 2017-06-06
- Revision:
- 7:e238827f0e47
- Parent:
- 6:240fd4938d51
- Child:
- 8:ab2f9a8d2eaa
File content as of revision 7:e238827f0e47:
/* / _____) _ | | ( (____ _____ ____ _| |_ _____ ____| |__ \____ \| ___ | (_ _) ___ |/ ___) _ \ _____) ) ____| | | || |_| ____( (___| | | | (______/|_____)_|_|_| \__)_____)\____)_| |_| (C)2013 Semtech ___ _____ _ ___ _ _____ ___ ___ ___ ___ / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| embedded.connectivity.solutions=============== Description: LoRa MAC layer implementation License: Revised BSD License, see LICENSE.TXT file include in the project Maintainer: Miguel Luis ( Semtech ), Gregory Cristian ( Semtech ) and Daniel Jäckle ( STACKFORCE ) */ #include <math.h> #include "board.h" #include "LoRaMacCrypto.h" #include "LoRaMac.h" #include "LoRaMacTest.h" #define PING_SLOT_RESOLUTION_us 30000 /*! * Maximum PHY layer payload size */ #define LORAMAC_PHY_MAXPAYLOAD 255 /*! * Maximum MAC commands buffer size */ #define LORA_MAC_COMMAND_MAX_LENGTH 15 /*! * FRMPayload overhead to be used when setting the Radio.SetMaxPayloadLength * in RxWindowSetup function. * Maximum PHYPayload = MaxPayloadOfDatarate/MaxPayloadOfDatarateRepeater + LORA_MAC_FRMPAYLOAD_OVERHEAD */ #define LORA_MAC_FRMPAYLOAD_OVERHEAD 13 // MHDR(1) + FHDR(7) + Port(1) + MIC(4) /*! * LoRaMac duty cycle for the back-off procedure during the first hour. */ #define BACKOFF_DC_1_HOUR 100 /*! * LoRaMac duty cycle for the back-off procedure during the next 10 hours. */ #define BACKOFF_DC_10_HOURS 1000 /*! * LoRaMac duty cycle for the back-off procedure during the next 24 hours. */ #define BACKOFF_DC_24_HOURS 10000 /*! * Device IEEE EUI */ static uint8_t *LoRaMacDevEui; /*! * Application IEEE EUI */ static uint8_t *LoRaMacAppEui; /*! * AES encryption/decryption cipher application key */ static uint8_t *LoRaMacAppKey; /*! * AES encryption/decryption cipher network session key */ static uint8_t LoRaMacNwkSKey[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /*! * AES encryption/decryption cipher application session key */ static uint8_t LoRaMacAppSKey[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; /*! * Device nonce is a random value extracted by issuing a sequence of RSSI * measurements */ static uint16_t LoRaMacDevNonce; /*! * Network ID ( 3 bytes ) */ static uint32_t LoRaMacNetID; /*! * Mote Address */ static uint32_t LoRaMacDevAddr; /*! * Multicast channels linked list */ static MulticastParams_t *MulticastChannels = NULL; /*! * Actual device class */ static DeviceClass_t LoRaMacDeviceClass; /*! * Indicates if the node is connected to a private or public network */ static bool PublicNetwork; /*! * Buffer containing the data to be sent or received. */ static uint8_t LoRaMacBuffer[LORAMAC_PHY_MAXPAYLOAD]; /*! * Length of packet in LoRaMacBuffer */ static uint16_t LoRaMacBufferPktLen = 0; /*! * Length of the payload in LoRaMacBuffer */ static uint8_t LoRaMacTxPayloadLen = 0; /*! * Buffer containing the upper layer data. */ static uint8_t LoRaMacRxPayload[LORAMAC_PHY_MAXPAYLOAD]; /*! * LoRaMAC frame counter. Each time a packet is sent the counter is incremented. * Only the 16 LSB bits are sent */ static uint32_t UpLinkCounter = 0; /*! * LoRaMAC frame counter. Each time a packet is received the counter is incremented. * Only the 16 LSB bits are received */ static uint32_t DownLinkCounter = 0; /*! * IsPacketCounterFixed enables the MIC field tests by fixing the * UpLinkCounter value */ static bool IsUpLinkCounterFixed = false; /*! * Used for test purposes. Disables the opening of the reception windows. */ static bool IsRxWindowsEnabled = true; LowPowerTimeout rx_timeout; /*! * Indicates if the MAC layer has already joined a network. */ static bool IsLoRaMacNetworkJoined = false; /*! * If the node has sent a FRAME_TYPE_DATA_CONFIRMED_UP this variable indicates * if the nodes needs to manage the server acknowledgement. */ static bool NodeAckRequested = false; /*! * If the server has sent a FRAME_TYPE_DATA_CONFIRMED_DOWN this variable indicates * if the ACK bit must be set for the next transmission */ static bool SrvAckRequested = false; /*! * Indicates if the MAC layer wants to send MAC commands */ static bool MacCommandsInNextTx = false; /*! * Contains the current MacCommandsBuffer index */ static uint8_t MacCommandsBufferIndex = 0; /*! * Contains the current MacCommandsBuffer index for MAC commands to repeat */ static uint8_t MacCommandsBufferToRepeatIndex = 0; /*! * Buffer containing the MAC layer commands */ static uint8_t MacCommandsBuffer[LORA_MAC_COMMAND_MAX_LENGTH]; /*! * Buffer containing the MAC layer commands which must be repeated */ static uint8_t MacCommandsBufferToRepeat[LORA_MAC_COMMAND_MAX_LENGTH]; #if defined( USE_BAND_915_SINGLE ) /*! * Data rates table definition */ const uint8_t Datarates[] = { 10, 9, 8, 7, 8, 0, 0, 0, 12, 11, 10, 9, 8, 7, 0, 0 }; /*! * Bandwidths table definition in Hz */ const uint32_t Bandwidths[] = { 125000, 125000, 125000, 125000, 500000, 0, 0, 0, 500000, 500000, 500000, 500000, 500000, 500000, 0, 0 }; /*! * LoRaMac bands */ /*static Band_t Bands[LORA_MAX_NB_BANDS] = { BAND0, };*/ /*! * LoRaMAC channels */ static ChannelParams_t Channels[LORA_MAX_NB_CHANNELS]; /*! * Tx output powers table definition */ const int8_t TxPowers[] = { 30, 28, 26, 24, 22, 20, 18, 16, 14, 12, 10 }; #define LORAMAC_FIRST_CHANNEL ( (uint32_t)910.0e6 ) #define LORAMAC_STEPWIDTH_CHANNEL ( (uint32_t)800e3 ) #define BEACON_SIZE 6 /* bytes */ #define BEACON_CHANNEL_BW 2 /* 2=500KHz */ #define BEACON_CHANNEL_DR LORAMAC_DEFAULT_DATARATE /* measured beacon duration (all at bw500) * latency assigned for correct rx-before-tx measurement */ #if (LORAMAC_DEFAULT_DATARATE == DR_8) #define BEACON_RXDONE_LATENCY_us 6000 #define BEACON_TOA_us 209000 #elif (LORAMAC_DEFAULT_DATARATE == DR_9) #define BEACON_RXDONE_LATENCY_us 3500 #define BEACON_TOA_us 105000 #elif (LORAMAC_DEFAULT_DATARATE == DR_10) #define BEACON_RXDONE_LATENCY_us 1460 #define BEACON_TOA_us 52800 #elif (LORAMAC_DEFAULT_DATARATE == DR_11) #define BEACON_RXDONE_LATENCY_us 2000 #define BEACON_TOA_us 26000 #elif (LORAMAC_DEFAULT_DATARATE == DR_12) #define BEACON_RXDONE_LATENCY_us 1500 #define BEACON_TOA_us 12800 #elif (LORAMAC_DEFAULT_DATARATE == DR_13) #define BEACON_RXDONE_LATENCY_us 1300 #define BEACON_TOA_us 6560 #endif #define BEACON_GUARD_us 2000000 // pre-beacon start #define BEACON_RESERVED_us 2120000 // post-beacon start //#define BEACON_SYMBOL_TIMEOUT_UNLOCKED 100 #else #error "Please define a frequency band in the compiler options." #endif #define BEACON_MIN_SYMBOL_TIMEOUT 8 LowPowerTimer lp_timer; /*! * LoRaMac parameters */ LoRaMacParams_t LoRaMacParams; /*! * LoRaMac default parameters */ LoRaMacParams_t LoRaMacParamsDefaults; /*! * Uplink messages repetitions counter */ static uint8_t ChannelsNbRepCounter = 0; static uint32_t AggregatedLastTxDoneTime_us; /*! * Current channel index */ static uint8_t Channel; /*! * Stores the time at LoRaMac initialization. * * \remark Used for the BACKOFF_DC computation. */ //static TimerTime_t LoRaMacInitializationTime = 0; /*! * LoRaMac internal states */ enum eLoRaMacState { LORAMAC_IDLE = 0x00000000, LORAMAC_TX_RUNNING = 0x00000001, LORAMAC_RX = 0x00000002, LORAMAC_ACK_REQ = 0x00000004, LORAMAC_ACK_RETRY = 0x00000008, LORAMAC_TX_DELAYED = 0x00000010, LORAMAC_TX_CONFIG = 0x00000020, LORAMAC_RX_ABORT = 0x00000040, LORAMAC_TX_SCHED = 0x00000080, }; /*! * LoRaMac internal state */ uint32_t LoRaMacState = LORAMAC_IDLE; /*! * LoRaMac timer used to check the LoRaMacState (runs every second) */ static LowPowerTimeout MacStateCheckTimer; /*! * LoRaMac upper layer event functions */ static LoRaMacPrimitives_t *LoRaMacPrimitives; /*! * LoRaMac upper layer callback functions */ static LoRaMacCallback_t *LoRaMacCallbacks; /*! * Radio events function pointer */ static RadioEvents_t RadioEvents; /*! * LoRaMac duty cycle delayed Tx timer */ static LowPowerTimeout TxDelayedTimer; LowPowerTimeout tx_timeout; /*! * LoRaMac reception windows delay * \remark normal frame: RxWindowXDelay = ReceiveDelayX - RADIO_WAKEUP_TIME * join frame : RxWindowXDelay = JoinAcceptDelayX - RADIO_WAKEUP_TIME */ static uint32_t RxWindowDelay_us; typedef enum { BEACON_STATE_NONE = 0, BEACON_STATE_FIRST_ACQ, BEACON_STATE_ACQ_ERROR, BEACON_STATE_LOCKED_, } beacon_state_e; struct beacon_struct { int rx_precession_us; // positive: rxing before tx start, negative: rxing after tx start unsigned int RxBeaconSetupAt_us; unsigned int LastBeaconRx_us; // updated only at beacon reception unsigned int LastBeaconStart_us; // updated at beacon reception and beacon reception timeout unsigned int next_beacon_expected_us; int BeaconRxTimerError_us; float symbol_period_secs; uint8_t Precess_symbols; // how many symbols we want to start receiver before expected transmitter uint8_t SymbolTimeout; float SymbolTimeout_sec; uint8_t num_missed; beacon_state_e state; uint16_t tx_slot_offset; uint16_t periodicity_slots; LowPowerTimeout timeout; bool have_beacon; } BeaconCtx; /*! * Rx window parameters */ typedef struct { int8_t Datarate; uint8_t Bandwidth; uint32_t RxWindowTimeout; int32_t RxOffset; }RxConfigParams_t; /*! * Rx windows params */ static RxConfigParams_t RxWindowsParam; static bool expecting_beacon; /*! * Acknowledge timeout timer. Used for packet retransmissions. */ static LowPowerTimeout AckTimeoutTimer; uint32_t TxTimeOnAir = 0; /*! * Number of trials for the Join Request */ static uint8_t JoinRequestTrials; /*! * Maximum number of trials for the Join Request */ static uint8_t MaxJoinRequestTrials; /*! * Structure to hold an MCPS indication data. */ static McpsIndication_t McpsIndication; /*! * Structure to hold MCPS confirm data. */ static McpsConfirm_t McpsConfirm; /*! * Structure to hold MLME confirm data. */ static MlmeConfirm_t MlmeConfirm; /*! * Structure to hold MLME indication data. */ static MlmeIndication_t MlmeIndication; /*! * LoRaMac tx/rx operation state */ LoRaMacFlags_t LoRaMacFlags; /*! * \brief Function to be executed on Radio Tx Done event */ static void OnRadioTxDone( unsigned int tx_done_us ); unsigned int TxDone_us; /*! * \brief This function prepares the MAC to abort the execution of function * OnRadioRxDone in case of a reception error. */ static void PrepareRxDoneAbort( void ); /*! * \brief Function to be executed on Radio Rx Done event */ static void OnRadioRxDone(unsigned rx_us, uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr ); /*! * \brief Function executed on Radio Tx Timeout event */ static void OnRadioTxTimeout( void ); /*! * \brief Function executed on Radio Rx error event */ static void OnRadioRxError( void ); /*! * \brief Function executed on Radio Rx Timeout event */ static void OnRadioRxTimeout( void ); /*! * \brief Function executed on Resend Frame timer event. */ static void OnMacStateCheckTimerEvent( void ); /*! * \brief Function executed on duty cycle delayed Tx timer event */ static void OnTxDelayedTimerEvent( void ); static void OnRxWindowTimerEvent( void ); /*! * \brief Function executed on AckTimeout timer event */ static void OnAckTimeoutTimerEvent( void ); /*! * \brief Initializes and opens the reception window * * \param [IN] freq window channel frequency * \param [IN] datarate window channel datarate * \param [IN] bandwidth window channel bandwidth * \param [IN] timeout window channel timeout * * \retval status Operation status [true: Success, false: Fail] */ static bool RxWindowSetup( uint32_t freq, int8_t datarate, uint32_t bandwidth, uint16_t timeout, bool rxContinuous ); /*! * \brief Verifies if the RX window 2 frequency is in range * * \param [IN] freq window channel frequency * * \retval status Function status [1: OK, 0: Frequency not applicable] */ //static bool Rx2FreqInRange( uint32_t freq ); /*! * \brief Adds a new MAC command to be sent. * * \Remark MAC layer internal function * * \param [in] cmd MAC command to be added * [MOTE_MAC_LINK_CHECK_REQ, * MOTE_MAC_LINK_ADR_ANS, * MOTE_MAC_DUTY_CYCLE_ANS, * MOTE_MAC_RX2_PARAM_SET_ANS, * MOTE_MAC_DEV_STATUS_ANS * MOTE_MAC_NEW_CHANNEL_ANS] * \param [in] p1 1st parameter ( optional depends on the command ) * \param [in] p2 2nd parameter ( optional depends on the command ) * * \retval status Function status [0: OK, 1: Unknown command, 2: Buffer full] */ static LoRaMacStatus_t AddMacCommand( uint8_t cmd, uint8_t p1, uint8_t p2 ); /*! * \brief Parses the MAC commands which must be repeated. * * \Remark MAC layer internal function * * \param [IN] cmdBufIn Buffer which stores the MAC commands to send * \param [IN] length Length of the input buffer to parse * \param [OUT] cmdBufOut Buffer which stores the MAC commands which must be * repeated. * * \retval Size of the MAC commands to repeat. */ static uint8_t ParseMacCommandsToRepeat( uint8_t* cmdBufIn, uint8_t length, uint8_t* cmdBufOut ); /*! * \brief Verifies, if a value is in a given range. * * \param value Value to verify, if it is in range * * \param min Minimum possible value * * \param max Maximum possible value * * \retval Returns the maximum valid tx power */ static bool ValueInRange( int8_t value, int8_t min, int8_t max ); /*! * \brief Decodes MAC commands in the fOpts field and in the payload */ static void ProcessMacCommands( uint8_t *payload, uint8_t macIndex, uint8_t commandsSize, uint8_t snr ); /*! * \brief LoRaMAC layer generic send frame * * \param [IN] macHdr MAC header field * \param [IN] fPort MAC payload port * \param [IN] fBuffer MAC data buffer to be sent * \param [IN] fBufferSize MAC data buffer size * \retval status Status of the operation. */ LoRaMacStatus_t Send( LoRaMacHeader_t *macHdr, uint8_t fPort, void *fBuffer, uint16_t fBufferSize ); /*! * \brief LoRaMAC layer frame buffer initialization * * \param [IN] macHdr MAC header field * \param [IN] fCtrl MAC frame control field * \param [IN] fOpts MAC commands buffer * \param [IN] fPort MAC payload port * \param [IN] fBuffer MAC data buffer to be sent * \param [IN] fBufferSize MAC data buffer size * \retval status Status of the operation. */ LoRaMacStatus_t PrepareFrame( LoRaMacHeader_t *macHdr, LoRaMacFrameCtrl_t *fCtrl, uint8_t fPort, void *fBuffer, uint16_t fBufferSize ); /* * \brief Schedules the frame according to the duty cycle * * \retval Status of the operation */ static LoRaMacStatus_t ScheduleTx( void ); /*! * \brief LoRaMAC layer prepared frame buffer transmission with channel specification * * \remark PrepareFrame must be called at least once before calling this * function. * * \param [IN] channel Channel parameters * \retval status Status of the operation. */ LoRaMacStatus_t SendFrameOnChannel( ChannelParams_t channel ); /*! * \brief Sets the radio in continuous transmission mode * * \remark Uses the radio parameters set on the previous transmission. * * \param [IN] timeout Time in seconds while the radio is kept in continuous wave mode * \retval status Status of the operation. */ LoRaMacStatus_t SetTxContinuousWave( uint16_t timeout ); /*! * \brief Sets the radio in continuous transmission mode * * \remark Uses the radio parameters set on the previous transmission. * * \param [IN] timeout Time in seconds while the radio is kept in continuous wave mode * \param [IN] frequency RF frequency to be set. * \param [IN] power RF ouptput power to be set. * \retval status Status of the operation. */ LoRaMacStatus_t SetTxContinuousWave1( uint16_t timeout, uint32_t frequency, uint8_t power ); /*! * \brief Resets MAC specific parameters to default */ static void ResetMacParameters( void ); void loramac_print_status() { isr_printf("LoRaMacState:%lx DR%u=sf%u\r\n", LoRaMacState, LoRaMacParams.ChannelsDatarate_fixed, Datarates[LoRaMacParams.ChannelsDatarate_fixed]); } /* * Rx window precise timing * * For more details please consult the following document, chapter 3.1.2. * http://www.semtech.com/images/datasheet/SX1272_settings_for_LoRaWAN_v2.0.pdf * or * http://www.semtech.com/images/datasheet/SX1276_settings_for_LoRaWAN_v2.0.pdf * * Downlink start: T = Tx + 1s (+/- 20 us) * | * TRxEarly | TRxLate * | | | * | | +---+---+---+---+---+---+---+---+ * | | | Latest Rx window | * | | +---+---+---+---+---+---+---+---+ * | | | * +---+---+---+---+---+---+---+---+ * | Earliest Rx window | * +---+---+---+---+---+---+---+---+ * | * +---+---+---+---+---+---+---+---+ *Downlink preamble 8 symbols | | | | | | | | | * +---+---+---+---+---+---+---+---+ * * Worst case Rx window timings * * TRxLate = DEFAULT_MIN_RX_SYMBOLS * tSymbol - RADIO_WAKEUP_TIME * TRxEarly = 8 - DEFAULT_MIN_RX_SYMBOLS * tSymbol - RxWindowTimeout - RADIO_WAKEUP_TIME * * TRxLate - TRxEarly = 2 * DEFAULT_SYSTEM_MAX_RX_ERROR * * RxOffset = ( TRxLate + TRxEarly ) / 2 * * RxWindowTimeout = ( 2 * DEFAULT_MIN_RX_SYMBOLS - 8 ) * tSymbol + 2 * DEFAULT_SYSTEM_MAX_RX_ERROR * RxOffset = 4 * tSymbol - RxWindowTimeout / 2 - RADIO_WAKE_UP_TIME * * Minimal value of RxWindowTimeout must be 5 symbols which implies that the system always tolerates at least an error of 1.5 * tSymbol */ /*! * Computes the Rx window parameters. * * \param [IN] datarate Rx window datarate to be used * \param [IN] rxError Maximum timing error of the receiver. in milliseconds * The receiver will turn on in a [-rxError : +rxError] ms * interval around RxOffset * * \retval rxConfigParams Returns a RxConfigParams_t structure. */ static RxConfigParams_t ComputeRxWindowParameters( int8_t datarate, uint32_t rxError ); static void OnRadioTxDone( unsigned int tx_done_us ) { Radio.Sleep( ); TxDone_us = tx_done_us; // Setup timers if( IsRxWindowsEnabled == true ) { rx_timeout.attach_us(&OnRxWindowTimerEvent, RxWindowDelay_us); if( NodeAckRequested == true ) { AckTimeoutTimer.attach_us(&OnAckTimeoutTimerEvent, (RxWindowDelay_us/1000) + ACK_TIMEOUT_us + randr(-ACK_TIMEOUT_RND_us, ACK_TIMEOUT_RND_us)); } } else { McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK; MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX_TIMEOUT; if( LoRaMacFlags.Value == 0 ) { LoRaMacFlags.Bits.McpsReq = 1; } LoRaMacFlags.Bits.MacDone = 1; } // Update last tx done time for the current channel //Bands[Channels[Channel].Band].last_tx_done_us = tx_done_us; // Update Aggregated last tx done time AggregatedLastTxDoneTime_us = tx_done_us; // Update Backoff if( NodeAckRequested == false ) { McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK; ChannelsNbRepCounter++; } } static void PrepareRxDoneAbort( void ) { LoRaMacState |= LORAMAC_RX_ABORT; if( NodeAckRequested ) { OnAckTimeoutTimerEvent( ); } LoRaMacFlags.Bits.McpsInd = 1; LoRaMacFlags.Bits.MacDone = 1; // Trig OnMacCheckTimerEvent call as soon as possible OnMacStateCheckTimerEvent(); } void send_callback() { Radio.SetTxConfig( /* RadioModems_t modem */ MODEM_LORA, /* int8_t power */ TxPowers[LoRaMacParams.ChannelsTxPower], /* uint32_t fdev */ 0, /* uint32_t bandwidth */ 2, /* uint32_t datarate */ Datarates[LoRaMacParams.ChannelsDatarate_fixed], /* uint8_t coderate */ 1, /* uint16_t preambleLen */ 8, /* bool fixLen */ false, /* bool crcOn */ true, /* bool freqHopOn */ 0, /* uint8_t hopPeriod */ 0, /* bool iqInverted */ false, /* uint32_t timeout */ 3e3 ); Radio.Send( LoRaMacBuffer, LoRaMacBufferPktLen ); LoRaMacState &= ~LORAMAC_TX_SCHED; LoRaMacState |= LORAMAC_TX_RUNNING; // Starts the MAC layer status check timer MacStateCheckTimer.attach_us(&OnMacStateCheckTimerEvent, MAC_STATE_CHECK_TIMEOUT_us); /* unsigned int now_us = lp_timer.read_us(); int us_since_beacon_start = now_us - BeaconCtx.LastBeaconStart_us; int now_slot = (us_since_beacon_start - BEACON_RESERVED_us) / 30000; isr_printf("send now slot:%u\r\n", now_slot); */ } void OnRxBeaconSetup() { BeaconCtx.RxBeaconSetupAt_us = lp_timer.read_us(); Radio.SetRxConfig( /* RadioModems_t */ MODEM_LORA, /* uint32_t bandwidth */ BEACON_CHANNEL_BW, /* uint32_t datarate */ Datarates[BEACON_CHANNEL_DR], /* uint8_t coderate */ 1, /* uint32_t bandwidthAfc */ 0, /* uint16_t preambleLen */ 10, /* uint16_t symbTimeout */ BeaconCtx.SymbolTimeout, /* bool fixLen */ true, /* uint8_t payloadLen */ BEACON_SIZE, /* bool crcOn */ false, /* bool freqHopOn */ 0, /* uint8_t hopPeriod */ 0, /* bool iqInverted */ false, /* bool rxContinuous */false ); Radio.Rx(2000); expecting_beacon = true; //isr_printf("OnRxBeaconSetup() %u\r\n", BeaconCtx.SymbolTimeout); } static void set_beacon_symbol_timeout(float secs) { BeaconCtx.SymbolTimeout = secs / BeaconCtx.symbol_period_secs; if (BeaconCtx.SymbolTimeout < (BEACON_MIN_SYMBOL_TIMEOUT+BeaconCtx.Precess_symbols)) { BeaconCtx.SymbolTimeout = BEACON_MIN_SYMBOL_TIMEOUT+BeaconCtx.Precess_symbols; BeaconCtx.SymbolTimeout_sec = BeaconCtx.SymbolTimeout * BeaconCtx.symbol_period_secs; } } static uint16_t beacon_crc( uint8_t *buffer, uint16_t length ) { // The CRC calculation follows CCITT const uint16_t polynom = 0x1021; // CRC initial value uint16_t crc = 0x0000; if( buffer == NULL ) { return 0; } for( uint16_t i = 0; i < length; ++i ) { crc ^= ( uint16_t ) buffer[i] << 8; for( uint16_t j = 0; j < 8; ++j ) { crc = ( crc & 0x8000 ) ? ( crc << 1 ) ^ polynom : ( crc << 1 ); } } return crc; } /* low power timer needs larger value due to poor resolution */ #define TARGET_PRECESSION_US 3000 #define BEACON_RX_TIMEOUT_LOCKED 0.008 void rx_beacon(unsigned int rx_us, uint8_t* payload, uint16_t size) { static bool compensate_precession = false; int32_t compensation = 0; unsigned ThisBeaconRx_us = rx_us - (BEACON_TOA_us + BEACON_RXDONE_LATENCY_us); BeaconCtx.rx_precession_us = ThisBeaconRx_us - BeaconCtx.RxBeaconSetupAt_us; if (BeaconCtx.state != BEACON_STATE_FIRST_ACQ) { unsigned int intervals_since_last = (ThisBeaconRx_us / BEACON_INTERVAL_us) - (BeaconCtx.LastBeaconRx_us / BEACON_INTERVAL_us); /* get average of error history */ BeaconCtx.BeaconRxTimerError_us = (ThisBeaconRx_us - BeaconCtx.LastBeaconRx_us) % BEACON_INTERVAL_us; /* BeaconRxTimerError: positive means our clock is fast * negative means our clock is slow */ if (BeaconCtx.BeaconRxTimerError_us > (BEACON_INTERVAL_us/2)) BeaconCtx.BeaconRxTimerError_us -= BEACON_INTERVAL_us; // negative value representing slow crystal if (intervals_since_last > 1) { /* timer error is measured over more than one beacon period */ BeaconCtx.BeaconRxTimerError_us /= intervals_since_last; } if (BeaconCtx.state == BEACON_STATE_ACQ_ERROR) { isr_printf("-->LOCKED "); BeaconCtx.state = BEACON_STATE_LOCKED_; compensate_precession = true; set_beacon_symbol_timeout(BEACON_RX_TIMEOUT_LOCKED); } } else { /* ignore precession at first acquisition because it has slot resolution added */ isr_printf("-->ACQ_ERROR "); // next beacon will give us our crystal error BeaconCtx.state = BEACON_STATE_ACQ_ERROR; } isr_printf("err%d=%u-%u ", BeaconCtx.BeaconRxTimerError_us, ThisBeaconRx_us, BeaconCtx.LastBeaconRx_us); isr_printf(" rx-before-tx:%d ", BeaconCtx.rx_precession_us); BeaconCtx.LastBeaconRx_us = ThisBeaconRx_us; BeaconCtx.LastBeaconStart_us = BeaconCtx.LastBeaconRx_us; if (BeaconCtx.state == BEACON_STATE_LOCKED_) { if (compensate_precession) { compensation = BeaconCtx.rx_precession_us - TARGET_PRECESSION_US + BeaconCtx.BeaconRxTimerError_us; isr_printf(" comp%ld", compensation); } } isr_printf("\r\n"); BeaconCtx.next_beacon_expected_us = BEACON_INTERVAL_us + compensation; unsigned now_us = lp_timer.read_us(); unsigned us_since_rx_setup = now_us - BeaconCtx.RxBeaconSetupAt_us; BeaconCtx.timeout.attach_us(&OnRxBeaconSetup, BeaconCtx.next_beacon_expected_us - us_since_rx_setup); if (BeaconCtx.num_missed > 0) { /* restore rx symbol timeout */ set_beacon_symbol_timeout(BEACON_RX_TIMEOUT_LOCKED); } BeaconCtx.num_missed = 0; BeaconCtx.have_beacon = true; MlmeIndication.MlmeIndication = MLME_BEACON; MlmeIndication.Status = LORAMAC_EVENT_INFO_STATUS_BEACON_LOCKED; LoRaMacPrimitives->MacMlmeIndication( &MlmeIndication ); //LoRaMacFlags.Bits.MlmeInd = 1; /* check beacon payload */ uint16_t calc_crc = beacon_crc(payload, 4); uint16_t rx_crc = payload[4]; rx_crc |= payload[5] << 8; if (rx_crc == calc_crc) { unsigned int rx = payload[0]; rx |= payload[1] << 8; rx |= payload[2] << 16; rx |= payload[3] << 24; if (rx != 0) isr_printf("beacon payload:%08x\r\n", rx); } else isr_printf("calc_crc:%04x rx_crc:%04x\r\n", calc_crc, rx_crc); } float get_symbol_period(uint8_t bw, uint8_t sf) { //bw:, // 0=125kHz, 1=250kHz, 2=500KHz float hz; switch( bw ) { //case 0: // 7.8 kHz // bw = 78e2; // break; //case 1: // 10.4 kHz // bw = 104e2; // break; //case 2: // 15.6 kHz // bw = 156e2; // break; //case 3: // 20.8 kHz // bw = 208e2; // break; //case 4: // 31.2 kHz // bw = 312e2; // break; //case 5: // 41.4 kHz // bw = 414e2; // break; //case 6: // 62.5 kHz // bw = 625e2; // break; case 0: // 125 kHz hz = 125e3; break; case 1: // 250 kHz hz = 250e3; break; case 2: // 500 kHz hz = 500e3; break; default: return 0; } // return symbol period in seconds return (1 << sf) / hz; } static uint32_t GetRxBandwidth( int8_t datarate ) { return 2; /* always 500KHz */ } static void OnRadioRxDone(unsigned rx_us, uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr ) { LoRaMacHeader_t macHdr; LoRaMacFrameCtrl_t fCtrl; bool skipIndication = false; uint8_t pktHeaderLen = 0; uint32_t address = 0; uint8_t appPayloadStartIndex = 0; uint8_t port = 0xFF; uint8_t frameLen = 0; uint32_t mic = 0; uint32_t micRx = 0; uint16_t sequenceCounter = 0; uint16_t sequenceCounterPrev = 0; uint16_t sequenceCounterDiff = 0; uint32_t downLinkCounter = 0; MulticastParams_t *curMulticastParams = NULL; uint8_t *nwkSKey = LoRaMacNwkSKey; uint8_t *appSKey = LoRaMacAppSKey; uint8_t multicast = 0; bool isMicOk = false; McpsConfirm.AckReceived = false; McpsIndication.Rssi = rssi; McpsIndication.Snr = snr; McpsIndication.Port = 0; McpsIndication.Multicast = 0; McpsIndication.FramePending = 0; McpsIndication.Buffer = NULL; McpsIndication.BufferSize = 0; McpsIndication.RxData = false; McpsIndication.AckReceived = false; McpsIndication.DownLinkCounter = 0; McpsIndication.McpsIndication = MCPS_UNCONFIRMED; Radio.Sleep( ); if (expecting_beacon) { rx_beacon(rx_us, payload, size); expecting_beacon = false; return; } macHdr.Value = payload[pktHeaderLen++]; switch( macHdr.Bits.MType ) { case FRAME_TYPE_JOIN_ACCEPT: if( IsLoRaMacNetworkJoined == true ) { McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_JOIN_ACCEPT; PrepareRxDoneAbort( ); return; } LoRaMacJoinDecrypt( payload + 1, size - 1, LoRaMacAppKey, LoRaMacRxPayload + 1 ); LoRaMacRxPayload[0] = macHdr.Value; LoRaMacJoinComputeMic( LoRaMacRxPayload, size - LORAMAC_MFR_LEN, LoRaMacAppKey, &mic ); micRx |= ( uint32_t )LoRaMacRxPayload[size - LORAMAC_MFR_LEN]; micRx |= ( ( uint32_t )LoRaMacRxPayload[size - LORAMAC_MFR_LEN + 1] << 8 ); micRx |= ( ( uint32_t )LoRaMacRxPayload[size - LORAMAC_MFR_LEN + 2] << 16 ); micRx |= ( ( uint32_t )LoRaMacRxPayload[size - LORAMAC_MFR_LEN + 3] << 24 ); if( micRx == mic ) { LoRaMacJoinComputeSKeys( LoRaMacAppKey, LoRaMacRxPayload + 1, LoRaMacDevNonce, LoRaMacNwkSKey, LoRaMacAppSKey ); isr_printf("decr%u:", size); for (mic = 0; mic < size; mic++) isr_printf("%02x ", LoRaMacRxPayload[mic]); isr_printf("\r\n"); LoRaMacNetID = ( uint32_t )LoRaMacRxPayload[4]; LoRaMacNetID |= ( ( uint32_t )LoRaMacRxPayload[5] << 8 ); LoRaMacNetID |= ( ( uint32_t )LoRaMacRxPayload[6] << 16 ); LoRaMacDevAddr = ( uint32_t )LoRaMacRxPayload[7]; LoRaMacDevAddr |= ( ( uint32_t )LoRaMacRxPayload[8] << 8 ); LoRaMacDevAddr |= ( ( uint32_t )LoRaMacRxPayload[9] << 16 ); LoRaMacDevAddr |= ( ( uint32_t )LoRaMacRxPayload[10] << 24 ); // DLSettings LoRaMacParams.Rx1DrOffset = ( LoRaMacRxPayload[11] >> 4 ) & 0x07; LoRaMacParams.ReceiveDelay_us = ( LoRaMacRxPayload[12] & 0x0F ); if( LoRaMacParams.ReceiveDelay_us == 0 ) LoRaMacParams.ReceiveDelay_us = RECEIVE_DELAY_us; else LoRaMacParams.ReceiveDelay_us *= 10; uint16_t beaconTimingDelay = LoRaMacRxPayload[13] & 0xff; beaconTimingDelay |= LoRaMacRxPayload[14] << 8; isr_printf("%lx slots:%x (rxdelay %lu)", LoRaMacDevAddr, beaconTimingDelay, LoRaMacParams.ReceiveDelay_us); // how long from tx-done of join-request is beacon going to occur at uint32_t us_to_beacon = ( PING_SLOT_RESOLUTION_us * beaconTimingDelay ); // get time elapsed since last tx-done unsigned int now_us = lp_timer.read_us(); unsigned int us_since_TxDone = now_us - TxDone_us; BeaconCtx.timeout.attach_us(&OnRxBeaconSetup, us_to_beacon - us_since_TxDone); BeaconCtx.next_beacon_expected_us = now_us + us_to_beacon; isr_printf("us_to_beacon:%lu, since_tx_done:%u\r\n", us_to_beacon, us_since_TxDone); BeaconCtx.have_beacon = false; BeaconCtx.state = BEACON_STATE_FIRST_ACQ; BeaconCtx.num_missed = 0; BeaconCtx.rx_precession_us = 0; BeaconCtx.BeaconRxTimerError_us = -PPM_100_BEACON_INTERVAL; BeaconCtx.symbol_period_secs = get_symbol_period(GetRxBandwidth(LORAMAC_DEFAULT_DATARATE), Datarates[LORAMAC_DEFAULT_DATARATE]); // N-ms: slot resolution + minimum for preamble detector + 100ppm fast crystal rxing 12ms early BeaconCtx.Precess_symbols = TARGET_PRECESSION_US * (BeaconCtx.symbol_period_secs); BeaconCtx.SymbolTimeout_sec = 0.050 + (BEACON_MIN_SYMBOL_TIMEOUT * BeaconCtx.symbol_period_secs); BeaconCtx.SymbolTimeout = BeaconCtx.SymbolTimeout_sec / BeaconCtx.symbol_period_secs; isr_printf("sp:%f sto:%d\r\n", BeaconCtx.symbol_period_secs, BeaconCtx.SymbolTimeout); BeaconCtx.tx_slot_offset = LoRaMacRxPayload[15]; BeaconCtx.tx_slot_offset |= LoRaMacRxPayload[16] << 8; BeaconCtx.periodicity_slots = LoRaMacRxPayload[17]; BeaconCtx.periodicity_slots |= LoRaMacRxPayload[18] << 8; isr_printf("tso:%u, PS:%u\r\n", BeaconCtx.tx_slot_offset, BeaconCtx.periodicity_slots); MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK; IsLoRaMacNetworkJoined = true; LoRaMacParams.ChannelsDatarate_fixed = LoRaMacParamsDefaults.ChannelsDatarate_fixed; } else { MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL; isr_printf("join-mic-fail\r\n"); JoinRequestTrials = MaxJoinRequestTrials; // stop trying } break; case FRAME_TYPE_DATA_CONFIRMED_DOWN: case FRAME_TYPE_DATA_UNCONFIRMED_DOWN: { address = payload[pktHeaderLen++]; address |= ( (uint32_t)payload[pktHeaderLen++] << 8 ); address |= ( (uint32_t)payload[pktHeaderLen++] << 16 ); address |= ( (uint32_t)payload[pktHeaderLen++] << 24 ); if( address != LoRaMacDevAddr ) { curMulticastParams = MulticastChannels; while( curMulticastParams != NULL ) { if( address == curMulticastParams->Address ) { multicast = 1; nwkSKey = curMulticastParams->NwkSKey; appSKey = curMulticastParams->AppSKey; downLinkCounter = curMulticastParams->DownLinkCounter; break; } curMulticastParams = curMulticastParams->Next; } if( multicast == 0 ) { // We are not the destination of this frame. McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ADDRESS_FAIL; PrepareRxDoneAbort( ); return; } } else { multicast = 0; nwkSKey = LoRaMacNwkSKey; appSKey = LoRaMacAppSKey; downLinkCounter = DownLinkCounter; } fCtrl.Value = payload[pktHeaderLen++]; sequenceCounter = ( uint16_t )payload[pktHeaderLen++]; sequenceCounter |= ( uint16_t )payload[pktHeaderLen++] << 8; appPayloadStartIndex = 8 + fCtrl.Bits.FOptsLen; micRx |= ( uint32_t )payload[size - LORAMAC_MFR_LEN]; micRx |= ( ( uint32_t )payload[size - LORAMAC_MFR_LEN + 1] << 8 ); micRx |= ( ( uint32_t )payload[size - LORAMAC_MFR_LEN + 2] << 16 ); micRx |= ( ( uint32_t )payload[size - LORAMAC_MFR_LEN + 3] << 24 ); sequenceCounterPrev = ( uint16_t )downLinkCounter; sequenceCounterDiff = ( sequenceCounter - sequenceCounterPrev ); if( sequenceCounterDiff < ( 1 << 15 ) ) { downLinkCounter += sequenceCounterDiff; LoRaMacComputeMic( payload, size - LORAMAC_MFR_LEN, nwkSKey, address, DOWN_LINK, downLinkCounter, &mic ); if( micRx == mic ) { isMicOk = true; } } else { // check for sequence roll-over uint32_t downLinkCounterTmp = downLinkCounter + 0x10000 + ( int16_t )sequenceCounterDiff; LoRaMacComputeMic( payload, size - LORAMAC_MFR_LEN, nwkSKey, address, DOWN_LINK, downLinkCounterTmp, &mic ); if( micRx == mic ) { isMicOk = true; downLinkCounter = downLinkCounterTmp; } } // Check for a the maximum allowed counter difference if( sequenceCounterDiff >= MAX_FCNT_GAP ) { McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_TOO_MANY_FRAMES_LOSS; McpsIndication.DownLinkCounter = downLinkCounter; PrepareRxDoneAbort( ); return; } if( isMicOk == true ) { McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_OK; McpsIndication.Multicast = multicast; McpsIndication.FramePending = fCtrl.Bits.FPending; McpsIndication.Buffer = NULL; McpsIndication.BufferSize = 0; McpsIndication.DownLinkCounter = downLinkCounter; McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK; MacCommandsBufferToRepeatIndex = 0; // Update 32 bits downlink counter if( multicast == 1 ) { McpsIndication.McpsIndication = MCPS_MULTICAST; if( ( curMulticastParams->DownLinkCounter == downLinkCounter ) && ( curMulticastParams->DownLinkCounter != 0 ) ) { McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED; McpsIndication.DownLinkCounter = downLinkCounter; PrepareRxDoneAbort( ); return; } curMulticastParams->DownLinkCounter = downLinkCounter; } else { if( macHdr.Bits.MType == FRAME_TYPE_DATA_CONFIRMED_DOWN ) { SrvAckRequested = true; McpsIndication.McpsIndication = MCPS_CONFIRMED; if( ( DownLinkCounter == downLinkCounter ) && ( DownLinkCounter != 0 ) ) { // Duplicated confirmed downlink. Skip indication. // In this case, the MAC layer shall accept the MAC commands // which are included in the downlink retransmission. // It should not provide the same frame to the application // layer again. skipIndication = true; } } else { SrvAckRequested = false; McpsIndication.McpsIndication = MCPS_UNCONFIRMED; if( ( DownLinkCounter == downLinkCounter ) && ( DownLinkCounter != 0 ) ) { McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED; McpsIndication.DownLinkCounter = downLinkCounter; PrepareRxDoneAbort( ); return; } } DownLinkCounter = downLinkCounter; } // This must be done before parsing the payload and the MAC commands. // We need to reset the MacCommandsBufferIndex here, since we need // to take retransmissions and repititions into account. Error cases // will be handled in function OnMacStateCheckTimerEvent. if( McpsConfirm.McpsRequest == MCPS_CONFIRMED ) { if( fCtrl.Bits.Ack == 1 ) {// Reset MacCommandsBufferIndex when we have received an ACK. MacCommandsBufferIndex = 0; } } else {// Reset the variable if we have received any valid frame. MacCommandsBufferIndex = 0; } // Process payload and MAC commands if( ( ( size - 4 ) - appPayloadStartIndex ) > 0 ) { port = payload[appPayloadStartIndex++]; frameLen = ( size - 4 ) - appPayloadStartIndex; McpsIndication.Port = port; if( port == 0 ) { // Only allow frames which do not have fOpts if( fCtrl.Bits.FOptsLen == 0 ) { LoRaMacPayloadDecrypt( payload + appPayloadStartIndex, frameLen, nwkSKey, address, DOWN_LINK, downLinkCounter, LoRaMacRxPayload ); // Decode frame payload MAC commands ProcessMacCommands( LoRaMacRxPayload, 0, frameLen, snr ); } else { skipIndication = true; } } else { if( fCtrl.Bits.FOptsLen > 0 ) { // Decode Options field MAC commands. Omit the fPort. ProcessMacCommands( payload, 8, appPayloadStartIndex - 1, snr ); } LoRaMacPayloadDecrypt( payload + appPayloadStartIndex, frameLen, appSKey, address, DOWN_LINK, downLinkCounter, LoRaMacRxPayload ); if( skipIndication == false ) { McpsIndication.Buffer = LoRaMacRxPayload; McpsIndication.BufferSize = frameLen; McpsIndication.RxData = true; } } } else { if( fCtrl.Bits.FOptsLen > 0 ) { // Decode Options field MAC commands ProcessMacCommands( payload, 8, appPayloadStartIndex, snr ); } } if( skipIndication == false ) { // Check if the frame is an acknowledgement if( fCtrl.Bits.Ack == 1 ) { McpsConfirm.AckReceived = true; McpsIndication.AckReceived = true; // Stop the AckTimeout timer as no more retransmissions // are needed. } else { McpsConfirm.AckReceived = false; } } // Provide always an indication, skip the callback to the user application, // in case of a confirmed downlink retransmission. LoRaMacFlags.Bits.McpsInd = 1; LoRaMacFlags.Bits.McpsIndSkip = skipIndication; } else { McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_MIC_FAIL; McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_MIC_FAIL; PrepareRxDoneAbort( ); return; } } break; case FRAME_TYPE_PROPRIETARY: { memcpy1( LoRaMacRxPayload, &payload[pktHeaderLen], size ); McpsIndication.McpsIndication = MCPS_PROPRIETARY; McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_OK; McpsIndication.Buffer = LoRaMacRxPayload; McpsIndication.BufferSize = size - pktHeaderLen; LoRaMacFlags.Bits.McpsInd = 1; break; } default: McpsIndication.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_RX_MTYPE; McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_RX_MTYPE; PrepareRxDoneAbort( ); break; } LoRaMacFlags.Bits.MacDone = 1; /* run mac check quickly, but not immedately (for isr_printf) */ MacStateCheckTimer.attach_us(&OnMacStateCheckTimerEvent, 50000); } // ..OnRadioRxDone(); static void OnRadioTxTimeout( void ) { Radio.Sleep( ); McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT; MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT; LoRaMacFlags.Bits.MacDone = 1; } static void OnRadioRxError( void ) { Radio.Sleep( ); if( NodeAckRequested == true ) { McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX_ERROR; } MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX_ERROR; if ((lp_timer.read_us() - AggregatedLastTxDoneTime_us) >= RxWindowDelay_us ) { LoRaMacFlags.Bits.MacDone = 1; } } static void OnRadioRxTimeout( void ) { Radio.Sleep( ); if (expecting_beacon) { /* generate simulated last beacon start */ BeaconCtx.LastBeaconStart_us += BEACON_INTERVAL_us + BeaconCtx.BeaconRxTimerError_us; BeaconCtx.next_beacon_expected_us = BEACON_INTERVAL_us; BeaconCtx.num_missed++; unsigned now_us = lp_timer.read_us(); if (BeaconCtx.state == BEACON_STATE_FIRST_ACQ) { BeaconCtx.next_beacon_expected_us -= 1000000; set_beacon_symbol_timeout(2.000); } else { BeaconCtx.next_beacon_expected_us += BeaconCtx.BeaconRxTimerError_us; // for measurement resolution and temperature drift while missing beacons: BeaconCtx.next_beacon_expected_us -= 3000; set_beacon_symbol_timeout(BeaconCtx.SymbolTimeout_sec + 0.003); } unsigned us_since_rx_setup = now_us - BeaconCtx.RxBeaconSetupAt_us; BeaconCtx.timeout.attach_us(&OnRxBeaconSetup, BeaconCtx.next_beacon_expected_us - us_since_rx_setup); isr_printf("beacon-rx-timeout %u %u next in %uus (%u)\r\n", BeaconCtx.num_missed, BeaconCtx.SymbolTimeout, BeaconCtx.next_beacon_expected_us, us_since_rx_setup); MlmeIndication.MlmeIndication = MLME_BEACON; MlmeIndication.Status = LORAMAC_EVENT_INFO_STATUS_BEACON_LOST; LoRaMacPrimitives->MacMlmeIndication( &MlmeIndication ); //LoRaMacFlags.Bits.MlmeInd = 1; expecting_beacon = false; } if( NodeAckRequested == true ) { McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX_TIMEOUT; } MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_RX_TIMEOUT; LoRaMacFlags.Bits.MacDone = 1; /* run mac check quickly, but not immedately (for isr_printf) */ MacStateCheckTimer.attach_us(&OnMacStateCheckTimerEvent, 50000 + randr(0, 50000)); } // ..OnRadioRxTimeout(); static void OnMacStateCheckTimerEvent( void ) { bool txTimeout = false; //isr_printf("mac-check:%d McpsInd%d ", LoRaMacFlags.Bits.MacDone, LoRaMacFlags.Bits.McpsInd); if( LoRaMacFlags.Bits.MacDone == 1 ) { if( ( LoRaMacState & LORAMAC_RX_ABORT ) == LORAMAC_RX_ABORT ) { LoRaMacState &= ~LORAMAC_RX_ABORT; LoRaMacState &= ~LORAMAC_TX_RUNNING; //isr_printf("tx-run-A\r\n"); } if( ( LoRaMacFlags.Bits.MlmeReq == 1 ) || ( ( LoRaMacFlags.Bits.McpsReq == 1 ) ) ) { if( ( McpsConfirm.Status == LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT ) || ( MlmeConfirm.Status == LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT ) ) { // Stop transmit cycle due to tx timeout. LoRaMacState &= ~LORAMAC_TX_RUNNING; //isr_printf("tx-run-B\r\n"); MacCommandsBufferIndex = 0; McpsConfirm.AckReceived = false; McpsConfirm.TxTimeOnAir = 0; txTimeout = true; } } if( ( NodeAckRequested == false ) && ( txTimeout == false ) ) { if( ( LoRaMacFlags.Bits.MlmeReq == 1 ) || ( ( LoRaMacFlags.Bits.McpsReq == 1 ) ) ) { if( ( LoRaMacFlags.Bits.MlmeReq == 1 ) && ( MlmeConfirm.MlmeRequest == MLME_JOIN ) ) { // Procedure for the join request if( MlmeConfirm.Status == LORAMAC_EVENT_INFO_STATUS_OK ) { // Node joined successfully isr_printf("mac-check-join ok "); UpLinkCounter = 0; ChannelsNbRepCounter = 0; LoRaMacState &= ~LORAMAC_TX_RUNNING; } else { isr_printf("jrt:%u, maxjrt:%u\r\n", JoinRequestTrials, MaxJoinRequestTrials); if( JoinRequestTrials >= MaxJoinRequestTrials ) { LoRaMacState &= ~LORAMAC_TX_RUNNING; isr_printf("tx-run-D\r\n"); } else { LoRaMacFlags.Bits.MacDone = 0; // Sends the same frame again OnTxDelayedTimerEvent( ); } } } } // ..if( ( LoRaMacFlags.Bits.MlmeReq == 1 ) || ( ( LoRaMacFlags.Bits.McpsReq == 1 ) ) ) } // ..if( ( NodeAckRequested == false ) && ( txTimeout == false ) ) if( LoRaMacFlags.Bits.McpsInd == 1 ) {// Procedure if we received a frame if( ( McpsConfirm.AckReceived == true ) /*|| ( AckTimeoutRetriesCounter > AckTimeoutRetries )*/ ) { NodeAckRequested = false; if( IsUpLinkCounterFixed == false ) { UpLinkCounter++; } LoRaMacState &= ~LORAMAC_TX_RUNNING; //isr_printf("tx-run-E\r\n"); } } } // ...if( LoRaMacFlags.Bits.MacDone == 1 ) // Handle reception for Class B and Class C if( ( LoRaMacState & LORAMAC_RX ) == LORAMAC_RX ) { LoRaMacState &= ~LORAMAC_RX; } //isr_printf("LoRaMacState:%lx ", LoRaMacState); if( LoRaMacState == LORAMAC_IDLE ) { //isr_printf("McpsReq%d ", LoRaMacFlags.Bits.McpsReq); if( LoRaMacFlags.Bits.McpsReq == 1 ) { LoRaMacPrimitives->MacMcpsConfirm( &McpsConfirm ); LoRaMacFlags.Bits.McpsReq = 0; } if( LoRaMacFlags.Bits.MlmeReq == 1 ) { LoRaMacPrimitives->MacMlmeConfirm( &MlmeConfirm ); LoRaMacFlags.Bits.MlmeReq = 0; } if( LoRaMacFlags.Bits.MlmeInd == 1 ) { LoRaMacPrimitives->MacMlmeIndication( &MlmeIndication ); LoRaMacFlags.Bits.MlmeInd = 0; } // Procedure done. Reset variables. LoRaMacFlags.Bits.MacDone = 0; } else { // Operation not finished restart timer //isr_printf("mac-restart-%lx ", LoRaMacState); MacStateCheckTimer.attach_us(&OnMacStateCheckTimerEvent, MAC_STATE_CHECK_TIMEOUT_us); } if( LoRaMacFlags.Bits.McpsInd == 1 ) { if( LoRaMacFlags.Bits.McpsIndSkip == 0 ) { LoRaMacPrimitives->MacMcpsIndication( &McpsIndication ); } LoRaMacFlags.Bits.McpsIndSkip = 0; LoRaMacFlags.Bits.McpsInd = 0; } //isr_printf("\r\n"); } // ..OnMacStateCheckTimerEvent( void ) static void OnTxDelayedTimerEvent( void ) { LoRaMacHeader_t macHdr; LoRaMacFrameCtrl_t fCtrl; LoRaMacState &= ~LORAMAC_TX_DELAYED; if( ( LoRaMacFlags.Bits.MlmeReq == 1 ) && ( MlmeConfirm.MlmeRequest == MLME_JOIN ) ) { ResetMacParameters( ); if (++Channel == LORA_MAX_NB_CHANNELS) Channel = 0; isr_printf("<ch%u>", Channel); isr_printf("tx-delayed join ch%u\r\n", Channel); macHdr.Value = 0; macHdr.Bits.MType = FRAME_TYPE_JOIN_REQ; fCtrl.Value = 0; fCtrl.Bits.Adr = 0; /* In case of join request retransmissions, the stack must prepare * the frame again, because the network server keeps track of the random * LoRaMacDevNonce values to prevent reply attacks. */ PrepareFrame( &macHdr, &fCtrl, 0, NULL, 0 ); } ScheduleTx( ); } static void OnRxWindowTimerEvent( void ) { Radio.Standby( ); RxWindowSetup( LORAMAC_FIRST_CHANNEL + ( Channel * LORAMAC_STEPWIDTH_CHANNEL), RxWindowsParam.Datarate, RxWindowsParam.Bandwidth, RxWindowsParam.RxWindowTimeout, false ); //isr_printf("rx-ch%u, %lu\r\n", Channel, RxWindowsParam.RxWindowTimeout); /* no retrying TX in this class of operation */ LoRaMacState &= ~LORAMAC_TX_RUNNING; //isr_printf("rx-window "); } static void OnAckTimeoutTimerEvent( void ) { if( NodeAckRequested == true ) { LoRaMacState &= ~LORAMAC_ACK_REQ; } } static bool RxWindowSetup( uint32_t freq, int8_t datarate, uint32_t bandwidth, uint16_t timeout, bool rxContinuous ) { uint8_t downlinkDatarate = Datarates[datarate]; RadioModems_t modem; if( Radio.GetStatus( ) == RF_IDLE ) { Radio.SetChannel( freq ); // Store downlink datarate McpsIndication.RxDatarate = ( uint8_t ) datarate; modem = MODEM_LORA; Radio.SetRxConfig( modem, bandwidth, downlinkDatarate, 1, 0, 8, timeout, false, 0, false, 0, 0, true, rxContinuous ); Radio.SetMaxPayloadLength( MODEM_LORA, 255 ); if( rxContinuous == false ) { Radio.Rx( LoRaMacParams.MaxRxWindow ); } else { Radio.Rx( 0 ); // Continuous mode } //isr_printf("rx-setup %u %lu\r\n", timeout, LoRaMacParams.MaxRxWindow); //isr_printf("rx:%luhz sf%u bw%lu\r\n", freq, downlinkDatarate, bandwidth); return true; } return false; } static bool ValueInRange( int8_t value, int8_t min, int8_t max ) { if( ( value >= min ) && ( value <= max ) ) { return true; } return false; } static LoRaMacStatus_t AddMacCommand( uint8_t cmd, uint8_t p1, uint8_t p2 ) { LoRaMacStatus_t status = LORAMAC_STATUS_BUSY; // The maximum buffer length must take MAC commands to re-send into account. uint8_t bufLen = LORA_MAC_COMMAND_MAX_LENGTH - MacCommandsBufferToRepeatIndex; switch( cmd ) { case MOTE_MAC_LINK_CHECK_REQ: if( MacCommandsBufferIndex < bufLen ) { MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; // No payload for this command status = LORAMAC_STATUS_OK; } break; case MOTE_MAC_RX_PARAM_SETUP_ANS: if( MacCommandsBufferIndex < ( bufLen - 1 ) ) { MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; // Status: Datarate ACK, Channel ACK MacCommandsBuffer[MacCommandsBufferIndex++] = p1; status = LORAMAC_STATUS_OK; } break; case MOTE_MAC_DEV_STATUS_ANS: if( MacCommandsBufferIndex < ( bufLen - 2 ) ) { MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; // 1st byte Battery // 2nd byte Margin MacCommandsBuffer[MacCommandsBufferIndex++] = p1; MacCommandsBuffer[MacCommandsBufferIndex++] = p2; status = LORAMAC_STATUS_OK; } break; case MOTE_MAC_RX_TIMING_SETUP_ANS: if( MacCommandsBufferIndex < bufLen ) { MacCommandsBuffer[MacCommandsBufferIndex++] = cmd; // No payload for this answer status = LORAMAC_STATUS_OK; } break; default: return LORAMAC_STATUS_SERVICE_UNKNOWN; } if( status == LORAMAC_STATUS_OK ) { MacCommandsInNextTx = true; } return status; } static uint8_t ParseMacCommandsToRepeat( uint8_t* cmdBufIn, uint8_t length, uint8_t* cmdBufOut ) { uint8_t i = 0; uint8_t cmdCount = 0; if( ( cmdBufIn == NULL ) || ( cmdBufOut == NULL ) ) { return 0; } for( i = 0; i < length; i++ ) { switch( cmdBufIn[i] ) { // STICKY case MOTE_MAC_RX_PARAM_SETUP_ANS: { cmdBufOut[cmdCount++] = cmdBufIn[i++]; cmdBufOut[cmdCount++] = cmdBufIn[i]; break; } case MOTE_MAC_RX_TIMING_SETUP_ANS: { cmdBufOut[cmdCount++] = cmdBufIn[i]; break; } // NON-STICKY case MOTE_MAC_DEV_STATUS_ANS: { // 2 bytes payload i += 2; break; } case MOTE_MAC_LINK_CHECK_REQ: { // 0 byte payload break; } default: break; } } return cmdCount; } static void ProcessMacCommands( uint8_t *payload, uint8_t macIndex, uint8_t commandsSize, uint8_t snr ) { while( macIndex < commandsSize ) { // Decode Frame MAC commands switch( payload[macIndex++] ) { case SRV_MAC_LINK_CHECK_ANS: MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_OK; MlmeConfirm.DemodMargin = payload[macIndex++]; MlmeConfirm.NbGateways = payload[macIndex++]; break; case SRV_MAC_DEV_STATUS_REQ: { uint8_t batteryLevel = BAT_LEVEL_NO_MEASURE; if( ( LoRaMacCallbacks != NULL ) && ( LoRaMacCallbacks->GetBatteryLevel != NULL ) ) { batteryLevel = LoRaMacCallbacks->GetBatteryLevel( ); } AddMacCommand( MOTE_MAC_DEV_STATUS_ANS, batteryLevel, snr ); break; } case SRV_MAC_RX_TIMING_SETUP_REQ: { uint8_t delay = payload[macIndex++] & 0x0F; if( delay == 0 ) { delay++; } LoRaMacParams.ReceiveDelay_us = delay * 1e6; AddMacCommand( MOTE_MAC_RX_TIMING_SETUP_ANS, 0, 0 ); } break; default: // Unknown command. ABORT MAC commands processing return; } } } LoRaMacStatus_t Send( LoRaMacHeader_t *macHdr, uint8_t fPort, void *fBuffer, uint16_t fBufferSize ) { LoRaMacFrameCtrl_t fCtrl; LoRaMacStatus_t status = LORAMAC_STATUS_PARAMETER_INVALID; fCtrl.Value = 0; fCtrl.Bits.FOptsLen = 0; fCtrl.Bits.FPending = 0; fCtrl.Bits.Ack = false; fCtrl.Bits.AdrAckReq = false; fCtrl.Bits.Adr = false; // Prepare the frame status = PrepareFrame( macHdr, &fCtrl, fPort, fBuffer, fBufferSize ); // Validate status if( status != LORAMAC_STATUS_OK ) { return status; } // Reset confirm parameters McpsConfirm.AckReceived = false; McpsConfirm.UpLinkCounter = UpLinkCounter; status = ScheduleTx( ); return status; } static LoRaMacStatus_t ScheduleTx( void ) { // Compute Rx1 windows parameters if( IsLoRaMacNetworkJoined == false ) { RxWindowDelay_us = LoRaMacParams.JoinAcceptDelay_us + RxWindowsParam.RxOffset; // dont care } else { RxWindowDelay_us = LoRaMacParams.ReceiveDelay_us + RxWindowsParam.RxOffset; } // Schedule transmission of frame // Try to send now return SendFrameOnChannel( Channels[Channel] ); } static void ResetMacParameters( void ) { IsLoRaMacNetworkJoined = false; // Counters UpLinkCounter = 0; DownLinkCounter = 0; ChannelsNbRepCounter = 0; MacCommandsBufferIndex = 0; MacCommandsBufferToRepeatIndex = 0; IsRxWindowsEnabled = true; LoRaMacParams.ChannelsTxPower = LoRaMacParamsDefaults.ChannelsTxPower; LoRaMacParams.ChannelsDatarate_fixed = LoRaMacParamsDefaults.ChannelsDatarate_fixed; LoRaMacParams.Rx1DrOffset = LoRaMacParamsDefaults.Rx1DrOffset; NodeAckRequested = false; SrvAckRequested = false; MacCommandsInNextTx = false; // Reset Multicast downlink counters MulticastParams_t *cur = MulticastChannels; while( cur != NULL ) { cur->DownLinkCounter = 0; cur = cur->Next; } } LoRaMacStatus_t PrepareFrame( LoRaMacHeader_t *macHdr, LoRaMacFrameCtrl_t *fCtrl, uint8_t fPort, void *fBuffer, uint16_t fBufferSize ) { uint16_t i; uint8_t pktHeaderLen = 0; uint32_t mic = 0; const void* payload = fBuffer; uint8_t framePort = fPort; LoRaMacBufferPktLen = 0; NodeAckRequested = false; if( fBuffer == NULL ) { fBufferSize = 0; } LoRaMacTxPayloadLen = fBufferSize; LoRaMacBuffer[pktHeaderLen++] = macHdr->Value; switch( macHdr->Bits.MType ) { case FRAME_TYPE_JOIN_REQ: LoRaMacBufferPktLen = pktHeaderLen; memcpyr( LoRaMacBuffer + LoRaMacBufferPktLen, LoRaMacAppEui, 8 ); LoRaMacBufferPktLen += 8; memcpyr( LoRaMacBuffer + LoRaMacBufferPktLen, LoRaMacDevEui, 8 ); LoRaMacBufferPktLen += 8; LoRaMacDevNonce = Radio.Random( ); LoRaMacBuffer[LoRaMacBufferPktLen++] = LoRaMacDevNonce & 0xFF; LoRaMacBuffer[LoRaMacBufferPktLen++] = ( LoRaMacDevNonce >> 8 ) & 0xFF; LoRaMacJoinComputeMic( LoRaMacBuffer, LoRaMacBufferPktLen & 0xFF, LoRaMacAppKey, &mic ); LoRaMacBuffer[LoRaMacBufferPktLen++] = mic & 0xFF; LoRaMacBuffer[LoRaMacBufferPktLen++] = ( mic >> 8 ) & 0xFF; LoRaMacBuffer[LoRaMacBufferPktLen++] = ( mic >> 16 ) & 0xFF; LoRaMacBuffer[LoRaMacBufferPktLen++] = ( mic >> 24 ) & 0xFF; break; case FRAME_TYPE_DATA_CONFIRMED_UP: NodeAckRequested = true; //Intentional fallthrough case FRAME_TYPE_DATA_UNCONFIRMED_UP: if( IsLoRaMacNetworkJoined == false ) { return LORAMAC_STATUS_NO_NETWORK_JOINED; // No network has been joined yet } fCtrl->Bits.AdrAckReq = 0; if( SrvAckRequested == true ) { SrvAckRequested = false; fCtrl->Bits.Ack = 1; } LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr ) & 0xFF; LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr >> 8 ) & 0xFF; LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr >> 16 ) & 0xFF; LoRaMacBuffer[pktHeaderLen++] = ( LoRaMacDevAddr >> 24 ) & 0xFF; LoRaMacBuffer[pktHeaderLen++] = fCtrl->Value; LoRaMacBuffer[pktHeaderLen++] = UpLinkCounter & 0xFF; LoRaMacBuffer[pktHeaderLen++] = ( UpLinkCounter >> 8 ) & 0xFF; // Copy the MAC commands which must be re-send into the MAC command buffer memcpy1( &MacCommandsBuffer[MacCommandsBufferIndex], MacCommandsBufferToRepeat, MacCommandsBufferToRepeatIndex ); MacCommandsBufferIndex += MacCommandsBufferToRepeatIndex; if( ( payload != NULL ) && ( LoRaMacTxPayloadLen > 0 ) ) { if( ( MacCommandsBufferIndex <= LORA_MAC_COMMAND_MAX_LENGTH ) && ( MacCommandsInNextTx == true ) ) { fCtrl->Bits.FOptsLen += MacCommandsBufferIndex; // Update FCtrl field with new value of OptionsLength LoRaMacBuffer[0x05] = fCtrl->Value; for( i = 0; i < MacCommandsBufferIndex; i++ ) { LoRaMacBuffer[pktHeaderLen++] = MacCommandsBuffer[i]; } } } else { if( ( MacCommandsBufferIndex > 0 ) && ( MacCommandsInNextTx ) ) { LoRaMacTxPayloadLen = MacCommandsBufferIndex; payload = MacCommandsBuffer; framePort = 0; } } MacCommandsInNextTx = false; // Store MAC commands which must be re-send in case the device does not receive a downlink anymore MacCommandsBufferToRepeatIndex = ParseMacCommandsToRepeat( MacCommandsBuffer, MacCommandsBufferIndex, MacCommandsBufferToRepeat ); if( MacCommandsBufferToRepeatIndex > 0 ) { MacCommandsInNextTx = true; } if( ( payload != NULL ) && ( LoRaMacTxPayloadLen > 0 ) ) { LoRaMacBuffer[pktHeaderLen++] = framePort; if( framePort == 0 ) { LoRaMacPayloadEncrypt( (uint8_t* ) payload, LoRaMacTxPayloadLen, LoRaMacNwkSKey, LoRaMacDevAddr, UP_LINK, UpLinkCounter, &LoRaMacBuffer[pktHeaderLen] ); } else { LoRaMacPayloadEncrypt( (uint8_t* ) payload, LoRaMacTxPayloadLen, LoRaMacAppSKey, LoRaMacDevAddr, UP_LINK, UpLinkCounter, &LoRaMacBuffer[pktHeaderLen] ); } } LoRaMacBufferPktLen = pktHeaderLen + LoRaMacTxPayloadLen; LoRaMacComputeMic( LoRaMacBuffer, LoRaMacBufferPktLen, LoRaMacNwkSKey, LoRaMacDevAddr, UP_LINK, UpLinkCounter, &mic ); LoRaMacBuffer[LoRaMacBufferPktLen + 0] = mic & 0xFF; LoRaMacBuffer[LoRaMacBufferPktLen + 1] = ( mic >> 8 ) & 0xFF; LoRaMacBuffer[LoRaMacBufferPktLen + 2] = ( mic >> 16 ) & 0xFF; LoRaMacBuffer[LoRaMacBufferPktLen + 3] = ( mic >> 24 ) & 0xFF; LoRaMacBufferPktLen += LORAMAC_MFR_LEN; break; case FRAME_TYPE_PROPRIETARY: if( ( fBuffer != NULL ) && ( LoRaMacTxPayloadLen > 0 ) ) { memcpy1( LoRaMacBuffer + pktHeaderLen, ( uint8_t* ) fBuffer, LoRaMacTxPayloadLen ); LoRaMacBufferPktLen = pktHeaderLen + LoRaMacTxPayloadLen; } break; default: return LORAMAC_STATUS_SERVICE_UNKNOWN; } return LORAMAC_STATUS_OK; } //TxPowers[LoRaMacParams.ChannelsTxPower] LoRaMacStatus_t SendFrameOnChannel( ChannelParams_t channel ) { int8_t datarate = Datarates[LoRaMacParams.ChannelsDatarate_fixed]; int8_t txPowerIndex = 0; int8_t txPower = 0; if (LoRaMacState & LORAMAC_TX_SCHED) { return LORAMAC_STATUS_BUSY; } txPowerIndex = LoRaMacParams.ChannelsTxPower; txPower = TxPowers[txPowerIndex]; MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_SEND; McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_SEND; McpsConfirm.TxPower = txPowerIndex; McpsConfirm.UpLinkFrequency = channel.Frequency; Radio.SetChannel( channel.Frequency ); if( LoRaMacParams.ChannelsDatarate_fixed >= DR_8 ) { // High speed LoRa channel BW500 kHz //Radio.SetTxConfig( MODEM_LORA, txPower, 0, 2, datarate, 1, 8, false, true, 0, 0, false, 3e3 ); TxTimeOnAir = Radio.TimeOnAir( MODEM_LORA, LoRaMacBufferPktLen ); } else return LORAMAC_STATUS_DATARATE_INVALID; // Store the time on air McpsConfirm.TxTimeOnAir = TxTimeOnAir; MlmeConfirm.TxTimeOnAir = TxTimeOnAir; if( IsLoRaMacNetworkJoined == false ) { JoinRequestTrials++; isr_printf("join %luhz try%u DR%u\r\n", channel.Frequency, JoinRequestTrials, LoRaMacParams.ChannelsDatarate_fixed); } /* anything not join request is sent at permitted time slot */ LoRaMacHeader_t* macHdr = (LoRaMacHeader_t*)&LoRaMacBuffer[0]; if (macHdr->Bits.MType == FRAME_TYPE_JOIN_REQ) { // Send now Radio.SetTxConfig( MODEM_LORA, txPower, 0, 2, datarate, 1, 8, false, true, 0, 0, false, 3e3 ); Radio.Send( LoRaMacBuffer, LoRaMacBufferPktLen ); LoRaMacState |= LORAMAC_TX_RUNNING; // Starts the MAC layer status check timer MacStateCheckTimer.attach_us(&OnMacStateCheckTimerEvent, MAC_STATE_CHECK_TIMEOUT_us); } else if (BeaconCtx.have_beacon) { /* find now ping slot */ unsigned int target_us, now_us = lp_timer.read_us(); int us_to_guard, us_since_beacon_start = now_us - BeaconCtx.LastBeaconStart_us; int use_slot = BeaconCtx.tx_slot_offset; int now_slot = (us_since_beacon_start - BEACON_RESERVED_us) / 30000; int use_slot_us; while (use_slot < now_slot) use_slot += BeaconCtx.periodicity_slots; use_slot_us = (use_slot * 30000); us_to_guard = BeaconCtx.next_beacon_expected_us - use_slot_us; #ifdef TX_DEBUG isr_printf("now_slot:%d, to-guard:%d use_slot:%u ", now_slot, us_to_guard, use_slot); #endif if (us_to_guard > BEACON_GUARD_us) { target_us = (use_slot * 30000) + BEACON_RESERVED_us; tx_timeout.attach_us(&send_callback, target_us - us_since_beacon_start); #ifdef TX_DEBUG isr_printf(" : tx in %uus\r\n", target_us - us_since_beacon_start); #endif } else { #ifdef TX_DEBUG isr_printf("sf-busy\r\n"); #endif return LORAMAC_STATUS_BUSY; } LoRaMacState |= LORAMAC_TX_SCHED; } else { isr_printf("dropped tx\r\n"); return LORAMAC_STATUS_SERVICE_UNKNOWN; // TODO use correct failure } return LORAMAC_STATUS_OK; } LoRaMacStatus_t SetTxContinuousWave( uint16_t timeout ) { int8_t txPowerIndex = 0; int8_t txPower = 0; txPowerIndex = LoRaMacParams.ChannelsTxPower; txPower = TxPowers[txPowerIndex]; // Starts the MAC layer status check timer MacStateCheckTimer.attach_us(&OnMacStateCheckTimerEvent, MAC_STATE_CHECK_TIMEOUT_us); Radio.SetTxContinuousWave( Channels[Channel].Frequency, txPower, timeout ); LoRaMacState |= LORAMAC_TX_RUNNING; return LORAMAC_STATUS_OK; } LoRaMacStatus_t SetTxContinuousWave1( uint16_t timeout, uint32_t frequency, uint8_t power ) { Radio.SetTxContinuousWave( frequency, power, timeout ); // Starts the MAC layer status check timer MacStateCheckTimer.attach_us(&OnMacStateCheckTimerEvent, MAC_STATE_CHECK_TIMEOUT_us); LoRaMacState |= LORAMAC_TX_RUNNING; return LORAMAC_STATUS_OK; } void seconds() { isr_printf("second\r\n"); } LoRaMacStatus_t LoRaMacInitialization( LoRaMacPrimitives_t *primitives, LoRaMacCallback_t *callbacks ) { if( primitives == NULL ) { return LORAMAC_STATUS_PARAMETER_INVALID; } if( ( primitives->MacMcpsConfirm == NULL ) || ( primitives->MacMcpsIndication == NULL ) || ( primitives->MacMlmeConfirm == NULL ) ) { return LORAMAC_STATUS_PARAMETER_INVALID; } LoRaMacPrimitives = primitives; LoRaMacCallbacks = callbacks; LoRaMacFlags.Value = 0; LoRaMacDeviceClass = CLASS_A; LoRaMacState = LORAMAC_IDLE; JoinRequestTrials = 0; MaxJoinRequestTrials = 255; // Reset to defaults LoRaMacParamsDefaults.ChannelsTxPower = LORAMAC_DEFAULT_TX_POWER; LoRaMacParamsDefaults.ChannelsDatarate_fixed = LORAMAC_DEFAULT_DATARATE; LoRaMacParamsDefaults.SystemMaxRxError = 10; LoRaMacParamsDefaults.MinRxSymbols = 6; // TODO XXX increase LoRaMacParamsDefaults.MaxRxWindow = MAX_RX_WINDOW; LoRaMacParamsDefaults.ReceiveDelay_us = RECEIVE_DELAY_us; LoRaMacParamsDefaults.JoinAcceptDelay_us = JOIN_ACCEPT_DELAY_us; LoRaMacParamsDefaults.ChannelsNbRep = 1; LoRaMacParamsDefaults.Rx1DrOffset = 0; #if defined(USE_BAND_915_SINGLE) // 500 kHz channels for( uint8_t i = 0; i < LORA_MAX_NB_CHANNELS; i++ ) { Channels[i].Frequency = LORAMAC_FIRST_CHANNEL + (i * LORAMAC_STEPWIDTH_CHANNEL); Channels[i].DrRange.Value = ( DR_13 << 4 ) | DR_8; Channels[i].Band = 0; } #endif // Init parameters which are not set in function ResetMacParameters LoRaMacParams.SystemMaxRxError = LoRaMacParamsDefaults.SystemMaxRxError; LoRaMacParams.MinRxSymbols = LoRaMacParamsDefaults.MinRxSymbols; LoRaMacParams.MaxRxWindow = LoRaMacParamsDefaults.MaxRxWindow; LoRaMacParams.ReceiveDelay_us = LoRaMacParamsDefaults.ReceiveDelay_us; LoRaMacParams.JoinAcceptDelay_us = LoRaMacParamsDefaults.JoinAcceptDelay_us; LoRaMacParams.ChannelsNbRep = LoRaMacParamsDefaults.ChannelsNbRep; ResetMacParameters( ); // Initialize Radio driver RadioEvents.TxDone = OnRadioTxDone; RadioEvents.RxDone = OnRadioRxDone; RadioEvents.RxError = OnRadioRxError; RadioEvents.TxTimeout = OnRadioTxTimeout; RadioEvents.RxTimeout = OnRadioRxTimeout; Radio.Init( &RadioEvents ); // Random seed initialization srand1( Radio.Random( ) ); PublicNetwork = true; Radio.SetPublicNetwork( PublicNetwork ); Radio.Sleep( ); lp_timer.start(); RxWindowsParam = ComputeRxWindowParameters(LORAMAC_DEFAULT_DATARATE, LoRaMacParams.SystemMaxRxError); return LORAMAC_STATUS_OK; } LoRaMacStatus_t LoRaMacQueryTxPossible( uint8_t size, LoRaMacTxInfo_t* txInfo ) { uint8_t fOptLen = MacCommandsBufferIndex + MacCommandsBufferToRepeatIndex; if( txInfo == NULL ) { return LORAMAC_STATUS_PARAMETER_INVALID; } txInfo->CurrentPayloadSize = 255; if( txInfo->CurrentPayloadSize >= fOptLen ) { txInfo->MaxPossiblePayload = txInfo->CurrentPayloadSize - fOptLen; } else { return LORAMAC_STATUS_MAC_CMD_LENGTH_ERROR; } return LORAMAC_STATUS_OK; } LoRaMacStatus_t LoRaMacMibGetRequestConfirm( MibRequestConfirm_t *mibGet ) { LoRaMacStatus_t status = LORAMAC_STATUS_OK; if( mibGet == NULL ) { return LORAMAC_STATUS_PARAMETER_INVALID; } switch( mibGet->Type ) { case MIB_DEVICE_CLASS: { mibGet->Param.Class = LoRaMacDeviceClass; break; } case MIB_NETWORK_JOINED: { mibGet->Param.IsNetworkJoined = IsLoRaMacNetworkJoined; break; } case MIB_NET_ID: { mibGet->Param.NetID = LoRaMacNetID; break; } case MIB_DEV_ADDR: { mibGet->Param.DevAddr = LoRaMacDevAddr; break; } case MIB_NWK_SKEY: { mibGet->Param.NwkSKey = LoRaMacNwkSKey; break; } case MIB_APP_SKEY: { mibGet->Param.AppSKey = LoRaMacAppSKey; break; } case MIB_PUBLIC_NETWORK: { mibGet->Param.EnablePublicNetwork = PublicNetwork; break; } case MIB_CHANNELS_NB_REP: { mibGet->Param.ChannelNbRep = LoRaMacParams.ChannelsNbRep; break; } case MIB_MAX_RX_WINDOW_DURATION: { mibGet->Param.MaxRxWindow = LoRaMacParams.MaxRxWindow; break; } case MIB_CHANNELS_DEFAULT_TX_POWER: { mibGet->Param.ChannelsDefaultTxPower = LoRaMacParamsDefaults.ChannelsTxPower; break; } case MIB_CHANNELS_TX_POWER: { mibGet->Param.ChannelsTxPower = LoRaMacParams.ChannelsTxPower; break; } case MIB_UPLINK_COUNTER: { mibGet->Param.UpLinkCounter = UpLinkCounter; break; } case MIB_DOWNLINK_COUNTER: { mibGet->Param.DownLinkCounter = DownLinkCounter; break; } case MIB_MULTICAST_CHANNEL: { mibGet->Param.MulticastList = MulticastChannels; break; } case MIB_SYSTEM_MAX_RX_ERROR: { mibGet->Param.SystemMaxRxError = LoRaMacParams.SystemMaxRxError; break; } case MIB_MIN_RX_SYMBOLS: { mibGet->Param.MinRxSymbols = LoRaMacParams.MinRxSymbols; break; } default: status = LORAMAC_STATUS_SERVICE_UNKNOWN; break; } return status; } LoRaMacStatus_t LoRaMacMibSetRequestConfirm( MibRequestConfirm_t *mibSet ) { LoRaMacStatus_t status = LORAMAC_STATUS_OK; if( mibSet == NULL ) { return LORAMAC_STATUS_PARAMETER_INVALID; } if( ( LoRaMacState & LORAMAC_TX_RUNNING ) == LORAMAC_TX_RUNNING ) { return LORAMAC_STATUS_BUSY; } switch( mibSet->Type ) { case MIB_DEVICE_CLASS: { LoRaMacDeviceClass = mibSet->Param.Class; switch( LoRaMacDeviceClass ) { case CLASS_A: { // Set the radio into sleep to setup a defined state Radio.Sleep( ); break; } case CLASS_B: { break; } case CLASS_D: isr_printf("TODO MIB_DEVICE_CLASS:D\r\n"); break; } break; } case MIB_NETWORK_JOINED: { IsLoRaMacNetworkJoined = mibSet->Param.IsNetworkJoined; break; } case MIB_NET_ID: { LoRaMacNetID = mibSet->Param.NetID; break; } case MIB_DEV_ADDR: { LoRaMacDevAddr = mibSet->Param.DevAddr; break; } case MIB_NWK_SKEY: { if( mibSet->Param.NwkSKey != NULL ) { memcpy1( LoRaMacNwkSKey, mibSet->Param.NwkSKey, sizeof( LoRaMacNwkSKey ) ); } else { status = LORAMAC_STATUS_PARAMETER_INVALID; } break; } case MIB_APP_SKEY: { if( mibSet->Param.AppSKey != NULL ) { memcpy1( LoRaMacAppSKey, mibSet->Param.AppSKey, sizeof( LoRaMacAppSKey ) ); } else { status = LORAMAC_STATUS_PARAMETER_INVALID; } break; } case MIB_PUBLIC_NETWORK: { PublicNetwork = mibSet->Param.EnablePublicNetwork; Radio.SetPublicNetwork( PublicNetwork ); break; } case MIB_CHANNELS_NB_REP: { if( ( mibSet->Param.ChannelNbRep >= 1 ) && ( mibSet->Param.ChannelNbRep <= 15 ) ) { LoRaMacParams.ChannelsNbRep = mibSet->Param.ChannelNbRep; } else { status = LORAMAC_STATUS_PARAMETER_INVALID; } break; } case MIB_MAX_RX_WINDOW_DURATION: { LoRaMacParams.MaxRxWindow = mibSet->Param.MaxRxWindow; break; } case MIB_CHANNELS_DEFAULT_TX_POWER: { if( ValueInRange( mibSet->Param.ChannelsDefaultTxPower, LORAMAC_MAX_TX_POWER, LORAMAC_MIN_TX_POWER ) ) { LoRaMacParamsDefaults.ChannelsTxPower = mibSet->Param.ChannelsDefaultTxPower; } else { status = LORAMAC_STATUS_PARAMETER_INVALID; } break; } case MIB_CHANNELS_TX_POWER: { if( ValueInRange( mibSet->Param.ChannelsTxPower, LORAMAC_MAX_TX_POWER, LORAMAC_MIN_TX_POWER ) ) { LoRaMacParams.ChannelsTxPower = mibSet->Param.ChannelsTxPower; } else { status = LORAMAC_STATUS_PARAMETER_INVALID; } break; } case MIB_UPLINK_COUNTER: { UpLinkCounter = mibSet->Param.UpLinkCounter; break; } case MIB_DOWNLINK_COUNTER: { DownLinkCounter = mibSet->Param.DownLinkCounter; break; } case MIB_SYSTEM_MAX_RX_ERROR: { LoRaMacParams.SystemMaxRxError = LoRaMacParamsDefaults.SystemMaxRxError = mibSet->Param.SystemMaxRxError; break; } case MIB_MIN_RX_SYMBOLS: { LoRaMacParams.MinRxSymbols = LoRaMacParamsDefaults.MinRxSymbols = mibSet->Param.MinRxSymbols; break; } default: status = LORAMAC_STATUS_SERVICE_UNKNOWN; break; } return status; } LoRaMacStatus_t LoRaMacMulticastChannelLink( MulticastParams_t *channelParam ) { if( channelParam == NULL ) { return LORAMAC_STATUS_PARAMETER_INVALID; } if( ( LoRaMacState & LORAMAC_TX_RUNNING ) == LORAMAC_TX_RUNNING ) { return LORAMAC_STATUS_BUSY; } // Reset downlink counter channelParam->DownLinkCounter = 0; if( MulticastChannels == NULL ) { // New node is the fist element MulticastChannels = channelParam; } else { MulticastParams_t *cur = MulticastChannels; // Search the last node in the list while( cur->Next != NULL ) { cur = cur->Next; } // This function always finds the last node cur->Next = channelParam; } return LORAMAC_STATUS_OK; } LoRaMacStatus_t LoRaMacMulticastChannelUnlink( MulticastParams_t *channelParam ) { if( channelParam == NULL ) { return LORAMAC_STATUS_PARAMETER_INVALID; } if( ( LoRaMacState & LORAMAC_TX_RUNNING ) == LORAMAC_TX_RUNNING ) { return LORAMAC_STATUS_BUSY; } if( MulticastChannels != NULL ) { if( MulticastChannels == channelParam ) { // First element MulticastChannels = channelParam->Next; } else { MulticastParams_t *cur = MulticastChannels; // Search the node in the list while( cur->Next && cur->Next != channelParam ) { cur = cur->Next; } // If we found the node, remove it if( cur->Next ) { cur->Next = channelParam->Next; } } channelParam->Next = NULL; } return LORAMAC_STATUS_OK; } LoRaMacStatus_t LoRaMacMlmeRequest( MlmeReq_t *mlmeRequest ) { LoRaMacStatus_t status = LORAMAC_STATUS_SERVICE_UNKNOWN; LoRaMacHeader_t macHdr; if( mlmeRequest == NULL ) { return LORAMAC_STATUS_PARAMETER_INVALID; } if( ( LoRaMacState & LORAMAC_TX_RUNNING ) == LORAMAC_TX_RUNNING ) { return LORAMAC_STATUS_BUSY; } memset1( ( uint8_t* ) &MlmeConfirm, 0, sizeof( MlmeConfirm ) ); MlmeConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_MLMEREQ; switch( mlmeRequest->Type ) { case MLME_JOIN: { if( ( LoRaMacState & LORAMAC_TX_DELAYED ) == LORAMAC_TX_DELAYED ) { return LORAMAC_STATUS_BUSY; } if( ( mlmeRequest->Req.Join.DevEui == NULL ) || ( mlmeRequest->Req.Join.AppEui == NULL ) || ( mlmeRequest->Req.Join.AppKey == NULL ) || ( mlmeRequest->Req.Join.NbTrials == 0 ) ) { return LORAMAC_STATUS_PARAMETER_INVALID; } // Enables at least the usage of all datarates. if( mlmeRequest->Req.Join.NbTrials < 48 ) { mlmeRequest->Req.Join.NbTrials = 48; } LoRaMacFlags.Bits.MlmeReq = 1; MlmeConfirm.MlmeRequest = mlmeRequest->Type; LoRaMacDevEui = mlmeRequest->Req.Join.DevEui; LoRaMacAppEui = mlmeRequest->Req.Join.AppEui; LoRaMacAppKey = mlmeRequest->Req.Join.AppKey; MaxJoinRequestTrials = mlmeRequest->Req.Join.NbTrials; // Reset variable JoinRequestTrials JoinRequestTrials = 0; // Setup header information macHdr.Value = 0; macHdr.Bits.MType = FRAME_TYPE_JOIN_REQ; ResetMacParameters( ); Channel = 0; // start with first channel isr_printf("<ch0>"); isr_printf("mlme-join-send ch%u\r\n", Channel); status = Send( &macHdr, 0, NULL, 0 ); break; } case MLME_LINK_CHECK: { LoRaMacFlags.Bits.MlmeReq = 1; // LoRaMac will send this command piggy-pack MlmeConfirm.MlmeRequest = mlmeRequest->Type; status = AddMacCommand( MOTE_MAC_LINK_CHECK_REQ, 0, 0 ); break; } case MLME_TXCW: { MlmeConfirm.MlmeRequest = mlmeRequest->Type; LoRaMacFlags.Bits.MlmeReq = 1; status = SetTxContinuousWave( mlmeRequest->Req.TxCw.Timeout ); break; } case MLME_TXCW_1: { MlmeConfirm.MlmeRequest = mlmeRequest->Type; LoRaMacFlags.Bits.MlmeReq = 1; status = SetTxContinuousWave1( mlmeRequest->Req.TxCw.Timeout, mlmeRequest->Req.TxCw.Frequency, mlmeRequest->Req.TxCw.Power ); break; } default: break; } if( status != LORAMAC_STATUS_OK ) { NodeAckRequested = false; LoRaMacFlags.Bits.MlmeReq = 0; } return status; } LoRaMacStatus_t LoRaMacMcpsRequest( McpsReq_t *mcpsRequest ) { LoRaMacStatus_t status = LORAMAC_STATUS_SERVICE_UNKNOWN; LoRaMacHeader_t macHdr; uint8_t fPort = 0; void *fBuffer; uint16_t fBufferSize; bool readyToSend = false; if( mcpsRequest == NULL ) { return LORAMAC_STATUS_PARAMETER_INVALID; } if( ( ( LoRaMacState & LORAMAC_TX_RUNNING ) == LORAMAC_TX_RUNNING ) || ( ( LoRaMacState & LORAMAC_TX_DELAYED ) == LORAMAC_TX_DELAYED ) ) { return LORAMAC_STATUS_BUSY; } macHdr.Value = 0; memset1 ( ( uint8_t* ) &McpsConfirm, 0, sizeof( McpsConfirm ) ); McpsConfirm.Status = LORAMAC_EVENT_INFO_STATUS_ERROR_MCPSREQ; switch( mcpsRequest->Type ) { case MCPS_UNCONFIRMED: { readyToSend = true; macHdr.Bits.MType = FRAME_TYPE_DATA_UNCONFIRMED_UP; fPort = mcpsRequest->Req.Unconfirmed.fPort; fBuffer = mcpsRequest->Req.Unconfirmed.fBuffer; fBufferSize = mcpsRequest->Req.Unconfirmed.fBufferSize; break; } case MCPS_CONFIRMED: { readyToSend = true; macHdr.Bits.MType = FRAME_TYPE_DATA_CONFIRMED_UP; fPort = mcpsRequest->Req.Confirmed.fPort; fBuffer = mcpsRequest->Req.Confirmed.fBuffer; fBufferSize = mcpsRequest->Req.Confirmed.fBufferSize; break; } case MCPS_PROPRIETARY: { readyToSend = true; macHdr.Bits.MType = FRAME_TYPE_PROPRIETARY; fBuffer = mcpsRequest->Req.Proprietary.fBuffer; fBufferSize = mcpsRequest->Req.Proprietary.fBufferSize; break; } default: break; } if( readyToSend == true ) { status = Send( &macHdr, fPort, fBuffer, fBufferSize ); if( status == LORAMAC_STATUS_OK ) { McpsConfirm.McpsRequest = mcpsRequest->Type; LoRaMacFlags.Bits.McpsReq = 1; } else { NodeAckRequested = false; } } return status; } void LoRaMacTestRxWindowsOn( bool enable ) { IsRxWindowsEnabled = enable; } void LoRaMacTestSetMic( uint16_t txPacketCounter ) { UpLinkCounter = txPacketCounter; IsUpLinkCounterFixed = true; } void LoRaMacTestSetChannel( uint8_t channel ) { isr_printf("set-testch%u\r\n", channel); Channel = channel; } static RxConfigParams_t ComputeRxWindowParameters( int8_t datarate, uint32_t rxError ) { RxConfigParams_t rxConfigParams = { 0, 0, 0, 0 }; double tSymbol = 0.0; rxConfigParams.Datarate = datarate; switch( Bandwidths[datarate] ) { default: case 125000: rxConfigParams.Bandwidth = 0; break; case 250000: rxConfigParams.Bandwidth = 1; break; case 500000: rxConfigParams.Bandwidth = 2; break; } #if defined( USE_BAND_433 ) || defined( USE_BAND_780 ) || defined( USE_BAND_868 ) if( datarate == DR_7 ) { // FSK tSymbol = ( 1.0 / ( double )Datarates[datarate] ) * 8.0; // 1 symbol equals 1 byte } else #endif { // LoRa tSymbol = ( ( double )( 1 << Datarates[datarate] ) / ( double )Bandwidths[datarate] ) * 1e3; } rxConfigParams.RxWindowTimeout = MAX( ( uint32_t )ceil( ( ( 2 * LoRaMacParams.MinRxSymbols - 8 ) * tSymbol + 2 * rxError ) / tSymbol ), LoRaMacParams.MinRxSymbols ); // Computed number of symbols rxConfigParams.RxOffset = ( int32_t )ceil( ( 4.0 * tSymbol ) - ( ( rxConfigParams.RxWindowTimeout * tSymbol ) / 2.0 ) - RADIO_WAKEUP_TIME ); return rxConfigParams; }