LoRaWAN-lib publish

Fork of LoRaWAN-lib by Christopher De Bank

Revision:
1:91e4e6c60d1e
Parent:
0:91d1a7783bb9
Child:
2:14a5d6ad92d5
--- a/LoRaMac.cpp	Tue Oct 20 13:21:26 2015 +0000
+++ b/LoRaMac.cpp	Mon Nov 23 10:09:43 2015 +0000
@@ -12,10 +12,7 @@
 
 Maintainer: Miguel Luis and Gregory Cristian
 */
-#include "mbed.h"
 #include "board.h"
-#include "utilities.h"
-#include "sx1276-hal.h"
 
 #include "LoRaMacCrypto.h"
 #include "LoRaMac.h"
@@ -23,7 +20,12 @@
 /*!
  * Maximum PHY layer payload size
  */
-#define LORAMAC_PHY_MAXPAYLOAD                      250
+#define LORAMAC_PHY_MAXPAYLOAD                      255
+
+/*!
+ * Maximum MAC commands buffer size
+ */
+#define LORA_MAC_COMMAND_MAX_LENGTH                 15
 
 /*!
  * Device IEEE EUI
@@ -173,7 +175,7 @@
 /*!
  * Buffer containing the MAC layer commands
  */
-static uint8_t MacCommandsBuffer[15];
+static uint8_t MacCommandsBuffer[LORA_MAC_COMMAND_MAX_LENGTH];
 
 #if defined( USE_BAND_433 )
 /*!
@@ -449,7 +451,7 @@
 /*!
  * LoRaMac upper layer event functions
  */
-static LoRaMacEvent_t *LoRaMacEvents;
+static LoRaMacCallbacks_t *LoRaMacCallbacks;
 
 /*!
  * LoRaMac notification event flags
@@ -581,8 +583,7 @@
 /*!
  * Radio events function pointer
  */
