public ILoRaDeviceMessageDeduplicationStrategy Create(LoRaDevice loRaDevice)
        {
            if (loRaDevice is null)
            {
                throw new ArgumentNullException(nameof(loRaDevice));
            }

            if (!string.IsNullOrEmpty(loRaDevice.GatewayID))
            {
                this.logger.LogDebug("LoRa device has a specific gateway assigned. Ignoring deduplication as it is not applicable.");
                return(null);
            }

            switch (loRaDevice.Deduplication)
            {
            case DeduplicationMode.Drop: return(new DeduplicationStrategyDrop(this.loggerFactory.CreateLogger <DeduplicationStrategyDrop>()));

            case DeduplicationMode.Mark: return(new DeduplicationStrategyMark(this.loggerFactory.CreateLogger <DeduplicationStrategyMark>()));

            case DeduplicationMode.None:
            default:
            {
                this.logger.LogDebug("no Deduplication Strategy selected");
                return(null);
            }
            }
        }
        /// <summary>
        /// Gets the available time to check for cloud to device messages
        /// It takes into consideration available time and <see cref="LoRaDevice.PreferredWindow"/>.
        /// </summary>
        /// <returns><see cref="TimeSpan.Zero"/> if there is no enough time or a positive <see cref="TimeSpan"/> value.</returns>
        public TimeSpan GetAvailableTimeToCheckCloudToDeviceMessage(LoRaDevice loRaDevice)
        {
            if (loRaDevice is null)
            {
                throw new ArgumentNullException(nameof(loRaDevice));
            }

            var elapsed = GetElapsedTime();

            if (loRaDevice.PreferredWindow is ReceiveWindow1)
            {
                var availableTimeForFirstWindow = TimeSpan.FromSeconds(GetReceiveWindow1Delay(loRaDevice)).Subtract(elapsed.Add(ExpectedTimeToPackageAndSendMessageAndCheckForCloudMessageOverhead));
                if (availableTimeForFirstWindow >= LoRaOperationTimeWatcher.MinimumAvailableTimeToCheckForCloudMessage)
                {
                    return(availableTimeForFirstWindow);
                }
            }

            // 2nd window
            var availableTimeForSecondWindow = TimeSpan.FromSeconds(GetReceiveWindow2Delay(loRaDevice)).Subtract(elapsed.Add(ExpectedTimeToPackageAndSendMessageAndCheckForCloudMessageOverhead));

            if (availableTimeForSecondWindow >= LoRaOperationTimeWatcher.MinimumAvailableTimeToCheckForCloudMessage)
            {
                return(availableTimeForSecondWindow);
            }

            return(TimeSpan.Zero);
        }
Пример #3
0
        public LoRaCloudToDeviceMessageWrapper(LoRaDevice loRaDevice, Message message)
        {
            this.loRaDevice = loRaDevice ?? throw new ArgumentNullException(nameof(loRaDevice));
            this.message    = message ?? throw new ArgumentNullException(nameof(message));

            this.ParseMessage();
        }
