public Task <LoRaADRTable> GetADRTable(DevEui devEUI) { lock (this.cache) { return(Task.FromResult(this.cache.Get <LoRaADRTable>(devEUI))); } }
public DownlinkMessage(byte[] payload, ulong xtime, ReceiveWindow?rx1, ReceiveWindow rx2, DevEui devEui, RxDelay lnsRxDelay, LoRaDeviceClassType deviceClassType, StationEui stationEui = default, uint?antennaPreference = null) { if (payload is null) { throw new ArgumentNullException(nameof(payload)); } Data = payload; DevEui = devEui; LnsRxDelay = lnsRxDelay; DeviceClassType = deviceClassType; AntennaPreference = antennaPreference; Rx1 = rx1; Rx2 = rx2; StationEui = stationEui; Xtime = xtime; }
internal async Task <LoRaADRResult> HandleADRRequest(DevEui devEUI, LoRaADRRequest request) { if (request == null) { return(null); } if (request.ClearCache) { _ = await this.adrManager.ResetAsync(devEUI); return(new LoRaADRResult()); } var newEntry = new LoRaADRTableEntry { DevEUI = devEUI, FCnt = request.FCntUp, GatewayId = request.GatewayId, Snr = request.RequiredSnr }; if (request.PerformADRCalculation) { return(await this.adrManager.CalculateADRResultAndAddEntryAsync(devEUI, request.GatewayId, request.FCntUp, request.FCntDown, request.RequiredSnr, request.DataRate, request.MinTxPowerIndex, request.MaxDataRate, newEntry)); } else { await this.adrManager.StoreADREntryAsync(newEntry); return(await this.adrManager.GetLastResultAsync(devEUI)); } }
public async ValueTask <DecodePayloadResult> DecodeMessageAsync(DevEui devEui, byte[] payload, FramePort fport, string sensorDecoder) { sensorDecoder ??= string.Empty; var base64Payload = ((payload?.Length ?? 0) == 0) ? string.Empty : Convert.ToBase64String(payload); // Call local decoder (no "http://" in SensorDecoder) if (Uri.TryCreate(sensorDecoder, UriKind.Absolute, out var url) && url.Scheme is "http") { // Support decoders that have a parameter in the URL // http://decoder/api/sampleDecoder?x=1 -> should become http://decoder/api/sampleDecoder?x=1&devEUI=11&fport=1&payload=12345 var query = HttpUtility.ParseQueryString(url.Query); query["devEUI"] = devEui.ToString(); query["fport"] = ((int)fport).ToString(CultureInfo.InvariantCulture); query["payload"] = base64Payload; var urlBuilder = new UriBuilder(url) { Query = query.ToString() }; if (urlBuilder.Path.EndsWith('/')) { urlBuilder.Path = urlBuilder.Path[..^ 1];
public async Task When_Direct_Method_Returns_Error_Code_Should_Forward_Status_Error() { var devEui = new DevEui(0123456789); var preferredGateway = new LoRaDevicePreferredGateway("gateway1", 100); LoRaDevicePreferredGateway.SaveToCache(this.cacheStore, devEui, preferredGateway); this.serviceClient.Setup(x => x.InvokeDeviceMethodAsync("gateway1", LoraKeysManagerFacadeConstants.NetworkServerModuleId, It.IsNotNull <CloudToDeviceMethod>())) .ReturnsAsync(new CloudToDeviceMethodResult() { Status = (int)HttpStatusCode.BadRequest }); var actual = await this.sendCloudToDeviceMessage.SendCloudToDeviceMessageImplementationAsync( devEui, new LoRaCloudToDeviceMessage() { Fport = TestPort, }); Assert.IsType <ObjectResult>(actual); Assert.Equal((int)HttpStatusCode.BadRequest, ((ObjectResult)actual).StatusCode); this.serviceClient.VerifyAll(); this.registryManager.VerifyAll(); }
public async Task When_Payload_Is_Empty_ExternalDecoder_Should_Be_Called_With_Empty_Payload() { var devEui = new DevEui(0x12); var fport = FramePorts.App8; var decodedValue = "{\"from\":\"http\"}"; using var httpMessageHandler = new HttpMessageHandlerMock(); httpMessageHandler.SetupHandler((r) => { var queryDictionary = System.Web.HttpUtility.ParseQueryString(r.RequestUri.Query); Assert.Equal("0000000000000012", queryDictionary.Get("devEUI")); Assert.Equal(((byte)fport).ToString(CultureInfo.InvariantCulture), queryDictionary.Get("fport")); Assert.Empty(queryDictionary.Get("payload")); return(new HttpResponseMessage(System.Net.HttpStatusCode.OK) { Content = new StringContent(decodedValue, Encoding.UTF8, "application/json"), }); }); using var httpClient = new HttpClient(httpMessageHandler); var target = new LoRaPayloadDecoder(httpClient); var result = await target.DecodeMessageAsync(devEui, Array.Empty <byte>(), fport, "http://test/decoder"); var json = JsonConvert.SerializeObject(result.GetDecodedPayload()); Assert.Equal(decodedValue, json); }
public async Task UpdateADRTable(DevEui devEUI, LoRaADRTable table) { using var redisLock = new RedisLockWrapper(devEUI, this.redisCache); if (await redisLock.TakeLockAsync()) { _ = await this.redisCache.StringSetAsync(GetEntryKey(devEUI), JsonConvert.SerializeObject(table)); } }
public LoRaPayloadJoinRequest(JoinEui joinEui, DevEui devEui, DevNonce devNonce, Mic mic) { MHdr = new MacHeader(MacMessageType.JoinRequest); AppEui = joinEui; DevEUI = devEui; DevNonce = devNonce; Mic = mic; }
public void StationEui_Can_Be_Interpreted_As_Dev_Eui() { const ulong value = 0x1a2b3c; var devEui = new DevEui(value); var stationEui = new StationEui(value); Assert.Equal(devEui.ToString(), stationEui.ToString()); }
public Task <bool> Reset(DevEui devEUI) { lock (this.cache) { this.cache.Remove(devEUI); } return(Task.FromResult <bool>(true)); }
public JoinRequestFrame(MacHeader mHdr, JoinEui joinEui, DevEui devEui, DevNonce devNonce, Mic mic, RadioMetadata radioMetadata) { MacHeader = mHdr; JoinEui = joinEui; DevEui = devEui; DevNonce = devNonce; Mic = mic; RadioMetadata = radioMetadata; }
public static async Task <bool> ResetADRCache(DevEui devEUI) { var path = $"FunctionBundler/{devEUI}"; // the gateway id is only used to identify who is taking the lock when // releasing the cache. Hence we do not need a real GW id var payload = "{\"AdrRequest\":{\"ClearCache\": true},\"GatewayId\":\"integrationTesting\", \"FunctionItems\": " + (int)FunctionBundlerItemType.ADR + "}"; return(await PostFunctionEndpointAsync(path, payload)); }
public ILoRaDeviceClient GetClient(DevEui devEui) { if (this.managedConnections.TryGetValue(GetConnectionCacheKey(devEui), out var managedConnection)) { return(managedConnection.DeviceClient); } throw new ManagedConnectionException($"Connection for device {devEui} was not found"); }
internal async Task <DuplicateMsgResult> GetDuplicateMessageResultAsync(DevEui devEUI, string gatewayId, uint clientFCntUp, uint clientFCntDown, ILogger logger = null) { var isDuplicate = true; var processedDevice = gatewayId; using (var deviceCache = new LoRaDeviceCache(this.cacheStore, devEUI, gatewayId)) { if (await deviceCache.TryToLockAsync()) { // we are owning the lock now if (deviceCache.TryGetInfo(out var cachedDeviceState)) { var updateCacheState = false; if (cachedDeviceState.FCntUp < clientFCntUp) { isDuplicate = false; updateCacheState = true; } else if (cachedDeviceState.FCntUp == clientFCntUp && cachedDeviceState.GatewayId == gatewayId) { isDuplicate = false; processedDevice = cachedDeviceState.GatewayId; } else { processedDevice = cachedDeviceState.GatewayId; } if (updateCacheState) { cachedDeviceState.FCntUp = clientFCntUp; cachedDeviceState.GatewayId = gatewayId; _ = deviceCache.StoreInfo(cachedDeviceState); } } else { // initialize isDuplicate = false; var state = deviceCache.Initialize(clientFCntUp, clientFCntDown); logger?.LogDebug("initialized state for {id}:{gwid} = {state}", devEUI, gatewayId, state); } } else { processedDevice = "[unknown]"; logger?.LogWarning("Failed to acquire lock"); } } return(new DuplicateMsgResult { IsDuplicate = isDuplicate, GatewayId = processedDevice }); }
public async Task When_Querying_Devices_And_Finds_Class_A_Should_Send_Message() { var devEui = new DevEui(123456789); var deviceTwin = new Twin { Properties = new TwinProperties() { Desired = new TwinCollection($"{{\"DevAddr\": \"03010101\"}}"), } }; var query = new Mock <IQuery>(MockBehavior.Strict); query.Setup(x => x.HasMoreResults).Returns(true); query.Setup(x => x.GetNextAsTwinAsync()) .ReturnsAsync(new[] { deviceTwin }); this.registryManager.Setup(x => x.CreateQuery(It.IsNotNull <string>(), It.IsAny <int?>())) .Returns(query.Object); var actualMessage = new LoRaCloudToDeviceMessage() { MessageId = "myMessageId-1234", Fport = TestPort, Payload = "hello", }; LoRaCloudToDeviceMessage receivedC2DMessage = null; IDictionary <string, string> receivedProperties = null; this.serviceClient.Setup(x => x.SendAsync(devEui.ToString(), It.IsNotNull <Message>())) .Callback((string d, Message m) => (receivedProperties, receivedC2DMessage) = (m.Properties, JsonConvert.DeserializeObject <LoRaCloudToDeviceMessage>(Encoding.UTF8.GetString(m.GetBytes())))) .Returns(Task.CompletedTask); var actual = await this.sendCloudToDeviceMessage.SendCloudToDeviceMessageImplementationAsync( devEui, actualMessage); Assert.IsType <OkObjectResult>(actual); var responseValue = ((OkObjectResult)actual).Value as SendCloudToDeviceMessageResult; Assert.NotNull(responseValue); Assert.Equal("A", responseValue.ClassType); Assert.Equal(devEui, responseValue.DevEui); Assert.Equal(actualMessage.MessageId, responseValue.MessageID); Assert.Empty(receivedProperties); Assert.Equal(receivedC2DMessage.Fport, actualMessage.Fport); Assert.Equal(receivedC2DMessage.Payload, actualMessage.Payload); Assert.Equal(receivedC2DMessage.MessageId, actualMessage.MessageId); this.serviceClient.VerifyAll(); this.registryManager.VerifyAll(); query.VerifyAll(); }
public FunctionBundlerPipelineExecuter(IFunctionBundlerExecutionItem[] registeredHandlers, DevEui devEUI, FunctionBundlerRequest request, ILogger logger = null) { this.registeredHandlers = registeredHandlers; DevEUI = devEUI; Request = request; Logger = logger ?? NullLogger.Instance; }
public virtual async Task <LoRaADRResult> CalculateADRResultAndAddEntryAsync(DevEui devEUI, string gatewayId, uint fCntUp, uint fCntDown, float requiredSnr, DataRateIndex dataRate, int minTxPower, DataRateIndex maxDr, LoRaADRTableEntry newEntry = null) { var table = newEntry != null ? await this.store.AddTableEntry(newEntry) : await this.store.GetADRTable(devEUI); var currentStrategy = this.strategyProvider.GetStrategy(); var result = currentStrategy.ComputeResult(table, requiredSnr, dataRate, minTxPower, maxDr); if (result == null) { // In this case we want to reset the device to default values as we have null values if (table == null || !table.CurrentNbRep.HasValue || !table.CurrentTxPower.HasValue || fCntUp > currentStrategy.MinimumNumberOfResult) { result = ReturnDefaultValues(dataRate, currentStrategy.DefaultNbRep, currentStrategy.DefaultTxPower); } else { result = await GetLastResultAsync(devEUI) ?? new LoRaADRResult(); result.NumberOfFrames = table.Entries.Count; return(result); } } var nextFcntDown = await NextFCntDown(devEUI, gatewayId, fCntUp, fCntDown); result.CanConfirmToDevice = nextFcntDown > 0; if (result.CanConfirmToDevice) { if (table == null) { // in a reset case, we may not have a table, but still want to store the default // values that we sent to the client table = new LoRaADRTable(); } table.CurrentNbRep = result.NbRepetition; table.CurrentTxPower = result.TxPower; await this.store.UpdateADRTable(devEUI, table); UpdateState(result); result.FCntDown = nextFcntDown; } result.NumberOfFrames = table.Entries.Count; this.logger.LogDebug($"calculated ADR: CanConfirmToDevice: {result.CanConfirmToDevice}, TxPower: {result.TxPower}, DataRate: {result.DataRate}"); return(result); }
public BasicsStationConfigurationServiceTests() { this.stationEui = new StationEui(ulong.MaxValue); this.devEui = DevEui.Parse(this.stationEui.ToString()); this.loRaDeviceApiServiceMock = new Mock <LoRaDeviceAPIServiceBase>(); this.loRaDeviceFactoryMock = new Mock <ILoRaDeviceFactory>(); this.memoryCache = new MemoryCache(new MemoryCacheOptions()); this.sut = new BasicsStationConfigurationService(this.loRaDeviceApiServiceMock.Object, this.loRaDeviceFactoryMock.Object, this.memoryCache, NullLogger <BasicsStationConfigurationService> .Instance); }
public LoRaDeviceCache(ILoRaDeviceCacheStore cacheStore, DevEui devEUI, string gatewayId) { if (string.IsNullOrEmpty(gatewayId)) { throw new ArgumentNullException(nameof(gatewayId)); } this.cacheStore = cacheStore; this.devEUI = devEUI; this.gatewayId = gatewayId; this.cacheKey = devEUI.ToString(); }
public async Task <bool> Reset(DevEui devEUI) { using (var redisLock = new RedisLockWrapper(devEUI, this.redisCache)) { if (await redisLock.TakeLockAsync()) { return(await this.redisCache.KeyDeleteAsync(GetEntryKey(devEUI))); } } return(false); }
public async Task Test_ClassC_Send_Message_Using_Function_Endpoint_Should_Be_Received() { var device = TestFixtureCi.Device24_ABP; LogTestStart(device); await ArduinoDevice.setDeviceModeAsync(LoRaArduinoSerial._device_mode_t.LWABP); await ArduinoDevice.setIdAsync(device.DevAddr, device.DeviceID, null); await ArduinoDevice.setKeyAsync(device.NwkSKey, device.AppSKey, null); await ArduinoDevice.SetupLora(TestFixtureCi.Configuration); await ArduinoDevice.setClassTypeAsync(LoRaArduinoSerial._class_type_t.CLASS_C); // send one confirmed message for ensuring that a basicstation is "bound" to the device await Task.Delay(Constants.DELAY_BETWEEN_MESSAGES); var msg = PayloadGenerator.Next().ToString(CultureInfo.InvariantCulture); Log($"{device.DeviceID}: Sending confirmed '{msg}'"); await ArduinoDevice.transferPacketWithConfirmedAsync(msg, 10); await Task.Delay(Constants.DELAY_BETWEEN_MESSAGES); TestFixtureCi.ClearLogs(); // Now sending a c2d var c2d = new LoRaCloudToDeviceMessage() { DevEUI = DevEui.Parse(device.DeviceID), MessageId = Guid.NewGuid().ToString(), Fport = FramePorts.App23, RawPayload = Convert.ToBase64String(new byte[] { 0xFF, 0x00 }), }; TestLogger.Log($"[INFO] Using service API to send C2D message to device {device.DeviceID}"); TestLogger.Log($"[INFO] {JsonConvert.SerializeObject(c2d, Formatting.None)}"); // send message using the SendCloudToDeviceMessage API endpoint Assert.True(await LoRaAPIHelper.SendCloudToDeviceMessage(device.DevEui, c2d)); await Task.Delay(Constants.DELAY_BETWEEN_MESSAGES); // 0000000000000024: received cloud to device message from direct method await TestFixtureCi.AssertNetworkServerModuleLogStartsWithAsync($"{device.DeviceID}: received cloud to device message from direct method"); Assert.Contains(ArduinoDevice.SerialLogs, (l) => l.Contains("PORT: 23; RX: \"FF00\"", StringComparison.Ordinal)); Assert.Contains(ArduinoDevice.SerialLogs, (l) => l.Contains("RXWIN0, RSSI", StringComparison.Ordinal)); await AssertUtils.ContainsWithRetriesAsync("+MSG: Done", ArduinoDevice.SerialLogs); }
public async Task <LoRaADRTable> GetADRTable(DevEui devEUI) { using (var redisLock = new RedisLockWrapper(devEUI, this.redisCache)) { if (await redisLock.TakeLockAsync()) { return(await GetADRTableCore(GetEntryKey(devEUI))); } } this.logger.LogError("Failed to acquire ADR redis lock. Can't deliver ADR Table"); return(null); }
public PreferredGatewayResult(DevEui devEUI, uint fcntUp, LoRaDevicePreferredGateway preferredGateway) { if (preferredGateway is null) { throw new ArgumentNullException(nameof(preferredGateway)); } DevEUI = devEUI; RequestFcntUp = fcntUp; CurrentFcntUp = preferredGateway.FcntUp; PreferredGatewayID = preferredGateway.GatewayID; Conflict = fcntUp != preferredGateway.FcntUp; }
public virtual async Task <LoRaADRResult> GetLastResultAsync(DevEui devEUI) { var table = await this.store.GetADRTable(devEUI); return(table != null ? new LoRaADRResult { NbRepetition = table.CurrentNbRep, TxPower = table.CurrentTxPower, NumberOfFrames = table.Entries.Count } : null); }
internal FunctionBundler(DevEui devEui, LoRaDeviceAPIServiceBase deviceApi, FunctionBundlerRequest request, IList <IFunctionBundlerExecutionItem> executionItems, FunctionBundlerExecutionContext executionContext, ILogger <FunctionBundler> logger) { this.devEui = devEui; this.deviceApi = deviceApi; this.request = request; this.executionItems = executionItems; this.executionContext = executionContext; this.logger = logger; }
public override async Task <bool> ABPFcntCacheResetAsync(DevEui devEUI, uint fcntUp, string gatewayId) { var client = this.serviceFacadeHttpClientProvider.GetHttpClient(); var url = GetFullUri($"NextFCntDown?code={AuthCode}&DevEUI={devEUI}&ABPFcntCacheReset=true&GatewayId={gatewayId}&FCntUp={fcntUp}"); var response = await client.GetAsync(url); if (!response.IsSuccessStatusCode) { this.logger.LogError($"error calling the NextFCntDown function, check the function log, {response.ReasonPhrase}"); return(false); } return(true); }
/// <summary> /// Searchs for devices that match the join request. /// </summary> public async Task <LoRaDevice> GetDeviceForJoinRequestAsync(DevEui devEUI, DevNonce devNonce) { this.logger.LogDebug("querying the registry for OTAA device"); var searchDeviceResult = await this.loRaDeviceAPIService.SearchAndLockForJoinAsync( gatewayID : this.configuration.GatewayID, devEUI : devEUI, devNonce : devNonce); if (searchDeviceResult.IsDevNonceAlreadyUsed) { // another gateway processed the join request. If we have it in the cache // with existing session keys, we need to invalidate that entry, to ensure // it gets re-fetched on the next message if (this.deviceCache.TryGetByDevEui(devEUI, out var someDevice) && someDevice.AppSKey != null) { _ = this.deviceCache.Remove(someDevice); this.logger.LogDebug("Device was removed from cache."); } this.logger.LogInformation("join refused: Join already processed by another gateway."); return(null); } if (searchDeviceResult?.Devices == null || searchDeviceResult.Devices.Count == 0) { this.logger.LogInformation(searchDeviceResult.RefusedMessage ?? "join refused: no devices found matching join request"); return(null); } var matchingDeviceInfo = searchDeviceResult.Devices[0]; if (deviceCache.TryGetByDevEui(matchingDeviceInfo.DevEUI, out var cachedDevice)) { // if we already have the device in the cache, then it is either from a previous // join rquest or it's a re-join. Both scenarios are ok, and we can use the cached // information. return(cachedDevice); } var loader = GetOrCreateJoinDeviceLoader(matchingDeviceInfo); var loRaDevice = await loader.LoadAsync(); if (!loader.CanCache) { RemoveJoinDeviceLoader(devEUI); } return(loRaDevice); }
private async Task <IActionResult> SendMessageViaDirectMethodAsync( string preferredGatewayID, DevEui devEUI, LoRaCloudToDeviceMessage c2dMessage) { try { var method = new CloudToDeviceMethod(LoraKeysManagerFacadeConstants.CloudToDeviceMessageMethodName, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)); _ = method.SetPayloadJson(JsonConvert.SerializeObject(c2dMessage)); var res = await this.serviceClient.InvokeDeviceMethodAsync(preferredGatewayID, LoraKeysManagerFacadeConstants.NetworkServerModuleId, method); if (IsSuccessStatusCode(res.Status)) { this.log.LogInformation("Direct method call to {gatewayID} and {devEUI} succeeded with {statusCode}", preferredGatewayID, devEUI, res.Status); return(new OkObjectResult(new SendCloudToDeviceMessageResult() { DevEui = devEUI, MessageID = c2dMessage.MessageId, ClassType = "C", })); } this.log.LogError("Direct method call to {gatewayID} failed with {statusCode}. Response: {response}", preferredGatewayID, res.Status, res.GetPayloadAsJson()); return(new ObjectResult(res.GetPayloadAsJson()) { StatusCode = res.Status, }); } catch (JsonSerializationException ex) { this.log.LogError(ex, "Failed to serialize C2D message {c2dmessage} to {devEUI}", JsonConvert.SerializeObject(c2dMessage), devEUI); return(new ObjectResult("Failed serialize C2D Message") { StatusCode = (int)HttpStatusCode.InternalServerError }); } catch (IotHubException ex) { this.log.LogError(ex, "Failed to send message for {devEUI} to the IoT Hub", devEUI); return(new ObjectResult("Failed to send message for device to the iot Hub") { StatusCode = (int)HttpStatusCode.InternalServerError }); } }
public async Task GetDeviceForJoinRequestAsync_When_Device_Api_Throws_Error_Should_Not_Catch() { var devEui = new DevEui(1); var devNonce = new DevNonce(1); var apiService = new Mock <LoRaDeviceAPIServiceBase>(); apiService.Setup(x => x.SearchAndLockForJoinAsync(ServerConfiguration.GatewayID, devEui, devNonce)) .Throws(new InvalidOperationException()); using var target = new LoRaDeviceRegistry(ServerConfiguration, this.cache, apiService.Object, this.loraDeviceFactoryMock.Object, DeviceCache); Task Act() => target.GetDeviceForJoinRequestAsync(devEui, devNonce); _ = await Assert.ThrowsAsync <InvalidOperationException>(Act); // Device was searched by DevAddr apiService.VerifyAll(); }
public async Task When_Device_Is_Found_Should_Returns_Device_Information() { // arrange var devEui = new DevEui(13213123212131); var primaryKey = Convert.ToBase64String(Encoding.UTF8.GetBytes(PrimaryKey)); var(registryManager, request) = SetupIotHubQuery(devEui.ToString(), PrimaryKey); var searchDeviceByDevEUI = new SearchDeviceByDevEUI(registryManager.Object); // act var result = await searchDeviceByDevEUI.GetDeviceByDevEUI(request, NullLogger.Instance); // assert var okObjectResult = Assert.IsType <OkObjectResult>(result); Assert.Equal(JsonConvert.SerializeObject(new { DevEUI = devEui.ToString(), PrimaryKey = primaryKey }), JsonConvert.SerializeObject(okObjectResult.Value)); registryManager.VerifyAll(); }