-//static RadioEvents_t RadioEvents;
-SX1276MB1xAS Radio( OnRadioTxDone, OnRadioTxTimeout, OnRadioRxDone, OnRadioRxTimeout, OnRadioRxError, NULL, NULL );
+static RadioEvents_t RadioEvents;
 
 /*!
  * \brief Validates if the payload fits into the frame, taking the datarate
@@ -633,7 +634,7 @@
     uint8_t enabledChannels[LORA_MAX_NB_CHANNELS];
     TimerTime_t curTime = TimerGetCurrentTime( );
 
-    memset( enabledChannels, 0, LORA_MAX_NB_CHANNELS );
+    memset1( enabledChannels, 0, LORA_MAX_NB_CHANNELS );
 
     // Update Aggregated duty cycle
     if( AggregatedTimeOff < ( curTime - AggregatedLastTxDoneTime ) )
@@ -741,70 +742,99 @@
  * \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]
+ * \retval status  Function status [0: OK, 1: Unknown command, 2: Busy]
  */
 static uint8_t AddMacCommand( uint8_t cmd, uint8_t p1, uint8_t p2 )
 {
-    MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
+    uint8_t status = 2; // Busy
+
     switch( cmd )
     {
         case MOTE_MAC_LINK_CHECK_REQ:
-            // No payload for this command
+            if( MacCommandsBufferIndex < LORA_MAC_COMMAND_MAX_LENGTH )
+            {
+                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
+                // No payload for this command
+                status = 0; // OK
+            }
             break;
         case MOTE_MAC_LINK_ADR_ANS:
-            // Margin
-            MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
+            if( MacCommandsBufferIndex < ( LORA_MAC_COMMAND_MAX_LENGTH - 1 ) )
+            {
+                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
+                // Margin
+                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
+                status = 0; // OK
+            }
             break;
         case MOTE_MAC_DUTY_CYCLE_ANS:
-            // No payload for this answer
+            if( MacCommandsBufferIndex < LORA_MAC_COMMAND_MAX_LENGTH )
+            {
+                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
+                // No payload for this answer
+                status = 0; // OK
+            }
             break;
         case MOTE_MAC_RX_PARAM_SETUP_ANS:
-            // Status: Datarate ACK, Channel ACK
-            MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
+            if( MacCommandsBufferIndex < ( LORA_MAC_COMMAND_MAX_LENGTH - 1 ) )
+            {
+                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
+                // Status: Datarate ACK, Channel ACK
+                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
+                status = 0; // OK
+            }
             break;
         case MOTE_MAC_DEV_STATUS_ANS:
-            // 1st byte Battery
-            // 2nd byte Margin
-            MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
-            MacCommandsBuffer[MacCommandsBufferIndex++] = p2;
+            if( MacCommandsBufferIndex < ( LORA_MAC_COMMAND_MAX_LENGTH - 2 ) )
+            {
+                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
+                // 1st byte Battery
+                // 2nd byte Margin
+                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
+                MacCommandsBuffer[MacCommandsBufferIndex++] = p2;
+                status = 0; // OK
+            }
             break;
         case MOTE_MAC_NEW_CHANNEL_ANS:
-            // Status: Datarate range OK, Channel frequency OK
-            MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
+            if( MacCommandsBufferIndex < ( LORA_MAC_COMMAND_MAX_LENGTH - 1 ) )
+            {
+                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
+                // Status: Datarate range OK, Channel frequency OK
+                MacCommandsBuffer[MacCommandsBufferIndex++] = p1;
+                status = 0; // OK
+            }
             break;
         case MOTE_MAC_RX_TIMING_SETUP_ANS:
-            // No payload for this answer
+            if( MacCommandsBufferIndex < LORA_MAC_COMMAND_MAX_LENGTH )
+            {
+                MacCommandsBuffer[MacCommandsBufferIndex++] = cmd;
+                // No payload for this answer
+                status = 0; // OK
+            }
             break;
         default:
-            return 1;
+            return 1; // Unknown command
     }
-    if( MacCommandsBufferIndex <= 15 )
+    if( status == 0 )
     {
         MacCommandsInNextTx = true;
-        return 0;
     }
-    else
-    {
-        return 2;
-    }
+    return status;
 }
 
 // TODO: Add Documentation
 static void LoRaMacNotify( LoRaMacEventFlags_t *flags, LoRaMacEventInfo_t *info )
 {
-    if( ( LoRaMacEvents != NULL ) && ( LoRaMacEvents->MacEvent != NULL ) )
+    if( ( LoRaMacCallbacks != NULL ) && ( LoRaMacCallbacks->MacEvent != NULL ) )
     {
-        LoRaMacEvents->MacEvent( flags, info );
+        LoRaMacCallbacks->MacEvent( flags, info );
     }
     flags->Value = 0;
 }
 
