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); }
public LoRaCloudToDeviceMessageWrapper(LoRaDevice loRaDevice, Message message) { this.loRaDevice = loRaDevice ?? throw new ArgumentNullException(nameof(loRaDevice)); this.message = message ?? throw new ArgumentNullException(nameof(message)); this.ParseMessage(); }
/// <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); } }
public void Release(LoRaDevice loRaDevice) { if (this.managedConnections.TryRemove(this.GetConnectionCacheKey(loRaDevice.DevEUI), out var removedItem)) { removedItem.DeviceClient.Dispose(); } }
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); } }
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"); }
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); } } }
/// <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)); }
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); }
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); } } }
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); } }
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); }