Пример #4
0
        /// <summary>
        /// Helper method to resolve FcntDown in case one was not yet acquired
        /// </summary>
        /// <returns>0 if the resolution failed or > 0 if a valid frame count was acquired</returns>
        async ValueTask <uint> EnsureHasFcntDownAsync(
            LoRaDevice loRaDevice,
            uint?fcntDown,
            uint payloadFcnt,
            ILoRaDeviceFrameCounterUpdateStrategy frameCounterStrategy)
        {
            if (fcntDown > 0)
            {
                return(fcntDown.Value);
            }

            var newFcntDown = await frameCounterStrategy.NextFcntDown(loRaDevice, payloadFcnt);

            // Failed to update the fcnt down
            // In multi gateway scenarios it means the another gateway was faster than using, can stop now
            if (newFcntDown <= 0)
            {
                Logger.Log(loRaDevice.DevEUI, "another gateway has already sent ack or downlink msg", LogLevel.Debug);
            }
            else
            {
                Logger.Log(loRaDevice.DevEUI, $"down frame counter: {loRaDevice.FCntDown}", LogLevel.Debug);
            }

            return(newFcntDown);
        }
        private async Task <LoRaADRResult> PerformADR(LoRaRequest request, LoRaDevice loRaDevice, LoRaPayloadData loraPayload, uint payloadFcnt, LoRaADRResult loRaADRResult, ILoRaDeviceFrameCounterUpdateStrategy frameCounterStrategy)
        {
            var loRaADRManager = this.loRaADRManagerFactory.Create(this.loRaADRStrategyProvider, frameCounterStrategy, loRaDevice);

            var loRaADRTableEntry = new LoRaADRTableEntry()
            {
                DevEUI    = loRaDevice.DevEUI,
                FCnt      = payloadFcnt,
                GatewayId = this.configuration.GatewayID,
                Snr       = request.Rxpk.Lsnr
            };

            // If the ADR req bit is not set we don't perform rate adaptation.
            if (!loraPayload.IsAdrReq)
            {
                _ = loRaADRManager.StoreADREntryAsync(loRaADRTableEntry);
            }
            else
            {
                loRaADRResult = await loRaADRManager.CalculateADRResultAndAddEntryAsync(
                    loRaDevice.DevEUI,
                    this.configuration.GatewayID,
                    payloadFcnt,
                    loRaDevice.FCntDown,
                    (float)request.Rxpk.RequiredSnr,
                    request.Region.GetDRFromFreqAndChan(request.Rxpk.Datr),
                    request.Region.TXPowertoMaxEIRP.Count - 1,
                    request.Region.MaxADRDataRate,
                    loRaADRTableEntry);

                Logger.Log(loRaDevice.DevEUI, $"device sent ADR ack request, computing an answer", LogLevel.Debug);
            }

            return(loRaADRResult);
        }
        void HandlePreferredGatewayChanges(
            LoRaRequest request,
            LoRaDevice loRaDevice,
            FunctionBundlerResult bundlerResult)
        {
            var preferredGatewayResult = bundlerResult.PreferredGatewayResult;

            if (preferredGatewayResult.IsSuccessful())
            {
                var currentIsPreferredGateway = bundlerResult.PreferredGatewayResult.PreferredGatewayID == this.configuration.GatewayID;

                var preferredGatewayChanged = bundlerResult.PreferredGatewayResult.PreferredGatewayID != loRaDevice.PreferredGatewayID;
                if (preferredGatewayChanged)
                {
                    Logger.Log(loRaDevice.DevEUI, $"preferred gateway changed from '{loRaDevice.PreferredGatewayID}' to '{preferredGatewayResult.PreferredGatewayID}'", LogLevel.Debug);
                }

                if (preferredGatewayChanged)
                {
                    loRaDevice.UpdatePreferredGatewayID(bundlerResult.PreferredGatewayResult.PreferredGatewayID, acceptChanges: !currentIsPreferredGateway);
                }

                // Save the region if we are the winning gateway and it changed
                if (request.Region.LoRaRegion != loRaDevice.LoRaRegion)
                {
                    loRaDevice.UpdateRegion(request.Region.LoRaRegion, acceptChanges: !currentIsPreferredGateway);
                }
            }
            else
            {
                Logger.Log(loRaDevice.DevEUI, $"failed to resolve preferred gateway: {preferredGatewayResult}", LogLevel.Error);
            }
        }
 private void CleanupOldDevAddr(LoRaDevice loRaDevice, DevAddr?oldDevAddr)
 {
     if (oldDevAddr is { } someOldDevAddr&& loRaDevice.DevAddr != someOldDevAddr)
     {
         this.deviceCache.CleanupOldDevAddrForDevice(loRaDevice, someOldDevAddr);
     }
 }
Пример #8
0
 public void Release(LoRaDevice loRaDevice)
 {
     if (this.managedConnections.TryRemove(this.GetConnectionCacheKey(loRaDevice.DevEUI), out var removedItem))
     {
         removedItem.DeviceClient.Dispose();
     }
 }