-typedef uint8_t ( *GetBatteryLevel )( );
-GetBatteryLevel LoRaMacGetBatteryLevel;
-
-void LoRaMacInit( LoRaMacEvent_t *events, uint8_t ( *getBatteryLevel )( ) )
+void LoRaMacInit( LoRaMacCallbacks_t *callbacks )
 {
-    LoRaMacEvents = events;
+    LoRaMacCallbacks = callbacks;
 
     LoRaMacEventFlags.Value = 0;
     
@@ -821,8 +851,6 @@
     LoRaMacEventInfo.NbGateways = 0;
     LoRaMacEventInfo.Status = LORAMAC_EVENT_INFO_STATUS_OK;
    
-    LoRaMacGetBatteryLevel = getBatteryLevel;
-    
     LoRaMacDeviceClass = CLASS_A;
     
     UpLinkCounter = 1;
@@ -908,9 +936,17 @@
     TimerInit( &RxWindowTimer1, OnRxWindow1TimerEvent );
     TimerInit( &RxWindowTimer2, OnRxWindow2TimerEvent );
     TimerInit( &AckTimeoutTimer, OnAckTimeoutTimerEvent );
+    
+    // Initialize Radio driver
+    RadioEvents.TxDone = OnRadioTxDone;
+    RadioEvents.RxDone = OnRadioRxDone;
+    RadioEvents.RxError = OnRadioRxError;
+    RadioEvents.TxTimeout = OnRadioTxTimeout;
+    RadioEvents.RxTimeout = OnRadioRxTimeout;
+    Radio.Init( &RadioEvents );
 
     // Random seed initialization
-    srand( Radio.Random( ) );
+    srand1( Radio.Random( ) );
 
     // Initialize channel index.
     Channel = LORA_MAX_NB_CHANNELS;
@@ -1035,7 +1071,7 @@
 #if defined( USE_BAND_915 ) || defined( USE_BAND_915_HYBRID )
     static uint8_t drSwitch = 0;
     
-    if( drSwitch == 0 )
+    if( ( ++drSwitch & 0x01 ) == 0x01 )
     {
         ChannelsDatarate = DR_0;
     }
@@ -1043,7 +1079,6 @@
     {
         ChannelsDatarate = DR_4;
     }
-    drSwitch = ( drSwitch + 1 ) % 2;
 #endif
     return LoRaMacSend( &macHdr, NULL, 0, NULL, 0 );
 }
@@ -1268,9 +1303,9 @@
                         LoRaMacBuffer[pktHeaderLen++] = MacCommandsBuffer[i];
                     }
                 }
-                MacCommandsInNextTx = false;
-                MacCommandsBufferIndex = 0;
             }
+            MacCommandsInNextTx = false;
+            MacCommandsBufferIndex = 0;
             
             if( ( pktHeaderLen + fBufferSize ) > LORAMAC_PHY_MAXPAYLOAD )
             {
@@ -1448,10 +1483,11 @@
                         // Data rate ACK = 0
                         // Channel mask  = 0
                         AddMacCommand( MOTE_MAC_LINK_ADR_ANS, 0, 0 );
+                        macIndex += 3;  // Skip over the remaining bytes of the request
                         break;
                     }
-                    chMask = payload[macIndex++];
-                    chMask |= payload[macIndex++] << 8;
+                    chMask = ( uint16_t )payload[macIndex++];
+                    chMask |= ( uint16_t )payload[macIndex++] << 8;
 
                     nbRep = payload[macIndex++];
                     chMaskCntl = ( nbRep >> 4 ) & 0x07;
@@ -1590,10 +1626,9 @@
                     int8_t drOffset = 0;
                     uint32_t freq = 0;
                 
-                    drOffset = payload[macIndex++];
-                    datarate = drOffset & 0x0F;
-                    drOffset = ( drOffset >> 4 ) & 0x0F;
-                    
+                    drOffset = ( payload[macIndex] >> 4 ) & 0x07;
+                    datarate = payload[macIndex] & 0x0F;
+                    macIndex++;
                     freq = ( uint32_t )payload[macIndex++];
                     freq |= ( uint32_t )payload[macIndex++] << 8;
                     freq |= ( uint32_t )payload[macIndex++] << 16;
@@ -1610,7 +1645,8 @@
                         status &= 0xFD; // Datarate KO
                     }
 
-                    if( ( ( drOffset < 0 ) || ( drOffset > 5 ) ) == true )
+                    if( ( ( drOffset < LORAMAC_MIN_RX1_DR_OFFSET ) ||
+                          ( drOffset > LORAMAC_MAX_RX1_DR_OFFSET ) ) == true )
                     {
                         status &= 0xFB; // Rx1DrOffset range KO
                     }
@@ -1625,7 +1661,14 @@
                 }
                 break;
             case SRV_MAC_DEV_STATUS_REQ:
-                AddMacCommand( MOTE_MAC_DEV_STATUS_ANS, LoRaMacGetBatteryLevel( ), LoRaMacEventInfo.RxSnr );
+                {
+                    uint8_t batteryLevel = BAT_LEVEL_NO_MEASURE;
+                    if( ( LoRaMacCallbacks != NULL ) && ( LoRaMacCallbacks->GetBatteryLevel != NULL ) )
+                    {
+                        batteryLevel = LoRaMacCallbacks->GetBatteryLevel( );
+                    }
+                    AddMacCommand( MOTE_MAC_DEV_STATUS_ANS, batteryLevel, LoRaMacEventInfo.RxSnr );
+                }
                 break;
             case SRV_MAC_NEW_CHANNEL_REQ:
                 {
@@ -1723,6 +1766,12 @@
             TimerSetValue( &RxWindowTimer2, RxWindow2Delay );
             TimerStart( &RxWindowTimer2 );
         }
+        if( ( LoRaMacDeviceClass == CLASS_C ) || ( NodeAckRequested == true ) )
+        {
+            TimerSetValue( &AckTimeoutTimer, RxWindow2Delay + ACK_TIMEOUT +
+                                             randr( -ACK_TIMEOUT_RND, ACK_TIMEOUT_RND ) );
+            TimerStart( &AckTimeoutTimer );
+        }
     }
     else
     {
@@ -1732,6 +1781,7 @@
     
     if( NodeAckRequested == false )
     {
+        LoRaMacEventInfo.Status = LORAMAC_EVENT_INFO_STATUS_OK;
         ChannelsNbRepCounter++;
     }
 }
@@ -1746,18 +1796,20 @@
 
     uint8_t pktHeaderLen = 0;
     uint32_t address = 0;
-    uint16_t sequenceCounter = 0;
-    int32_t sequence = 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;
-    uint32_t downLinkCounter = 0;
     
     bool isMicOk = false;
 
@@ -1814,7 +1866,16 @@
                 // DLSettings
                 Rx1DrOffset = ( LoRaMacRxPayload[11] >> 4 ) & 0x07;
                 Rx2Channel.Datarate = LoRaMacRxPayload[11] & 0x0F;
-                
+#if defined( USE_BAND_915 ) || defined( USE_BAND_915_HYBRID )
+                /*
+                 * WARNING: To be removed once Semtech server implementation
+                 *          is corrected.
+                 */
+                if( Rx2Channel.Datarate == DR_3 )
+                {
+                    Rx2Channel.Datarate = DR_8;
+                }
+#endif
                 // RxDelay
                 ReceiveDelay1 = ( LoRaMacRxPayload[12] & 0x0F );
                 if( ReceiveDelay1 == 0 )
@@ -1847,8 +1908,6 @@
             {
                 LoRaMacEventInfo.Status = LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL;
             }
-
-            LoRaMacEventFlags.Bits.Tx = 1;
             break;
         case FRAME_TYPE_DATA_CONFIRMED_DOWN:
         case FRAME_TYPE_DATA_UNCONFIRMED_DOWN:
@@ -1898,8 +1957,8 @@
                 }
                 fCtrl.Value = payload[pktHeaderLen++];
                 
-                sequenceCounter |= ( uint32_t )payload[pktHeaderLen++];
-                sequenceCounter |= ( uint32_t )payload[pktHeaderLen++] << 8;
+                sequenceCounter = ( uint16_t )payload[pktHeaderLen++];
+                sequenceCounter |= ( uint16_t )payload[pktHeaderLen++] << 8;
 
                 appPayloadStartIndex = 8 + fCtrl.Bits.FOptsLen;
 
@@ -1908,45 +1967,39 @@
                 micRx |= ( (uint32_t)payload[size - LORAMAC_MFR_LEN + 2] << 16 );
                 micRx |= ( (uint32_t)payload[size - LORAMAC_MFR_LEN + 3] << 24 );
 
