public SimulatedPacketForwarder(IPEndPoint networkServerIPEndpoint, Rxpk rxpk = null) { IPAddress ip = IPAddress.Any; int port = 1681; IPEndPoint endPoint = new IPEndPoint(ip, port); this.udpClient = new UdpClient(endPoint); this.networkServerIPEndpoint = networkServerIPEndpoint; this.TimeAtBoot = DateTimeOffset.Now.UtcTicks; this.MacAddress = Utility.GetMacAddress(); this.Rxpk = rxpk ?? new Rxpk() { Chan = 7, Rfch = 1, Freq = 903.700000, Stat = 1, Modu = "LORA", Datr = "SF10BW125", Codr = "4/5", Rssi = -17, Lsnr = 12.0f }; TestLogger.Log($"*** Simulated Packed Forwarder created: {ip}:{port} ***"); }
/// <summary> /// Process a raw message /// </summary> /// <param name="rxpk"><see cref="Rxpk"/> representing incoming message</param> /// <param name="startTimeProcessing">Starting time counting from the moment the message was received</param> /// <returns>A <see cref="DownlinkPktFwdMessage"/> if a message has to be sent back to device</returns> public async Task <DownlinkPktFwdMessage> ProcessMessageAsync(Rxpk rxpk, DateTime startTimeProcessing) { if (!LoRaPayload.TryCreateLoRaPayload(rxpk, out LoRaPayload loRaPayload)) { Logger.Log("There was a problem in decoding the Rxpk", LogLevel.Error); return(null); } if (this.loraRegion == null) { if (!RegionFactory.TryResolveRegion(rxpk)) { // log is generated in Region factory // move here once V2 goes GA return(null); } this.loraRegion = RegionFactory.CurrentRegion; } if (loRaPayload.LoRaMessageType == LoRaMessageType.JoinRequest) { return(await this.ProcessJoinRequestAsync(rxpk, (LoRaPayloadJoinRequest)loRaPayload, startTimeProcessing)); } else if (loRaPayload.LoRaMessageType == LoRaMessageType.UnconfirmedDataUp || loRaPayload.LoRaMessageType == LoRaMessageType.ConfirmedDataUp) { return(await this.ProcessDataMessageAsync(rxpk, (LoRaPayloadData)loRaPayload, startTimeProcessing)); } Logger.Log("Unknwon message type in rxpk, message ignored", LogLevel.Error); return(null); }
public void When_Creating_From_Json_With_Custom_Elements_Has_Correct_Value() { string jsonUplink = @"{ ""rxpk"":[ { ""time"":""2013-03-31T16:21:17.528002Z"", ""tmst"":3512348611, ""chan"":2, ""rfch"":0, ""freq"":866.349812, ""stat"":1, ""modu"":""LORA"", ""datr"":""SF7BW125"", ""codr"":""4/6"", ""rssi"":-35, ""lsnr"":5.1, ""size"":32, ""data"":""AAQDAgEEAwIBBQQDAgUEAwItEGqZDhI="", ""custom_prop_a"":""a"", ""custom_prop_b"":10 }]}"; byte[] physicalUpstreamPyld = new byte[12]; physicalUpstreamPyld[0] = 2; var request = Encoding.Default.GetBytes(jsonUplink); var rxpks = Rxpk.CreateRxpk(physicalUpstreamPyld.Concat(request).ToArray()); Assert.Single(rxpks); Assert.Equal(2, rxpks[0].ExtraData.Count); Assert.Contains("custom_prop_a", rxpks[0].ExtraData.Keys); Assert.Contains("custom_prop_b", rxpks[0].ExtraData.Keys); Assert.Equal("a", rxpks[0].ExtraData["custom_prop_a"]); Assert.Equal(10L, rxpks[0].ExtraData["custom_prop_b"]); }
public LoRaDeviceTelemetry(Rxpk rxpk, LoRaPayloadData upstreamPayload, object payloadData, byte[] decryptedPayloadData) { if (rxpk.ExtraData != null) { this.ExtraData = new Dictionary <string, object>(rxpk.ExtraData); } this.Chan = rxpk.Chan; this.Codr = rxpk.Codr; this.Data = payloadData; this.Rawdata = decryptedPayloadData?.Length > 0 ? Convert.ToBase64String(decryptedPayloadData) : string.Empty; this.Datr = rxpk.Datr; this.Freq = rxpk.Freq; this.Lsnr = rxpk.Lsnr; this.Modu = rxpk.Modu; this.Rfch = rxpk.Rfch; this.Rssi = rxpk.Rssi; this.Size = rxpk.Size; this.Stat = rxpk.Stat; this.Time = rxpk.Time; this.Tmms = rxpk.Tmms; this.Tmst = rxpk.Tmst; this.Fcnt = upstreamPayload.GetFcnt(); this.Port = upstreamPayload.GetFPort(); }
private async Task ProcessRxpkAsync(string remoteIp, Rxpk rxpk, DateTime startTimeProcessing) { try { var downstreamMessage = await this.messageProcessor.ProcessMessageAsync(rxpk, startTimeProcessing); if (downstreamMessage?.Txpk != null) { var jsonMsg = JsonConvert.SerializeObject(downstreamMessage); var messageByte = Encoding.UTF8.GetBytes(jsonMsg); var token = await this.GetTokenAsync(); PhysicalPayload pyld = new PhysicalPayload(token, PhysicalIdentifier.PULL_RESP, messageByte); if (this.pullAckRemoteLoRaAggregatorPort != 0) { await this.UdpSendMessage(pyld.GetMessage(), remoteIp, this.pullAckRemoteLoRaAggregatorPort); Logger.Log("UDP", $"message sent with ID {ConversionHelper.ByteArrayToString(token)}", LogLevel.Information); } else { Logger.Log( "UDP", "Waiting for first pull_ack message from the packet forwarder. The received message was discarded as the network server is still starting.", LogLevel.Debug); } } } catch (Exception ex) { Logger.Log($"Error processing the message {ex.Message}, {ex.StackTrace}", LogLevel.Error); } }
public void TestJoinRequest() { byte[] physicalUpstreamPyld = new byte[12]; physicalUpstreamPyld[0] = 2; string jsonUplink = @"{ ""rxpk"":[ { ""time"":""2013-03-31T16:21:17.528002Z"", ""tmst"":3512348611, ""chan"":2, ""rfch"":0, ""freq"":866.349812, ""stat"":1, ""modu"":""LORA"", ""datr"":""SF7BW125"", ""codr"":""4/6"", ""rssi"":-35, ""lsnr"":5.1, ""size"":32, ""data"":""AAQDAgEEAwIBBQQDAgUEAwItEGqZDhI="" }]}"; var joinRequestInput = Encoding.Default.GetBytes(jsonUplink); List <Rxpk> rxpk = Rxpk.CreateRxpk(physicalUpstreamPyld.Concat(joinRequestInput).ToArray()); TestRxpk(rxpk[0]); }
public void CheckEUValidUpstreamRxpk(double frequency, string datr, string expectedDatr) { string jsonUplink = "{\"rxpk\":[{\"time\":\"2013-03-31T16:21:17.528002Z\"," + "\"tmst\":3512348611," + "\"chan\":2," + "\"rfch\":0," + $"\"freq\": {frequency}," + "\"stat\":1," + "\"modu\":\"LORA\"," + $"\"datr\":\"{datr}\"," + "\"codr\":\"4/6\"," + "\"rssi\":-35," + "\"lsnr\":5.1," + "\"size\":32," + "\"data\":\"AAQDAgEEAwIBBQQDAgUEAwItEGqZDhI=\"," + "\"custom_prop_a\":\"a\"," + "\"custom_prop_b\":10" + " }]}"; byte[] physicalUpstreamPyld = new byte[12]; physicalUpstreamPyld[0] = 2; var request = Encoding.Default.GetBytes(jsonUplink); var rxpks = Rxpk.CreateRxpk(physicalUpstreamPyld.Concat(request).ToArray()); var downstream = RegionManager.EU868.GetDownstreamDR(rxpks[0]); Assert.Equal(expectedDatr, downstream); }
public LoRaDeviceTelemetry(Rxpk rxpk, LoRaPayloadData loRaPayloadData, object payloadData) { if (rxpk.ExtraData != null) { this.ExtraData = new Dictionary <string, object>(rxpk.ExtraData); } this.Chan = rxpk.Chan; this.Codr = rxpk.Codr; this.Data = payloadData; this.Rawdata = rxpk.Data; this.Datr = rxpk.Datr; this.Freq = rxpk.Freq; this.Lsnr = rxpk.Lsnr; this.Modu = rxpk.Modu; this.Rfch = rxpk.Rfch; this.Rssi = rxpk.Rssi; this.Size = rxpk.Size; this.Stat = rxpk.Stat; this.Time = rxpk.Time; this.Tmms = rxpk.Tmms; this.Tmst = rxpk.Tmst; this.Fcnt = loRaPayloadData.GetFcnt(); this.Port = loRaPayloadData.GetFPort(); }
private static List <Rxpk> GenerateRxpk(string datr, double freq) { string jsonUplink = @"{ ""rxpk"":[ { ""time"":""2013-03-31T16:21:17.528002Z"", ""tmst"":3512348611, ""chan"":2, ""rfch"":0, ""freq"":" + freq + @", ""stat"":1, ""modu"":""LORA"", ""datr"":""" + datr + @""", ""codr"":""4/6"", ""rssi"":-35, ""lsnr"":5.1, ""size"":32, ""data"":""AAQDAgEEAwIBBQQDAgUEAwItEGqZDhI="" }]}"; var multiRxpkInput = Encoding.Default.GetBytes(jsonUplink); byte[] physicalUpstreamPyld = new byte[12]; physicalUpstreamPyld[0] = 2; List <Rxpk> rxpk = Rxpk.CreateRxpk(physicalUpstreamPyld.Concat(multiRxpkInput).ToArray()); return(rxpk); }
public LoRaRequest( Rxpk rxpk, IPacketForwarder packetForwarder, DateTime startTime) { this.Rxpk = rxpk; this.PacketForwarder = packetForwarder; this.StartTime = startTime; }
public void Multiple_Rxpk_Are_Detected_Correctly() { string jsonUplink = @"{""rxpk"":[{""tmst"":373051724,""time"":""2020-02-19T04:08:57.265951Z"",""chan"":0,""rfch"":0,""freq"":923.200000,""stat"":1,""modu"":""LORA"",""datr"":""SF9BW125"",""codr"":""4/5"",""lsnr"":12.5,""rssi"":-47,""size"":21,""data"":""gAMAABKgmAAIAvEgIbhjS0LBeM/d""},{""tmst"":373053772,""time"":""2020-02-19T04:08:57.265951Z"",""chan"":6,""rfch"":0,""freq"":923.000000,""stat"":-1,""modu"":""LORA"",""datr"":""SF9BW125"",""codr"":""4/5"",""lsnr"":-13.0,""rssi"":-97,""size"":21,""data"":""gJni7n4+wQBUl/E0sO4vB4gFePx7""}]}"; byte[] physicalUpstreamPyld = new byte[12]; physicalUpstreamPyld[0] = 2; var request = Encoding.Default.GetBytes(jsonUplink); var rxpks = Rxpk.CreateRxpk(physicalUpstreamPyld.Concat(request).ToArray()); Assert.Equal(2, rxpks.Count); }
static public void Create(Rxpk _rxpk) { //EU863-870 if (_rxpk.freq < 870 && _rxpk.freq > 863) { CurrentRegion = CreateEURegion(); }//US902-928-->CN470-510 else if (_rxpk.freq <= 510 && _rxpk.freq >= 470) { CurrentRegion = CreateUSRegion(); } }
static public void Create(Rxpk _rxpk) { //EU863-870 if (_rxpk.freq < 870 && _rxpk.freq > 863) { CurrentRegion = CreateEURegion(); }//US902-928 else if (_rxpk.freq <= 928 && _rxpk.freq >= 902) { CurrentRegion = CreateUSRegion(); } }
// Sends device telemetry data to IoT Hub private async Task <bool> SendDeviceEventAsync(LoRaDevice loRaDevice, Rxpk rxpk, object decodedValue, LoRaPayloadData loRaPayloadData, LoRaOperationTimeWatcher timeWatcher) { var deviceTelemetry = new LoRaDeviceTelemetry(rxpk, loRaPayloadData, decodedValue) { DeviceEUI = loRaDevice.DevEUI, GatewayID = this.configuration.GatewayID, Edgets = (long)(timeWatcher.Start - DateTime.UnixEpoch).TotalMilliseconds }; Dictionary <string, string> eventProperties = null; if (loRaPayloadData.IsUpwardAck()) { eventProperties = new Dictionary <string, string>(); Logger.Log(loRaDevice.DevEUI, $"Message ack received for C2D message id {loRaDevice.LastConfirmedC2DMessageID}", LogLevel.Information); eventProperties.Add(C2D_MSG_PROPERTY_VALUE_NAME, loRaDevice.LastConfirmedC2DMessageID ?? C2D_MSG_ID_PLACEHOLDER); loRaDevice.LastConfirmedC2DMessageID = null; } var macCommand = loRaPayloadData.GetMacCommands(); if (macCommand.MacCommand.Count > 0) { eventProperties = eventProperties ?? new Dictionary <string, string>(); for (int i = 0; i < macCommand.MacCommand.Count; i++) { eventProperties[macCommand.MacCommand[i].Cid.ToString()] = JsonConvert.SerializeObject(macCommand.MacCommand[i], Formatting.None); // in case it is a link check mac, we need to send it downstream. if (macCommand.MacCommand[i].Cid == CidEnum.LinkCheckCmd) { // linkCheckCmdResponse = new LinkCheckCmd(rxPk.GetModulationMargin(), 1).ToBytes(); } } } if (await loRaDevice.SendEventAsync(deviceTelemetry, eventProperties)) { var payloadAsRaw = deviceTelemetry.Data as string; if (payloadAsRaw == null && deviceTelemetry.Data != null) { payloadAsRaw = JsonConvert.SerializeObject(deviceTelemetry.Data, Formatting.None); } Logger.Log(loRaDevice.DevEUI, $"message '{payloadAsRaw}' sent to hub", LogLevel.Information); return(true); } return(false); }
public void TestUnconfirmedUplink() { string jsonUplinkUnconfirmedDataUp = @"{ ""rxpk"":[ { ""time"":""2013-03-31T16:21:17.528002Z"", ""tmst"":3512348611, ""chan"":2, ""rfch"":0, ""freq"":866.349812, ""stat"":1, ""modu"":""LORA"", ""datr"":""SF7BW125"", ""codr"":""4/6"", ""rssi"":-35, ""lsnr"":5.1, ""size"":32, ""data"":""QAQDAgGAAQABppRkJhXWw7WC"" }]}"; byte[] physicalUpstreamPyld = new byte[12]; physicalUpstreamPyld[0] = 2; var jsonUplinkUnconfirmedDataUpBytes = Encoding.Default.GetBytes(jsonUplinkUnconfirmedDataUp); List <Rxpk> rxpk = Rxpk.CreateRxpk(physicalUpstreamPyld.Concat(jsonUplinkUnconfirmedDataUpBytes).ToArray()); Assert.True(LoRaPayload.TryCreateLoRaPayload(rxpk[0], out LoRaPayload loRaPayload)); Assert.Equal(LoRaMessageType.UnconfirmedDataUp, loRaPayload.LoRaMessageType); LoRaPayloadData loRaPayloadUplinkObj = (LoRaPayloadData)loRaPayload; Assert.True(loRaPayloadUplinkObj.Fcnt.Span.SequenceEqual(new byte[2] { 1, 0 })); Assert.True(loRaPayloadUplinkObj.DevAddr.Span.SequenceEqual(new byte[4] { 1, 2, 3, 4 })); byte[] loRaPayloadUplinkNwkKey = new byte[16] { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 }; Assert.True(loRaPayloadUplinkObj.CheckMic(ConversionHelper.ByteArrayToString(loRaPayloadUplinkNwkKey))); byte[] loRaPayloadUplinkAppKey = new byte[16] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; var key = ConversionHelper.ByteArrayToString(loRaPayloadUplinkAppKey); Assert.Equal("hello", Encoding.ASCII.GetString(loRaPayloadUplinkObj.PerformEncryption(key))); }
public WaitableLoRaRequest CreateWaitableRequest(Rxpk rxpk, IPacketForwarder packetForwarder = null, TimeSpan?startTimeOffset = null, TimeSpan?constantElapsedTime = null) { var requestStartTime = startTimeOffset.HasValue ? DateTime.UtcNow.Subtract(startTimeOffset.Value) : DateTime.UtcNow; var request = new WaitableLoRaRequest(rxpk, packetForwarder ?? this.PacketForwarder, requestStartTime); if (constantElapsedTime.HasValue) { Assert.True(RegionManager.TryResolveRegion(rxpk, out var region)); var timeWatcher = new TestLoRaOperationTimeWatcher(region, constantElapsedTime.Value); request.UseTimeWatcher(timeWatcher); } return(request); }
/// <summary> /// Tries the resolve region. /// </summary> /// <returns><c>true</c>, if a region was resolved, <c>false</c> otherwise.</returns> /// <param name="rxpk">Rxpk.</param> /// <param name="region">Region.</param> public static bool TryResolveRegion(Rxpk rxpk, out Region region) { region = null; // EU863-870 if (rxpk.Freq < 870 && rxpk.Freq > 863) { region = EU868; return(true); }// US902-928 frequency band, upstream messages are between 902 and 915. else if (rxpk.Freq <= 915 && rxpk.Freq >= 902) { region = US915; return(true); } return(false); }
private void DispatchMessages(byte[] buffer, DateTime startTimeProcessing) { try { List <Rxpk> messageRxpks = Rxpk.CreateRxpk(buffer); if (messageRxpks != null) { foreach (var rxpk in messageRxpks) { this.messageDispatcher.DispatchRequest(new LoRaRequest(rxpk, this, startTimeProcessing)); } } } catch (Exception ex) { Logger.Log("UDP", $"failed to dispatch messages: {ex.Message}", LogLevel.Error); } }
/// <summary> /// Tries the resolve region. /// </summary> /// <returns><c>true</c>, if a region was resolved, <c>false</c> otherwise.</returns> /// <param name="rxpk">Rxpk.</param> /// <param name="region">Region.</param> public static bool TryResolveRegion(Rxpk rxpk, out Region region) { region = null; // EU863-870 if (rxpk.Freq < 870 && rxpk.Freq > 863) { region = EU868; return(true); }// US902-928 else if (rxpk.Freq <= 928 && rxpk.Freq >= 902) { region = US915; return(true); } return(false); }
public static bool TryResolveRegion(Rxpk rxpk) { // EU863-870 if (rxpk.Freq < 870 && rxpk.Freq > 863) { CurrentRegion = CreateEU868Region(); return(true); }// US902-928 else if (rxpk.Freq <= 928 && rxpk.Freq >= 902) { CurrentRegion = CreateUS915Region(); return(true); } else { Logger.Log("RegionFactory", "The current frequency plan is not supported. Currently only EU868 and US915 frequency bands are supported.", LogLevel.Error); return(false); } }
public Rxpk CreateUpstreamRxpk(bool isConfirmed, bool hasMacInUpstream, string datr, SimulatedDevice simulatedDevice) { Rxpk rxpk = null; string msgPayload = null; if (isConfirmed) { if (hasMacInUpstream) { // Cofirmed message with Mac command in upstream msgPayload = "02"; var confirmedMessagePayload = simulatedDevice.CreateConfirmedDataUpMessage(msgPayload, isHexPayload: true, fport: 0); rxpk = confirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey, datr: datr).Rxpk[0]; } else { // Cofirmed message without Mac command in upstream msgPayload = "1234567890"; var confirmedMessagePayload = simulatedDevice.CreateConfirmedDataUpMessage(msgPayload); rxpk = confirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey, datr: datr).Rxpk[0]; } } else { if (hasMacInUpstream) { // Uncofirmed message with Mac command in upstream msgPayload = "02"; var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage(msgPayload, isHexPayload: true, fport: 0); rxpk = unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey, datr: datr).Rxpk[0]; } else { // Uncofirmed message without Mac command in upstream msgPayload = "1234567890"; var unconfirmedMessagePayload = simulatedDevice.CreateUnconfirmedDataUpMessage(msgPayload); rxpk = unconfirmedMessagePayload.SerializeUplink(simulatedDevice.AppSKey, simulatedDevice.NwkSKey, datr: datr).Rxpk[0]; } } return(rxpk); }
public SimulatedPacketForwarder(IPEndPoint networkServerIPEndpoint, Rxpk rxpk = null) { IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 1681); this.udpClient = new UdpClient(endPoint); this.networkServerIPEndpoint = networkServerIPEndpoint; TimeAtBoot = DateTimeOffset.Now.UtcTicks; this.MacAddress = Utility.GetMacAddress(); this.rxpk = rxpk ?? new Rxpk() { chan = 7, rfch = 1, freq = 903.700000, stat = 1, modu = "LORA", datr = "SF10BW125", codr = "4/5", rssi = -17, lsnr = 12.0f }; }
private static void TestRxpk(Rxpk rxpk) { Assert.True(LoRaPayload.TryCreateLoRaPayload(rxpk, out LoRaPayload loRaPayload)); Assert.Equal(LoRaMessageType.JoinRequest, loRaPayload.LoRaMessageType); LoRaPayloadJoinRequest joinRequestMessage = (LoRaPayloadJoinRequest)loRaPayload; byte[] joinRequestAppKey = new byte[16] { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; var joinRequestBool = joinRequestMessage.CheckMic(ConversionHelper.ByteArrayToString(joinRequestAppKey)); if (!joinRequestBool) { Console.WriteLine("Join Request type was not computed correclty"); } byte[] joinRequestAppEui = new byte[8] { 1, 2, 3, 4, 1, 2, 3, 4 }; byte[] joinRequestDevEUI = new byte[8] { 2, 3, 4, 5, 2, 3, 4, 5 }; byte[] joinRequestDevNonce = new byte[2] { 16, 45 }; Array.Reverse(joinRequestAppEui); Array.Reverse(joinRequestDevEUI); Array.Reverse(joinRequestDevNonce); Assert.True(joinRequestMessage.AppEUI.ToArray().SequenceEqual(joinRequestAppEui)); Assert.True(joinRequestMessage.DevEUI.ToArray().SequenceEqual(joinRequestDevEUI)); Assert.True(joinRequestMessage.DevNonce.ToArray().SequenceEqual(joinRequestDevNonce)); }
public static bool TryCreateLoRaPayload(Rxpk rxpk, out LoRaPayload loRaPayloadMessage) { byte[] convertedInputMessage = Convert.FromBase64String(rxpk.Data); var messageType = convertedInputMessage[0]; switch (messageType) { case (int)LoRaMessageType.UnconfirmedDataUp: case (int)LoRaMessageType.ConfirmedDataUp: loRaPayloadMessage = new LoRaPayloadData(convertedInputMessage); break; case (int)LoRaMessageType.JoinRequest: loRaPayloadMessage = new LoRaPayloadJoinRequest(convertedInputMessage); break; default: loRaPayloadMessage = null; return(false); } loRaPayloadMessage.LoRaMessageType = (LoRaMessageType)messageType; return(true); }
public async Task Should_Accept( bool isConfirmed, bool hasMacInUpstream, bool hasMacInC2D, bool isTooLongForUpstreamMacCommandInAnswer, bool isSendingInRx2, [CombinatorialValues("SF10BW125", "SF9BW125", "SF8BW125", "SF7BW125")] string datr) { const int InitialDeviceFcntUp = 9; const int InitialDeviceFcntDown = 20; // This scenario makes no sense if (hasMacInUpstream && isTooLongForUpstreamMacCommandInAnswer) { return; } var simulatedDevice = new SimulatedDevice( TestDeviceInfo.CreateABPDevice(1, gatewayID: this.ServerConfiguration.GatewayID), frmCntUp: InitialDeviceFcntUp, frmCntDown: InitialDeviceFcntDown); var loraDevice = this.CreateLoRaDevice(simulatedDevice); Rxpk rxpk = this.CreateUpstreamRxpk(isConfirmed, hasMacInUpstream, datr, simulatedDevice); if (!hasMacInUpstream) { this.LoRaDeviceClient.Setup(x => x.SendEventAsync(It.IsNotNull <LoRaDeviceTelemetry>(), null)) .ReturnsAsync(true); } var euRegion = RegionManager.EU868; var c2dMessageMacCommand = new DevStatusRequest(); var c2dMessageMacCommandSize = hasMacInC2D ? c2dMessageMacCommand.Length : 0; var upstreamMessageMacCommandSize = 0; string expectedDownlinkDatr; if (hasMacInUpstream && !isTooLongForUpstreamMacCommandInAnswer) { upstreamMessageMacCommandSize = new LinkCheckAnswer(1, 1).Length; } if (isSendingInRx2) { expectedDownlinkDatr = euRegion.DRtoConfiguration[euRegion.RX2DefaultReceiveWindows.dr].configuration; } else { expectedDownlinkDatr = datr; } var c2dPayloadSize = euRegion.GetMaxPayloadSize(expectedDownlinkDatr) - c2dMessageMacCommandSize - upstreamMessageMacCommandSize - Constants.LORA_PROTOCOL_OVERHEAD_SIZE; var c2dMessagePayload = TestUtils.GeneratePayload("123457890", (int)c2dPayloadSize); var c2dMessage = new ReceivedLoRaCloudToDeviceMessage() { Payload = c2dMessagePayload, Fport = 1, }; if (hasMacInC2D) { c2dMessage.MacCommands = new[] { c2dMessageMacCommand }; } var cloudToDeviceMessage = c2dMessage.CreateMessage(); this.LoRaDeviceClient.SetupSequence(x => x.ReceiveAsync(It.IsAny <TimeSpan>())) .ReturnsAsync(cloudToDeviceMessage) .ReturnsAsync((Message)null); this.LoRaDeviceClient.Setup(x => x.CompleteAsync(cloudToDeviceMessage)) .ReturnsAsync(true); var deviceRegistry = new LoRaDeviceRegistry(this.ServerConfiguration, this.NewNonEmptyCache(loraDevice), this.LoRaDeviceApi.Object, this.LoRaDeviceFactory); // Send to message processor var messageProcessor = new MessageDispatcher( this.ServerConfiguration, deviceRegistry, this.FrameCounterUpdateStrategyProvider); var startTimeOffset = isSendingInRx2 ? TestUtils.GetStartTimeOffsetForSecondWindow() : TimeSpan.Zero; var request = this.CreateWaitableRequest(rxpk, startTimeOffset: startTimeOffset); messageProcessor.DispatchRequest(request); // Expectations // 1. Message was sent to IoT Hub Assert.True(await request.WaitCompleteAsync()); Assert.True(request.ProcessingSucceeded); // 2. Return is downstream message Assert.NotNull(request.ResponseDownlink); Assert.Equal(expectedDownlinkDatr, request.ResponseDownlink.Txpk.Datr); // Get downlink message var downlinkMessage = this.PacketForwarder.DownlinkMessages[0]; var payloadDataDown = new LoRaPayloadData(Convert.FromBase64String(downlinkMessage.Txpk.Data)); payloadDataDown.PerformEncryption(loraDevice.AppSKey); // 3. downlink message payload contains expected message type and DevAddr Assert.Equal(payloadDataDown.DevAddr.ToArray(), LoRaTools.Utils.ConversionHelper.StringToByteArray(loraDevice.DevAddr)); Assert.Equal(LoRaMessageType.UnconfirmedDataDown, payloadDataDown.LoRaMessageType); // 4. Expected Mac commands are present var expectedMacCommandsCount = 0; if (hasMacInC2D) { expectedMacCommandsCount++; } if (hasMacInUpstream && !isTooLongForUpstreamMacCommandInAnswer) { expectedMacCommandsCount++; } if (expectedMacCommandsCount > 0) { // Possible problem: Manually casting payloadDataDown.Fopts to array and reversing it var macCommands = MacCommand.CreateServerMacCommandFromBytes(simulatedDevice.DevEUI, payloadDataDown.Fopts.ToArray().Reverse().ToArray()); Assert.Equal(expectedMacCommandsCount, macCommands.Count); } else { Assert.Null(payloadDataDown.MacCommands); } this.LoRaDeviceClient.VerifyAll(); this.LoRaDeviceApi.VerifyAll(); }
private async Task <byte[]> ProcessLoraMessage(LoRaMessage loraMessage) { bool validFrameCounter = false; byte[] udpMsgForPktForwarder = new byte[0]; string devAddr = BitConverter.ToString(loraMessage.payloadMessage.devAddr).Replace("-", ""); Message c2dMsg = null; Cache.TryGetValue(devAddr, out LoraDeviceInfo loraDeviceInfo); if (loraDeviceInfo == null) { loraDeviceInfo = await LoraDeviceInfoManager.GetLoraDeviceInfoAsync(devAddr); Logger.Log(loraDeviceInfo.DevEUI, $"processing message, device not in cache", Logger.LoggingLevel.Info); Cache.AddToCache(devAddr, loraDeviceInfo); } else { Logger.Log(loraDeviceInfo.DevEUI, $"processing message, device in cache", Logger.LoggingLevel.Info); } if (loraDeviceInfo.IsOurDevice) { //either there is no gateway linked to the device or the gateway is the one that the code is running if (String.IsNullOrEmpty(loraDeviceInfo.GatewayID) || loraDeviceInfo.GatewayID.ToUpper() == GatewayID.ToUpper()) { if (loraMessage.CheckMic(loraDeviceInfo.NwkSKey)) { if (loraDeviceInfo.HubSender == null) { loraDeviceInfo.HubSender = new IoTHubSender(loraDeviceInfo.DevEUI, loraDeviceInfo.PrimaryKey); } UInt16 fcntup = BitConverter.ToUInt16(((LoRaPayloadStandardData)loraMessage.payloadMessage).fcnt, 0); //check if the frame counter is valid: either is above the server one or is an ABP device resetting the counter (relaxed seqno checking) if (fcntup > loraDeviceInfo.FCntUp || (fcntup == 1 && String.IsNullOrEmpty(loraDeviceInfo.AppEUI))) { //save the reset fcnt for ABP (relaxed seqno checking) if (fcntup == 1 && String.IsNullOrEmpty(loraDeviceInfo.AppEUI)) { _ = loraDeviceInfo.HubSender.UpdateFcntAsync(fcntup, 0, true); } validFrameCounter = true; Logger.Log(loraDeviceInfo.DevEUI, $"valid frame counter, msg: {fcntup} server: {loraDeviceInfo.FCntUp}", Logger.LoggingLevel.Info); byte[] decryptedMessage = null; try { decryptedMessage = loraMessage.DecryptPayload(loraDeviceInfo.AppSKey); } catch (Exception ex) { Logger.Log(loraDeviceInfo.DevEUI, $"failed to decrypt message: {ex.Message}", Logger.LoggingLevel.Error); } Rxpk rxPk = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0]; dynamic fullPayload = JObject.FromObject(rxPk); string jsonDataPayload = ""; uint fportUp = (uint)((LoRaPayloadStandardData)loraMessage.payloadMessage).fport[0]; fullPayload.port = fportUp; fullPayload.fcnt = fcntup; if (String.IsNullOrEmpty(loraDeviceInfo.SensorDecoder)) { jsonDataPayload = Convert.ToBase64String(decryptedMessage); fullPayload.data = jsonDataPayload; } else { Logger.Log(loraDeviceInfo.DevEUI, $"decoding with: {loraDeviceInfo.SensorDecoder} port: {fportUp}", Logger.LoggingLevel.Info); jsonDataPayload = LoraDecoders.DecodeMessage(decryptedMessage, fportUp, loraDeviceInfo.SensorDecoder); fullPayload.data = JObject.Parse(jsonDataPayload); } fullPayload.eui = loraDeviceInfo.DevEUI; fullPayload.gatewayid = GatewayID; //todo check what the other ts are if milliseconds or seconds fullPayload.edgets = (long)((startTimeProcessing - new DateTime(1970, 1, 1)).TotalMilliseconds); string iotHubMsg = fullPayload.ToString(Newtonsoft.Json.Formatting.None); await loraDeviceInfo.HubSender.SendMessageAsync(iotHubMsg); Logger.Log(loraDeviceInfo.DevEUI, $"sent message '{jsonDataPayload}' to hub", Logger.LoggingLevel.Info); loraDeviceInfo.FCntUp = fcntup; } else { validFrameCounter = false; Logger.Log(loraDeviceInfo.DevEUI, $"invalid frame counter, msg: {fcntup} server: {loraDeviceInfo.FCntUp}", Logger.LoggingLevel.Info); } //start checking for new c2d message, we do it even if the fcnt is invalid so we support replying to the ConfirmedDataUp //todo ronnie should we wait up to 900 msec? c2dMsg = await loraDeviceInfo.HubSender.ReceiveAsync(TimeSpan.FromMilliseconds(20)); byte[] bytesC2dMsg = null; byte[] fport = null; byte[] fctl = new byte[1] { 32 }; //check if we got a c2d message to be added in the ack message and preprare the message if (c2dMsg != null) { //check if there is another message var secondC2dMsg = await loraDeviceInfo.HubSender.ReceiveAsync(TimeSpan.FromMilliseconds(20)); if (secondC2dMsg != null) { //put it back to the queue for the next pickup _ = loraDeviceInfo.HubSender.AbandonAsync(secondC2dMsg); //set the fpending flag so the lora device will call us back for the next message fctl = new byte[1] { 48 }; } bytesC2dMsg = c2dMsg.GetBytes(); fport = new byte[1] { 1 }; if (bytesC2dMsg != null) { Logger.Log(loraDeviceInfo.DevEUI, $"C2D message: {Encoding.UTF8.GetString(bytesC2dMsg)}", Logger.LoggingLevel.Info); } //todo ronnie implement a better max payload size by datarate //cut to the max payload of lora for any EU datarate if (bytesC2dMsg.Length > 51) { Array.Resize(ref bytesC2dMsg, 51); } Array.Reverse(bytesC2dMsg); } //if confirmation or cloud to device msg send down the message if (loraMessage.loRaMessageType == LoRaMessageType.ConfirmedDataUp || c2dMsg != null) { //check if we are not too late for the 1 and 2 window if (((DateTime.UtcNow - startTimeProcessing) <= TimeSpan.FromMilliseconds(1900))) { //increase the fcnt down and save it to iot hub twins loraDeviceInfo.FCntDown++; Logger.Log(loraDeviceInfo.DevEUI, $"down frame counter: {loraDeviceInfo.FCntDown}", Logger.LoggingLevel.Info); //Saving both fcnts to twins _ = loraDeviceInfo.HubSender.UpdateFcntAsync(loraDeviceInfo.FCntUp, loraDeviceInfo.FCntDown); var _datr = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].datr; uint _rfch = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].rfch; double _freq = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].freq; uint txDelay = 0; //if we are already longer than 900 mssecond move to the 2 second window //uncomment to force second windows usage //Thread.Sleep(901); if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(900)) { if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("RX2_DATR"))) { Logger.Log(loraDeviceInfo.DevEUI, $"using standard second receive windows", Logger.LoggingLevel.Info); //using EU fix DR for RX2 _freq = 869.525; _datr = "SF12BW125"; } //if specific twins are set, specify second channel to be as specified else { _freq = double.Parse(Environment.GetEnvironmentVariable("RX2_FREQ")); _datr = Environment.GetEnvironmentVariable("RX2_DATR"); Logger.Log(loraDeviceInfo.DevEUI, $"using custom DR second receive windows freq : {_freq}, datr:{_datr}", Logger.LoggingLevel.Info); } txDelay = 1000000; } long _tmst = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].tmst + txDelay; Byte[] devAddrCorrect = new byte[4]; Array.Copy(loraMessage.payloadMessage.devAddr, devAddrCorrect, 4); Array.Reverse(devAddrCorrect); //todo mik check what is the A0 LoRaPayloadStandardData ackLoRaMessage = new LoRaPayloadStandardData(StringToByteArray("A0"), devAddrCorrect, fctl, BitConverter.GetBytes(loraDeviceInfo.FCntDown), null, fport, bytesC2dMsg, 1); ackLoRaMessage.PerformEncryption(loraDeviceInfo.AppSKey); ackLoRaMessage.SetMic(loraDeviceInfo.NwkSKey); byte[] rndToken = new byte[2]; Random rnd = new Random(); rnd.NextBytes(rndToken); //todo ronnie should check the device twin preference if using confirmed or unconfirmed down LoRaMessage ackMessage = new LoRaMessage(ackLoRaMessage, LoRaMessageType.ConfirmedDataDown, rndToken, _datr, 0, _freq, _tmst); udpMsgForPktForwarder = ackMessage.physicalPayload.GetMessage(); //confirm the message to iot hub only if we are in time for a delivery if (c2dMsg != null) { _ = loraDeviceInfo.HubSender.CompleteAsync(c2dMsg); Logger.Log(loraDeviceInfo.DevEUI, $"complete the c2d msg to IoT Hub", Logger.LoggingLevel.Info); } } else { PhysicalPayload pushAck = new PhysicalPayload(loraMessage.physicalPayload.token, PhysicalIdentifier.PUSH_ACK, null); udpMsgForPktForwarder = pushAck.GetMessage(); _ = loraDeviceInfo.HubSender.UpdateFcntAsync(loraDeviceInfo.FCntUp, null); //put back the c2d message to the queue for the next round _ = loraDeviceInfo.HubSender.AbandonAsync(c2dMsg); Logger.Log(loraDeviceInfo.DevEUI, $"too late for down message, sending only ACK to gateway", Logger.LoggingLevel.Info); } } //No ack requested and no c2d message we send the udp ack only to the gateway else if (loraMessage.loRaMessageType == LoRaMessageType.UnconfirmedDataUp && c2dMsg == null) { PhysicalPayload pushAck = new PhysicalPayload(loraMessage.physicalPayload.token, PhysicalIdentifier.PUSH_ACK, null); udpMsgForPktForwarder = pushAck.GetMessage(); if (validFrameCounter) { _ = loraDeviceInfo.HubSender.UpdateFcntAsync(loraDeviceInfo.FCntUp, null); } } } else { Logger.Log(loraDeviceInfo.DevEUI, $"with devAddr {devAddr} check MIC failed. Device will be ignored from now on", Logger.LoggingLevel.Info); loraDeviceInfo.IsOurDevice = false; } } else { Logger.Log(loraDeviceInfo.DevEUI, $"ignore message because is not linked to this GatewayID", Logger.LoggingLevel.Info); } } else { Logger.Log(devAddr, $"device with devAddr {devAddr} is not our device, ignore message", Logger.LoggingLevel.Info); } Logger.Log(loraDeviceInfo.DevEUI, $"processing time: {DateTime.UtcNow - startTimeProcessing}", Logger.LoggingLevel.Info); return(udpMsgForPktForwarder); }
private async Task <byte[]> ProcessLoraMessage(LoRaMessageWrapper loraMessage) { bool validFrameCounter = false; byte[] udpMsgForPktForwarder = new byte[0]; string devAddr = ConversionHelper.ByteArrayToString(loraMessage.LoRaPayloadMessage.DevAddr.ToArray()); Message c2dMsg = null; Cache.TryGetValue(devAddr, out LoraDeviceInfo loraDeviceInfo); if (loraDeviceInfo == null || !loraDeviceInfo.IsOurDevice) { loraDeviceInfo = await LoraDeviceInfoManager.GetLoraDeviceInfoAsync(devAddr, GatewayID); if (loraDeviceInfo.DevEUI != null) { Logger.Log(loraDeviceInfo.DevEUI, $"processing message, device not in cache", Logger.LoggingLevel.Info); } else { Logger.Log(devAddr, $"processing message, device not in cache", Logger.LoggingLevel.Info); } Cache.AddToCache(devAddr, loraDeviceInfo); } else { Logger.Log(loraDeviceInfo.DevEUI, $"processing message, device in cache", Logger.LoggingLevel.Info); } if (loraDeviceInfo != null && loraDeviceInfo.IsOurDevice) { //either there is no gateway linked to the device or the gateway is the one that the code is running if (String.IsNullOrEmpty(loraDeviceInfo.GatewayID) || loraDeviceInfo.GatewayID.ToUpper() == GatewayID.ToUpper()) { if (loraMessage.CheckMic(loraDeviceInfo.NwkSKey)) { if (loraDeviceInfo.HubSender == null) { loraDeviceInfo.HubSender = new IoTHubConnector(loraDeviceInfo.DevEUI, loraDeviceInfo.PrimaryKey); } UInt16 fcntup = BitConverter.ToUInt16(loraMessage.LoRaPayloadMessage.GetLoRaMessage().Fcnt.ToArray(), 0); byte[] linkCheckCmdResponse = null; //check if the frame counter is valid: either is above the server one or is an ABP device resetting the counter (relaxed seqno checking) if (fcntup > loraDeviceInfo.FCntUp || (fcntup == 0 && loraDeviceInfo.FCntUp == 0) || (fcntup == 1 && String.IsNullOrEmpty(loraDeviceInfo.AppEUI))) { //save the reset fcnt for ABP (relaxed seqno checking) if (fcntup == 1 && String.IsNullOrEmpty(loraDeviceInfo.AppEUI)) { _ = loraDeviceInfo.HubSender.UpdateFcntAsync(fcntup, 0, true); //if the device is not attached to a gateway we need to reset the abp fcnt server side cache if (String.IsNullOrEmpty(loraDeviceInfo.GatewayID)) { bool rit = await LoraDeviceInfoManager.ABPFcntCacheReset(loraDeviceInfo.DevEUI); } } validFrameCounter = true; Logger.Log(loraDeviceInfo.DevEUI, $"valid frame counter, msg: {fcntup} server: {loraDeviceInfo.FCntUp}", Logger.LoggingLevel.Info); byte[] decryptedMessage = null; try { decryptedMessage = loraMessage.DecryptPayload(loraDeviceInfo.AppSKey); } catch (Exception ex) { Logger.Log(loraDeviceInfo.DevEUI, $"failed to decrypt message: {ex.Message}", Logger.LoggingLevel.Error); } Rxpk rxPk = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0]; dynamic fullPayload = JObject.FromObject(rxPk); string jsonDataPayload = ""; uint fportUp = 0; bool isAckFromDevice = false; if (loraMessage.LoRaPayloadMessage.GetLoRaMessage().Fport.Span.Length > 0) { fportUp = (uint)loraMessage.LoRaPayloadMessage.GetLoRaMessage().Fport.Span[0]; } else // this is an acknowledgment sent from the device { isAckFromDevice = true; fullPayload.deviceAck = true; } fullPayload.port = fportUp; fullPayload.fcnt = fcntup; if (isAckFromDevice) { jsonDataPayload = Convert.ToBase64String(decryptedMessage); fullPayload.data = jsonDataPayload; } else { Logger.Log(loraDeviceInfo.DevEUI, $"decoding with: {loraDeviceInfo.SensorDecoder} port: {fportUp}", Logger.LoggingLevel.Info); fullPayload.data = await LoraDecoders.DecodeMessage(decryptedMessage, fportUp, loraDeviceInfo.SensorDecoder); } fullPayload.eui = loraDeviceInfo.DevEUI; fullPayload.gatewayid = GatewayID; //Edge timestamp fullPayload.edgets = (long)((startTimeProcessing - new DateTime(1970, 1, 1)).TotalMilliseconds); List <KeyValuePair <String, String> > messageProperties = new List <KeyValuePair <String, String> >(); //Parsing MacCommands and add them as property of the message to be sent to the IoT Hub. var macCommand = ((LoRaPayloadData)loraMessage.LoRaPayloadMessage).GetMacCommands(); if (macCommand.macCommand.Count > 0) { for (int i = 0; i < macCommand.macCommand.Count; i++) { messageProperties.Add(new KeyValuePair <string, string>(macCommand.macCommand[i].Cid.ToString(), value: JsonConvert.SerializeObject(macCommand.macCommand[i], Newtonsoft.Json.Formatting.None))); //in case it is a link check mac, we need to send it downstream. if (macCommand.macCommand[i].Cid == CidEnum.LinkCheckCmd) { linkCheckCmdResponse = new LinkCheckCmd(rxPk.GetModulationMargin(), 1).ToBytes(); } } } string iotHubMsg = fullPayload.ToString(Newtonsoft.Json.Formatting.None); await loraDeviceInfo.HubSender.SendMessageAsync(iotHubMsg, messageProperties); if (isAckFromDevice) { Logger.Log(loraDeviceInfo.DevEUI, $"ack from device sent to hub", Logger.LoggingLevel.Info); } else { var fullPayloadAsString = fullPayload.data as string; if (fullPayloadAsString == null) { fullPayloadAsString = ((JObject)fullPayload.data).ToString(Formatting.None); } Logger.Log(loraDeviceInfo.DevEUI, $"message '{fullPayloadAsString}' sent to hub", Logger.LoggingLevel.Info); } loraDeviceInfo.FCntUp = fcntup; } else { validFrameCounter = false; Logger.Log(loraDeviceInfo.DevEUI, $"invalid frame counter, msg: {fcntup} server: {loraDeviceInfo.FCntUp}", Logger.LoggingLevel.Info); } //we lock as fast as possible and get the down fcnt for multi gateway support for confirmed message if (loraMessage.LoRaMessageType == LoRaMessageType.ConfirmedDataUp && String.IsNullOrEmpty(loraDeviceInfo.GatewayID)) { ushort newFCntDown = await LoraDeviceInfoManager.NextFCntDown(loraDeviceInfo.DevEUI, loraDeviceInfo.FCntDown, fcntup, GatewayID); //ok to send down ack or msg if (newFCntDown > 0) { loraDeviceInfo.FCntDown = newFCntDown; } //another gateway was first with this message we simply drop else { PhysicalPayload pushAck = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PUSH_ACK, null); udpMsgForPktForwarder = pushAck.GetMessage(); Logger.Log(loraDeviceInfo.DevEUI, $"another gateway has already sent ack or downlink msg", Logger.LoggingLevel.Info); Logger.Log(loraDeviceInfo.DevEUI, $"processing time: {DateTime.UtcNow - startTimeProcessing}", Logger.LoggingLevel.Info); return(udpMsgForPktForwarder); } } //start checking for new c2d message, we do it even if the fcnt is invalid so we support replying to the ConfirmedDataUp //todo ronnie should we wait up to 900 msec? c2dMsg = await loraDeviceInfo.HubSender.ReceiveAsync(TimeSpan.FromMilliseconds(20)); byte[] bytesC2dMsg = null; byte[] fport = null; //Todo revamp fctrl byte[] fctrl = new byte[1] { 32 }; //check if we got a c2d message to be added in the ack message and prepare the message if (c2dMsg != null) { ////check if there is another message var secondC2dMsg = await loraDeviceInfo.HubSender.ReceiveAsync(TimeSpan.FromMilliseconds(20)); if (secondC2dMsg != null) { //put it back to the queue for the next pickup //todo ronnie check abbandon logic especially in case of mqtt _ = await loraDeviceInfo.HubSender.AbandonAsync(secondC2dMsg); //set the fpending flag so the lora device will call us back for the next message fctrl = new byte[1] { 48 }; } bytesC2dMsg = c2dMsg.GetBytes(); fport = new byte[1] { 1 }; if (bytesC2dMsg != null) { Logger.Log(loraDeviceInfo.DevEUI, $"C2D message: {Encoding.UTF8.GetString(bytesC2dMsg)}", Logger.LoggingLevel.Info); } //todo ronnie implement a better max payload size by datarate //cut to the max payload of lora for any EU datarate if (bytesC2dMsg.Length > 51) { Array.Resize(ref bytesC2dMsg, 51); } Array.Reverse(bytesC2dMsg); } //if confirmation or cloud to device msg send down the message if (loraMessage.LoRaMessageType == LoRaMessageType.ConfirmedDataUp || c2dMsg != null) { //check if we are not too late for the second receive windows if ((DateTime.UtcNow - startTimeProcessing) <= TimeSpan.FromMilliseconds(RegionFactory.CurrentRegion.receive_delay2 * 1000 - 100)) { //if running in multigateway we need to use redis to sync the down fcnt if (!String.IsNullOrEmpty(loraDeviceInfo.GatewayID)) { loraDeviceInfo.FCntDown++; } else if (loraMessage.LoRaMessageType == LoRaMessageType.UnconfirmedDataUp) { ushort newFCntDown = await LoraDeviceInfoManager.NextFCntDown(loraDeviceInfo.DevEUI, loraDeviceInfo.FCntDown, fcntup, GatewayID); //ok to send down ack or msg if (newFCntDown > 0) { loraDeviceInfo.FCntDown = newFCntDown; } //another gateway was first with this message we simply drop else { PhysicalPayload pushAck = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PUSH_ACK, null); udpMsgForPktForwarder = pushAck.GetMessage(); Logger.Log(loraDeviceInfo.DevEUI, $"another gateway has already sent ack or downlink msg", Logger.LoggingLevel.Info); Logger.Log(loraDeviceInfo.DevEUI, $"processing time: {DateTime.UtcNow - startTimeProcessing}", Logger.LoggingLevel.Info); return(udpMsgForPktForwarder); } } Logger.Log(loraDeviceInfo.DevEUI, $"down frame counter: {loraDeviceInfo.FCntDown}", Logger.LoggingLevel.Info); //Saving both fcnts to twins _ = loraDeviceInfo.HubSender.UpdateFcntAsync(loraDeviceInfo.FCntUp, loraDeviceInfo.FCntDown); //todo need implementation of current configuation to implement this as this depends on RX1DROffset //var _datr = ((UplinkPktFwdMessage)loraMessage.LoraMetadata.FullPayload).rxpk[0].datr; var datr = RegionFactory.CurrentRegion.GetDownstreamDR(loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0]); //todo should discuss about the logic in case of multi channel gateway. uint rfch = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].rfch; //todo should discuss about the logic in case of multi channel gateway double freq = RegionFactory.CurrentRegion.GetDownstreamChannel(loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0]); //if we are already longer than 900 mssecond move to the 2 second window //uncomment the following line to force second windows usage TODO change this to a proper expression? //Thread.Sleep(901 long tmst = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].tmst + RegionFactory.CurrentRegion.receive_delay1 * 1000000; if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(RegionFactory.CurrentRegion.receive_delay1 * 1000 - 100)) { fctrl = new byte[1] { 32 }; if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("RX2_DATR"))) { Logger.Log(loraDeviceInfo.DevEUI, $"using standard second receive windows", Logger.LoggingLevel.Info); tmst = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].tmst + RegionFactory.CurrentRegion.receive_delay2 * 1000000; freq = RegionFactory.CurrentRegion.RX2DefaultReceiveWindows.frequency; datr = RegionFactory.CurrentRegion.DRtoConfiguration[RegionFactory.CurrentRegion.RX2DefaultReceiveWindows.dr].configuration; } //if specific twins are set, specify second channel to be as specified else { freq = double.Parse(Environment.GetEnvironmentVariable("RX2_FREQ")); datr = Environment.GetEnvironmentVariable("RX2_DATR"); Logger.Log(loraDeviceInfo.DevEUI, $"using custom DR second receive windows freq : {freq}, datr:{datr}", Logger.LoggingLevel.Info); } } Byte[] devAddrCorrect = new byte[4]; Array.Copy(loraMessage.LoRaPayloadMessage.DevAddr.ToArray(), devAddrCorrect, 4); Array.Reverse(devAddrCorrect); bool requestForConfirmedResponse = false; //check if the c2d message has a mac command byte[] macbytes = null; if (c2dMsg != null) { var macCmd = c2dMsg.Properties.Where(o => o.Key == "CidType"); if (macCmd.Count() != 0) { MacCommandHolder macCommandHolder = new MacCommandHolder(Convert.ToByte(macCmd.First().Value)); macbytes = macCommandHolder.macCommand[0].ToBytes(); } var confirmCmd = c2dMsg.Properties.Where(o => o.Key == "Confirmed"); if (confirmCmd.Count() != 0) { requestForConfirmedResponse = true; } } if (requestForConfirmedResponse) { fctrl[0] += 16; } if (macbytes != null && linkCheckCmdResponse != null) { macbytes = macbytes.Concat(linkCheckCmdResponse).ToArray(); } LoRaPayloadData ackLoRaMessage = new LoRaPayloadData( requestForConfirmedResponse ? MType.ConfirmedDataDown : MType.UnconfirmedDataDown, //ConversionHelper.StringToByteArray(requestForConfirmedResponse?"A0":"60"), devAddrCorrect, fctrl, BitConverter.GetBytes(loraDeviceInfo.FCntDown), macbytes, fport, bytesC2dMsg, 1); ackLoRaMessage.PerformEncryption(loraDeviceInfo.AppSKey); ackLoRaMessage.SetMic(loraDeviceInfo.NwkSKey); byte[] rndToken = new byte[2]; Random rnd = new Random(); rnd.NextBytes(rndToken); //todo ronnie should check the device twin preference if using confirmed or unconfirmed down LoRaMessageWrapper ackMessage = new LoRaMessageWrapper(ackLoRaMessage, LoRaMessageType.UnconfirmedDataDown, rndToken, datr, 0, freq, tmst); udpMsgForPktForwarder = ackMessage.PhysicalPayload.GetMessage(); linkCheckCmdResponse = null; //confirm the message to iot hub only if we are in time for a delivery if (c2dMsg != null) { //todo ronnie check if it is ok to do async so we make it in time to send the message _ = loraDeviceInfo.HubSender.CompleteAsync(c2dMsg); //bool rit = await loraDeviceInfo.HubSender.CompleteAsync(c2dMsg); //if (rit) // Logger.Log(loraDeviceInfo.DevEUI, $"completed the c2d msg to IoT Hub", Logger.LoggingLevel.Info); //else //{ // //we could not complete the msg so we send only a pushAck // PhysicalPayload pushAck = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PUSH_ACK, null); // udpMsgForPktForwarder = pushAck.GetMessage(); // Logger.Log(loraDeviceInfo.DevEUI, $"could not complete the c2d msg to IoT Hub", Logger.LoggingLevel.Info); //} } } else { PhysicalPayload pushAck = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PUSH_ACK, null); udpMsgForPktForwarder = pushAck.GetMessage(); //put back the c2d message to the queue for the next round //todo ronnie check abbandon logic especially in case of mqtt if (c2dMsg != null) { _ = await loraDeviceInfo.HubSender.AbandonAsync(c2dMsg); } Logger.Log(loraDeviceInfo.DevEUI, $"too late for down message, sending only ACK to gateway", Logger.LoggingLevel.Info); _ = loraDeviceInfo.HubSender.UpdateFcntAsync(loraDeviceInfo.FCntUp, null); } } //No ack requested and no c2d message we send the udp ack only to the gateway else if (loraMessage.LoRaMessageType == LoRaMessageType.UnconfirmedDataUp && c2dMsg == null) { PhysicalPayload pushAck = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PUSH_ACK, null); udpMsgForPktForwarder = pushAck.GetMessage(); ////if ABP and 1 we reset the counter (loose frame counter) with force, if not we update normally //if (fcntup == 1 && String.IsNullOrEmpty(loraDeviceInfo.AppEUI)) // _ = loraDeviceInfo.HubSender.UpdateFcntAsync(fcntup, null, true); if (validFrameCounter) { _ = loraDeviceInfo.HubSender.UpdateFcntAsync(loraDeviceInfo.FCntUp, null); } } //If there is a link check command waiting if (linkCheckCmdResponse != null) { Byte[] devAddrCorrect = new byte[4]; Array.Copy(loraMessage.LoRaPayloadMessage.DevAddr.ToArray(), devAddrCorrect, 4); byte[] fctrl2 = new byte[1] { 32 }; Array.Reverse(devAddrCorrect); LoRaPayloadData macReply = new LoRaPayloadData(MType.ConfirmedDataDown, devAddrCorrect, fctrl2, BitConverter.GetBytes(loraDeviceInfo.FCntDown), null, new byte[1] { 0 }, linkCheckCmdResponse, 1); macReply.PerformEncryption(loraDeviceInfo.AppSKey); macReply.SetMic(loraDeviceInfo.NwkSKey); byte[] rndToken = new byte[2]; Random rnd = new Random(); rnd.NextBytes(rndToken); var datr = RegionFactory.CurrentRegion.GetDownstreamDR(loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0]); //todo should discuss about the logic in case of multi channel gateway. uint rfch = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].rfch; double freq = RegionFactory.CurrentRegion.GetDownstreamChannel(loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0]); long tmst = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].tmst + RegionFactory.CurrentRegion.receive_delay1 * 1000000; //todo ronnie should check the device twin preference if using confirmed or unconfirmed down LoRaMessageWrapper ackMessage = new LoRaMessageWrapper(macReply, LoRaMessageType.UnconfirmedDataDown, rndToken, datr, 0, freq, tmst); udpMsgForPktForwarder = ackMessage.PhysicalPayload.GetMessage(); } } else { Logger.Log(loraDeviceInfo.DevEUI, $"with devAddr {devAddr} check MIC failed. Device will be ignored from now on", Logger.LoggingLevel.Info); loraDeviceInfo.IsOurDevice = false; } } else { Logger.Log(loraDeviceInfo.DevEUI, $"ignore message because is not linked to this GatewayID", Logger.LoggingLevel.Info); } } else { Logger.Log(devAddr, $"device is not our device, ignore message", Logger.LoggingLevel.Info); } Logger.Log(loraDeviceInfo.DevEUI, $"processing time: {DateTime.UtcNow - startTimeProcessing}", Logger.LoggingLevel.Info); return(udpMsgForPktForwarder); }
/// <summary> /// Prepare the Mac Commands to be sent in the downstream message. /// </summary> static ICollection <MacCommand> PrepareMacCommandAnswer( string devEUI, IEnumerable <MacCommand> requestedMacCommands, IEnumerable <MacCommand> serverMacCommands, Rxpk rxpk, LoRaADRResult loRaADRResult) { var macCommands = new Dictionary <int, MacCommand>(); if (requestedMacCommands != null) { foreach (var requestedMacCommand in requestedMacCommands) { switch (requestedMacCommand.Cid) { case CidEnum.LinkCheckCmd: { if (rxpk != null) { var linkCheckAnswer = new LinkCheckAnswer(rxpk.GetModulationMargin(), 1); if (macCommands.TryAdd((int)CidEnum.LinkCheckCmd, linkCheckAnswer)) { Logger.Log(devEUI, $"answering to a MAC command request {linkCheckAnswer.ToString()}", LogLevel.Information); } } break; } } } } if (serverMacCommands != null) { foreach (var macCmd in serverMacCommands) { if (macCmd != null) { try { if (!macCommands.TryAdd((int)macCmd.Cid, macCmd)) { Logger.Log(devEUI, $"could not send the cloud to device MAC command {macCmd.Cid}, as such a property was already present in the message. Please resend the cloud to device message", LogLevel.Error); } Logger.Log(devEUI, $"cloud to device MAC command {macCmd.Cid} received {macCmd}", LogLevel.Information); } catch (MacCommandException ex) { Logger.Log(devEUI, ex.ToString(), LogLevel.Error); } } } } // ADR Part. // Currently only replying on ADR Req if (loRaADRResult?.CanConfirmToDevice == true) { const int placeholderChannel = 25; LinkADRRequest linkADR = new LinkADRRequest((byte)loRaADRResult.DataRate, (byte)loRaADRResult.TxPower, placeholderChannel, 0, (byte)loRaADRResult.NbRepetition); macCommands.Add((int)CidEnum.LinkADRCmd, linkADR); Logger.Log(devEUI, $"performing a rate adaptation: DR {loRaADRResult.DataRate}, transmit power {loRaADRResult.TxPower}, #repetition {loRaADRResult.NbRepetition}", LogLevel.Information); } return(macCommands.Values); }
public WaitableLoRaRequest(Rxpk rxpk, IPacketForwarder packetForwarder) : this(rxpk, packetForwarder, DateTime.UtcNow) { }
public WaitableLoRaRequest(Rxpk rxpk, IPacketForwarder packetForwarder, DateTime startTime) : base(rxpk, packetForwarder, startTime) { this.complete = new SemaphoreSlim(0); }