Пример #9
0
        private static DownlinkMessage BuildDownstreamMessage(LoRaDevice loRaDevice,
                                                              StationEui stationEUI,
                                                              ILogger logger,
                                                              ulong xTime,
                                                              ReceiveWindow?rx1,
                                                              ReceiveWindow rx2,
                                                              RxDelay lnsRxDelay,
                                                              LoRaPayloadData loRaMessage,
                                                              LoRaDeviceClassType deviceClassType,
                                                              uint?antennaPreference = null)
        {
            var messageBytes    = loRaMessage.Serialize(loRaDevice.AppSKey.Value, loRaDevice.NwkSKey.Value);
            var downlinkMessage = new DownlinkMessage(
                messageBytes,
                xTime,
                rx1, rx2,
                loRaDevice.DevEUI,
                lnsRxDelay,
                deviceClassType,
                stationEUI,
                antennaPreference
                );

            if (logger.IsEnabled(LogLevel.Debug))
            {
                logger.LogDebug($"{loRaMessage.MessageType} {JsonConvert.SerializeObject(downlinkMessage)}");
            }
            return(downlinkMessage);
        }
 public Task <bool> SaveChangesAsync(LoRaDevice loRaDevice)
 {
     if (loRaDevice is null)
     {
         throw new ArgumentNullException(nameof(loRaDevice));
     }
     return(InternalSaveChangesAsync(loRaDevice, force: false));
 }
 public ValueTask <uint> NextFcntDown(LoRaDevice loRaDevice, uint messageFcnt)
 {
     if (loRaDevice is null)
     {
         throw new ArgumentNullException(nameof(loRaDevice));
     }
     return(new ValueTask <uint>(loRaDevice.IncrementFcntDown(1)));
 }
 /// <summary>
 /// Gets the receive window 1 (RX1) delay in seconds
 /// It takes into consideration region and device settings.
 /// </summary>
 /// <returns>Integer containing the delay in seconds.</returns>
 public uint GetReceiveWindow1Delay(LoRaDevice loRaDevice)
 {
     if (loRaDevice is null)
     {
         throw new ArgumentNullException(nameof(loRaDevice));
     }
     return(CalculateRXWindowsTime((ushort)this.loraRegion.ReceiveDelay1.ToSeconds(), loRaDevice.ReportedRXDelay));
 }
 public ILoRaDeviceClient GetClient(LoRaDevice loRaDevice)
 {
     if (loRaDevice is null)
     {
         throw new ArgumentNullException(nameof(loRaDevice));
     }
     return(GetClient(loRaDevice.DevEUI));
 }
 // Initializes a device instance created
 // For ABP increment down count by 10 to take into consideration failed save attempts
 void ILoRaDeviceInitializer.Initialize(LoRaDevice loRaDevice)
 {
     // In order to handle a scenario where the network server is restarted and the fcntDown was not yet saved (we save every 10)
     if (loRaDevice.IsABP)
     {
         loRaDevice.IncrementFcntDown(10);
     }
 }
        private async Task <bool> InternalSaveChangesAsync(LoRaDevice loRaDevice, bool force)
        {
            if (loRaDevice.FCntUp % 10 == 0 || force)
            {
                return(await loRaDevice.SaveFrameCountChangesAsync());
            }

            return(true);
        }
 // Initializes a device instance created
 // For ABP increment down count by 10 to take into consideration failed save attempts
 void ILoRaDeviceInitializer.Initialize(LoRaDevice loRaDevice)
 {
     // In order to handle a scenario where the network server is restarted and the fcntDown was not yet saved (we save every 10)
     if (loRaDevice.IsABP)
     {
         // Increment so that the next frame count down causes the count to be saved
         loRaDevice.IncrementFcntDown(Constants.MAX_FCNT_UNSAVED_DELTA - 1);
     }
 }
Пример #17
0
        public ILoRaDeviceClient Get(LoRaDevice loRaDevice)
        {
            if (this.managedConnections.TryGetValue(this.GetConnectionCacheKey(loRaDevice.DevEUI), out var managedConnection))
            {
                return(managedConnection.DeviceClient);
            }

            throw new ManagedConnectionException($"Connection for device {loRaDevice.DevEUI} was not found");
        }