-                sequence = ( int32_t )sequenceCounter - ( int32_t )( downLinkCounter & 0xFFFF );
-                if( sequence < 0 )
+                sequenceCounterPrev = ( uint16_t )downLinkCounter;
+                sequenceCounterDiff = ( sequenceCounter - sequenceCounterPrev );
+
+                if( sequenceCounterDiff < ( 1 << 15 ) )
                 {
-                    // sequence reset or roll over happened
-                    downLinkCounter = ( downLinkCounter & 0xFFFF0000 ) | ( sequenceCounter + ( uint32_t )0x10000 );
+                    downLinkCounter += sequenceCounterDiff;
                     LoRaMacComputeMic( payload, size - LORAMAC_MFR_LEN, nwkSKey, address, DOWN_LINK, downLinkCounter, &mic );
                     if( micRx == mic )
                     {
                         isMicOk = true;
                     }
-                    else
-                    {
-                        isMicOk = false;
-                        // sequence reset
-                        if( LoRaMacEventFlags.Bits.Multicast == 1 )
-                        {
-                            curMulticastParams->DownLinkCounter = downLinkCounter = sequenceCounter;
-                        }
-                        else
-                        {
-                            DownLinkCounter = downLinkCounter = sequenceCounter;
-                        }
-                        LoRaMacComputeMic( payload, size - LORAMAC_MFR_LEN, nwkSKey, address, DOWN_LINK, downLinkCounter, &mic );
-                    }
                 }
                 else
                 {
-                    downLinkCounter = ( downLinkCounter & 0xFFFF0000 ) | sequenceCounter;
-                    LoRaMacComputeMic( payload, size - LORAMAC_MFR_LEN, nwkSKey, address, DOWN_LINK, downLinkCounter, &mic );
+                    // check for downlink counter 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;
+                    }
                 }
 
-                if( ( isMicOk == true ) ||
-                    ( micRx == mic ) )
+                if( isMicOk == true )
                 {
                     LoRaMacEventFlags.Bits.Rx = 1;
                     LoRaMacEventInfo.RxSnr = snr;
                     LoRaMacEventInfo.RxRssi = rssi;
                     LoRaMacEventInfo.RxBufferSize = 0;
                     AdrAckCounter = 0;
+
+                    // Update 32 bits downlink counter
                     if( LoRaMacEventFlags.Bits.Multicast == 1 )
                     {
                         curMulticastParams->DownLinkCounter = downLinkCounter;
@@ -2025,27 +2078,29 @@
                         }
                     }
 
-                    LoRaMacEventFlags.Bits.Tx = 1;
                     LoRaMacEventInfo.Status = LORAMAC_EVENT_INFO_STATUS_OK;
                 }
                 else
                 {
                     LoRaMacEventInfo.TxAckReceived = false;
                     
-                    LoRaMacEventFlags.Bits.Tx = 1;
                     LoRaMacEventInfo.Status = LORAMAC_EVENT_INFO_STATUS_MIC_FAIL;
                     LoRaMacState &= ~MAC_TX_RUNNING;
+                    if( NodeAckRequested == true )
+                    {
+                        OnAckTimeoutTimerEvent( );
+                    }
                 }
             }
             break;
         case FRAME_TYPE_PROPRIETARY:
             //Intentional falltrough
         default:
-            LoRaMacEventFlags.Bits.Tx = 1;
             LoRaMacEventInfo.Status = LORAMAC_EVENT_INFO_STATUS_ERROR;
             LoRaMacState &= ~MAC_TX_RUNNING;
             break;
     }