Пример #18
0
        public FunctionBundler CreateIfRequired(
            string gatewayId,
            LoRaPayloadData loRaPayload,
            LoRaDevice loRaDevice,
            IDeduplicationStrategyFactory deduplicationFactory,
            LoRaRequest request)
        {
            if (!string.IsNullOrEmpty(loRaDevice.GatewayID))
            {
                // single gateway mode
                return(null);
            }

            var context = new FunctionBundlerExecutionContext
            {
                DeduplicationFactory = deduplicationFactory,
                FCntDown             = loRaDevice.FCntDown,
                FCntUp      = loRaPayload.GetFcnt(),
                GatewayId   = gatewayId,
                LoRaDevice  = loRaDevice,
                LoRaPayload = loRaPayload,
                Request     = request
            };

            var qualifyingExecutionItems = new List <IFunctionBundlerExecutionItem>(functionItems.Count);

            for (var i = 0; i < functionItems.Count; i++)
            {
                var itm = functionItems[i];
                if (itm.RequiresExecution(context))
                {
                    qualifyingExecutionItems.Add(itm);
                }
            }

            if (qualifyingExecutionItems.Count == 0)
            {
                return(null);
            }

            var bundlerRequest = new FunctionBundlerRequest
            {
                ClientFCntDown = context.FCntDown,
                ClientFCntUp   = context.FCntUp,
                GatewayId      = gatewayId,
                Rssi           = context.Request.Rxpk.Rssi,
            };

            for (var i = 0; i < qualifyingExecutionItems.Count; i++)
            {
                qualifyingExecutionItems[i].Prepare(context, bundlerRequest);
            }

            Logger.Log(loRaDevice.DevEUI, "FunctionBundler request: ", bundlerRequest, LogLevel.Debug);

            return(new FunctionBundler(loRaDevice.DevEUI, this.deviceApi, bundlerRequest, qualifyingExecutionItems, context));
        }
        private bool ValidateCloudToDeviceMessage(LoRaDevice loRaDevice, LoRaRequest request, IReceivedLoRaCloudToDeviceMessage cloudToDeviceMsg)
        {
            if (!cloudToDeviceMsg.IsValid(out var errorMessage))
            {
                Logger.Log(loRaDevice.DevEUI, errorMessage, LogLevel.Error);
                return(false);
            }

            var  rxpk       = request.Rxpk;
            var  loRaRegion = request.Region;
            uint maxPayload;

            // If preferred Window is RX2, this is the max. payload
            if (loRaDevice.PreferredWindow == Constants.RECEIVE_WINDOW_2)
            {
                // Get max. payload size for RX2, considering possilbe user provided Rx2DataRate
                if (string.IsNullOrEmpty(this.configuration.Rx2DataRate))
                {
                    maxPayload = loRaRegion.DRtoConfiguration[loRaRegion.RX2DefaultReceiveWindows.dr].maxPyldSize;
                }
                else
                {
                    maxPayload = loRaRegion.GetMaxPayloadSize(this.configuration.Rx2DataRate);
                }
            }

            // Otherwise, it is RX1.
            else
            {
                maxPayload = loRaRegion.GetMaxPayloadSize(loRaRegion.GetDownstreamDR(rxpk));
            }

            // Deduct 8 bytes from max payload size.
            maxPayload -= Constants.LORA_PROTOCOL_OVERHEAD_SIZE;

            // Calculate total C2D message size based on optional C2D Mac commands.
            var totalPayload = cloudToDeviceMsg.GetPayload()?.Length ?? 0;

            if (cloudToDeviceMsg.MacCommands?.Count > 0)
            {
                foreach (MacCommand macCommand in cloudToDeviceMsg.MacCommands)
                {
                    totalPayload += macCommand.Length;
                }
            }

            // C2D message and optional C2D Mac commands are bigger than max. payload size: REJECT.
            // This message can never be delivered.
            if (totalPayload > maxPayload)
            {
                Logger.Log(loRaDevice.DevEUI, $"message payload size ({totalPayload}) exceeds maximum allowed payload size ({maxPayload}) in cloud to device message", LogLevel.Error);
                return(false);
            }

            return(true);
        }
        public void Initialize(LoRaDevice loRaDevice)
        {
            var isMultiGateway = !string.Equals(this.gatewayID, loRaDevice.GatewayID, StringComparison.InvariantCultureIgnoreCase);
            var strategy       = isMultiGateway ? this.frameCounterUpdateStrategyFactory.GetMultiGatewayStrategy() : this.frameCounterUpdateStrategyFactory.GetSingleGatewayStrategy();

            if (strategy is ILoRaDeviceInitializer initializer)
            {
                initializer.Initialize(loRaDevice);
            }
        }
 private void InitializeDevice(LoRaDevice loRaDevice)
 {
     if (loRaDevice.IsOurDevice && this.initializers != null)
     {
         foreach (var initializer in this.initializers)
         {
             initializer.Initialize(loRaDevice);
         }
     }
 }
Пример #22
0
        /// <summary>
        /// Updates a device after a successful login
        /// </summary>
        public void UpdateDeviceAfterJoin(LoRaDevice loRaDevice, string oldDevAddr)
        {
            // once added, call initializers
            foreach (var initializer in this.initializers)
            {
                initializer.Initialize(loRaDevice);
            }

            this.UpdateDeviceRegistration(loRaDevice, oldDevAddr);
        }
            public void Dispose()
            {
                DeviceClient.Dispose();

                // Disposing the Connection Manager should only happen on application shutdown
                // (which in turn triggers the disposal of all managed connections).
                // In that specific case disposing the LoRaDevice will cause the LoRa device to unregister itself again,
                // which causes DeviceClient.Dispose() to be called twice. We do not optimize this case, since the Dispose logic is idempotent.
                LoRaDevice.Dispose();
            }
        public async Task <bool> ResetAsync(LoRaDevice loRaDevice, uint fcntUp, string gatewayId)
        {
            if (loRaDevice is null)
            {
                throw new ArgumentNullException(nameof(loRaDevice));
            }

            loRaDevice.ResetFcnt();
            return(await InternalSaveChangesAsync(loRaDevice, force : true));
        }
Пример #25
0
        private async Task <LoRaDevice> InitializeDeviceAsync(LoRaDevice loRaDevice)
        {
            try
            {
                // Our device if it does not have a gateway assigned or is assigned to our
                var isOurDevice = string.IsNullOrEmpty(loRaDevice.GatewayID) || string.Equals(loRaDevice.GatewayID, this.configuration.GatewayID, StringComparison.InvariantCultureIgnoreCase);
                // Only create client if the device is our
                if (!isOurDevice)
                {
                    loRaDevice.IsOurDevice = false;
                    return(loRaDevice);
                }

                // Calling initialize async here to avoid making async calls in the concurrent dictionary
                // Since only one device will be added, we guarantee that initialization only happens once
                if (await loRaDevice.InitializeAsync())
                {
                    // revalidate based on device twin property
                    loRaDevice.IsOurDevice = string.IsNullOrEmpty(loRaDevice.GatewayID) || string.Equals(loRaDevice.GatewayID, this.configuration.GatewayID, StringComparison.InvariantCultureIgnoreCase);
                    if (loRaDevice.IsOurDevice)
                    {
                        // once added, call initializers
                        if (this.initializers != null)
                        {
                            foreach (var initializer in this.initializers)
                            {
                                initializer.Initialize(loRaDevice);
                            }
                        }
                    }

                    // checking again in case one of the initializers change the value
                    if (!loRaDevice.IsOurDevice)
                    {
                        // Initialization does not use activity counters
                        // This should not fail
                        if (!loRaDevice.TryDisconnect())
                        {
                            Logger.Log(loRaDevice.DevEUI, "failed to disconnect device from another gateway", LogLevel.Error);
                        }
                    }

                    return(loRaDevice);
                }
            }
            catch (Exception ex)
            {
                // device does not have the required properties
                Logger.Log(loRaDevice.DevEUI ?? this.devAddr, $"error initializing device {loRaDevice.DevEUI}. {ex.Message}", LogLevel.Error);
            }

            // instance not used, dispose the connection
            loRaDevice.Dispose();
            return(null);
        }
Пример #26
0
 public void Initialize(LoRaDevice loRaDevice)
 {
     if (loRaDevice.IsOurDevice)
     {
         var strategy = this.frameCounterUpdateStrategyProvider.GetStrategy(loRaDevice.GatewayID);
         if (strategy != null && strategy is ILoRaDeviceInitializer initializer)
         {
             initializer.Initialize(loRaDevice);
         }
     }
 }
Пример #27
0
 public void Register(LoRaDevice loRaDevice, ILoRaDeviceClient loraDeviceClient)
 {
     this.managedConnections.AddOrUpdate(
         this.GetConnectionCacheKey(loRaDevice.DevEUI),
         new ManagedConnection(loRaDevice, loraDeviceClient),
         (k, existing) =>
     {
         // Update existing
         return(new ManagedConnection(loRaDevice, loraDeviceClient));
     });
 }
        public LoRaDevice Create(IoTHubDeviceInfo deviceInfo)
        {
            var loraDeviceClient = this.CreateDeviceClient(deviceInfo.DevEUI, deviceInfo.PrimaryKey);

            var loRaDevice = new LoRaDevice(
                devAddr: deviceInfo.DevAddr,
                devEUI: deviceInfo.DevEUI,
                loRaDeviceClient: loraDeviceClient);

            return(loRaDevice);
        }
 private static void LogFrameCounterDownState(LoRaDevice loRaDevice, uint newFcntDown)
 {
     if (newFcntDown <= 0)
     {
         Logger.Log(loRaDevice.DevEUI, "another gateway has already sent ack or downlink msg", LogLevel.Debug);
     }
     else
     {
         Logger.Log(loRaDevice.DevEUI, $"down frame counter: {loRaDevice.FCntDown}", LogLevel.Debug);
     }
 }
Пример #30
0
        public async Task <bool> ResetAsync(LoRaDevice loRaDevice)
        {
            loRaDevice.ResetFcnt();

            if (await this.InternalSaveChangesAsync(loRaDevice, force: true))
            {
                return(await this.loRaDeviceAPIService.ABPFcntCacheResetAsync(loRaDevice.DevEUI));
            }

            return(false);
        }