+    LoRaMacEventFlags.Bits.Tx = 1;
 }
 
 /*!
@@ -2075,6 +2130,10 @@
     {
         Radio.Sleep( );
     }
+    else
+    {
+        OnRxWindow2TimerEvent( );
+    }
     if( LoRaMacEventFlags.Bits.RxSlot == 1 )
     {
         LoRaMacEventFlags.Bits.Tx = 1;
@@ -2091,6 +2150,10 @@
     {
         Radio.Sleep( );
     }
+    else
+    {
+        OnRxWindow2TimerEvent( );
+    }
     if( LoRaMacEventFlags.Bits.RxSlot == 1 )
     {
         LoRaMacEventFlags.Bits.Tx = 1;
@@ -2108,17 +2171,36 @@
  */
 void LoRaMacRxWindowSetup( uint32_t freq, int8_t datarate, uint32_t bandwidth, uint16_t timeout, bool rxContinuous )
 {
-    if( Radio.GetStatus( ) == IDLE )
+    uint8_t downlinkDatarate = Datarates[datarate];
+    RadioModems_t modem;
+
+    if( Radio.GetStatus( ) == RF_IDLE )
     {
         Radio.SetChannel( freq );
+#if defined( USE_BAND_433 ) || defined( USE_BAND_780 ) || defined( USE_BAND_868 )
         if( datarate == DR_7 )
         {
-            Radio.SetRxConfig( MODEM_FSK, 50e3, Datarates[datarate] * 1e3, 0, 83.333e3, 5, 0, false, 0, true, 0, 0, false, rxContinuous );
+            modem = MODEM_FSK;
+            Radio.SetRxConfig( MODEM_FSK, 50e3, downlinkDatarate * 1e3, 0, 83.333e3, 5, 0, false, 0, true, 0, 0, false, rxContinuous );
         }
         else
         {
-            Radio.SetRxConfig( MODEM_LORA, bandwidth, Datarates[datarate], 1, 0, 8, timeout, false, 0, false, 0, 0, true, rxContinuous );
+            modem = MODEM_LORA;
+            Radio.SetRxConfig( MODEM_LORA, bandwidth, downlinkDatarate, 1, 0, 8, timeout, false, 0, false, 0, 0, true, rxContinuous );
         }
+#elif defined( USE_BAND_915 ) || defined( USE_BAND_915_HYBRID )
+        modem = MODEM_LORA;
+        Radio.SetRxConfig( MODEM_LORA, bandwidth, downlinkDatarate, 1, 0, 8, timeout, false, 0, false, 0, 0, true, rxContinuous );
+#endif
+        if( RepeaterSupport == true )
+        {
+            Radio.SetMaxPayloadLength( modem, MaxPayloadOfDatarateRepeater[datarate] );
+        }
+        else
+        {
+            Radio.SetMaxPayloadLength( modem, MaxPayloadOfDatarate[datarate] );
+        }
+
         if( rxContinuous == false )
         {
             Radio.Rx( MaxRxWindow );
@@ -2142,6 +2224,10 @@
     TimerStop( &RxWindowTimer1 );
     LoRaMacEventFlags.Bits.RxSlot = 0;
 
+    if( LoRaMacDeviceClass == CLASS_C )
+    {
+        Radio.Standby( );
+    }
 #if defined( USE_BAND_433 ) || defined( USE_BAND_780 ) || defined( USE_BAND_868 )
     datarate = ChannelsDatarate - Rx1DrOffset;
     if( datarate < 0 )
@@ -2174,7 +2260,6 @@
     {// LoRa 500 kHz
         bandwidth  = 2;
     }
-    //LoRaMacRxWindowSetup( Channels[Channel].Frequency, datarate, bandwidth, symbTimeout, false );
     LoRaMacRxWindowSetup( 923.3e6 + ( Channel % 8 ) * 600e3, datarate, bandwidth, symbTimeout, false );
 #else
     #error "Please define a frequency band in the compiler options."
@@ -2338,7 +2423,6 @@
                 {
                     UpLinkCounter++;
                 }
-                LoRaMacEventInfo.Status = LORAMAC_EVENT_INFO_STATUS_OK;
             }
         }
     }
@@ -2350,6 +2434,7 @@
     if( LoRaMacState == MAC_IDLE )
     {
         LoRaMacNotify( &LoRaMacEventFlags, &LoRaMacEventInfo );
+        LoRaMacEventFlags.Bits.Tx = 0;
     }
     else
     {