/// <summary> /// Method to create a Rxpk object from a byte array. /// This is typically used for an upstream communication. /// </summary> /// <param name="inputMessage">Input byte array</param> /// <returns>List of rxpk or null if no Rxpk was found</returns> public static List <Rxpk> CreateRxpk(byte[] inputMessage) { PhysicalPayload physicalPayload = new PhysicalPayload(inputMessage); if (physicalPayload.Message != null) { var payload = Encoding.UTF8.GetString(physicalPayload.Message); if (!payload.StartsWith("{\"stat")) { Logger.Log($"Physical dataUp {payload}", LogLevel.Debug); var payloadObject = JsonConvert.DeserializeObject <UplinkPktFwdMessage>(payload); if (payloadObject != null) { if (payloadObject.Rxpk != null) { return(payloadObject.Rxpk); } } } else { Logger.Log($"Statistic: {payload}", LogLevel.Debug); } } return(new List <Rxpk>()); }
async Task IPacketForwarder.SendDownstreamAsync(DownlinkPktFwdMessage downstreamMessage) { try { 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 && !string.IsNullOrEmpty(this.pullAckRemoteLoRaAddress)) { Logger.Log("UDP", $"sending message with ID {ConversionHelper.ByteArrayToString(token)}, to {this.pullAckRemoteLoRaAddress}:{this.pullAckRemoteLoRaAggregatorPort}", LogLevel.Debug); await this.UdpSendMessageAsync(pyld.GetMessage(), this.pullAckRemoteLoRaAddress, this.pullAckRemoteLoRaAggregatorPort); Logger.Log("UDP", $"message sent with ID {ConversionHelper.ByteArrayToString(token)}", LogLevel.Debug); } 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("UDP", $"error processing the message {ex.Message}, {ex.StackTrace}", LogLevel.Error); } }
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); } }
async Task ListenAsync(CancellationToken cts) { try { var currentToken = new byte[2]; while (!cts.IsCancellationRequested) { var receivedResults = await udpClient.ReceiveAsync(); // If 4, it may mean we received a confirmation if (receivedResults.Buffer.Length >= 4) { var identifier = PhysicalPayload.GetIdentifierFromPayload(receivedResults.Buffer); currentToken[0] = receivedResults.Buffer[1]; currentToken[1] = receivedResults.Buffer[2]; var tokenKey = CreateTokenKey(currentToken, identifier); TestLogger.Log($"[PKTFORWARDER] Received {identifier.ToString()} with token {tokenKey}"); if (this.subscribers.TryGetValue(tokenKey, out var subscriber)) { subscriber(receivedResults.Buffer); this.subscribers.Remove(tokenKey, out _); } } } } catch (ObjectDisposedException) { } }
async Task RunUdpListener() { IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, PORT); this.udpClient = new UdpClient(endPoint); Logger.LogAlways($"LoRaWAN server started on port {PORT}"); while (true) { UdpReceiveResult receivedResults = await this.udpClient.ReceiveAsync(); var startTimeProcessing = DateTime.UtcNow; switch (PhysicalPayload.GetIdentifierFromPayload(receivedResults.Buffer)) { // In this case we have a keep-alive PULL_DATA packet we don't need to start the engine and can return immediately a response to the challenge case PhysicalIdentifier.PULL_DATA: if (this.pullAckRemoteLoRaAggregatorPort == 0) { this.pullAckRemoteLoRaAggregatorPort = receivedResults.RemoteEndPoint.Port; this.pullAckRemoteLoRaAddress = receivedResults.RemoteEndPoint.Address.ToString(); } this.SendAcknowledgementMessage(receivedResults, (int)PhysicalIdentifier.PULL_ACK, receivedResults.RemoteEndPoint); break; // This is a PUSH_DATA (upstream message). case PhysicalIdentifier.PUSH_DATA: this.SendAcknowledgementMessage(receivedResults, (int)PhysicalIdentifier.PUSH_ACK, receivedResults.RemoteEndPoint); this.DispatchMessages(receivedResults.Buffer, startTimeProcessing); break; // This is a ack to a transmission we did previously case PhysicalIdentifier.TX_ACK: if (receivedResults.Buffer.Length == 12) { Logger.Log( "UDP", $"packet with id {ConversionHelper.ByteArrayToString(receivedResults.Buffer.RangeSubset(1, 2))} successfully transmitted by the aggregator", LogLevel.Debug); } else { var logMsg = string.Format( "packet with id {0} had a problem to be transmitted over the air :{1}", receivedResults.Buffer.Length > 2 ? ConversionHelper.ByteArrayToString(receivedResults.Buffer.RangeSubset(1, 2)) : string.Empty, receivedResults.Buffer.Length > 12 ? Encoding.UTF8.GetString(receivedResults.Buffer.RangeSubset(12, receivedResults.Buffer.Length - 12)) : string.Empty); Logger.Log("UDP", logMsg, LogLevel.Error); } break; default: Logger.Log("UDP", "unknown packet type or length being received", LogLevel.Error); break; } } }
public DecodedLoraPacket DecodeMessage(string encodedPhysicalPayload) { // conver to hex var byteArray = Convert.FromBase64String(encodedPhysicalPayload); var hexValuesList = BitConverter.ToString(byteArray).Split("-").ToList(); // create packet DecodedLoraPacket decodedPacket = new DecodedLoraPacket() { OriginalHexString = string.Join("", hexValuesList) }; var physicalPayload = new PhysicalPayload { MacHeader = hexValuesList[0], MIC = string.Join("", hexValuesList).Substring((hexValuesList.Count * 2) - 8, 8) }; hexValuesList.RemoveRange(hexValuesList.Count - 4, 4); hexValuesList.RemoveAt(0); var frameHeader = new FrameHeader { DeviceAddress = hexValuesList[3] + hexValuesList[2] + hexValuesList[1] + hexValuesList[0], // big endian FrameControlOctet = hexValuesList[4], FrameCounter = hexValuesList[6] + hexValuesList[5] // big endian }; // frame options is optional, need to work out size / if its present using octet var binaryFrameControl = Convert.ToString(Convert.ToInt32(frameHeader.FrameControlOctet, 16), 2); var frameOptionsOffset = Convert.ToInt32(binaryFrameControl.Substring(binaryFrameControl.Length - 3, 3)); if (frameOptionsOffset != 0) { for (int i = 1; i <= frameOptionsOffset; i++) { frameHeader.FrameOptions += hexValuesList[6 + i]; } } // FramePayload var values = string.Join("", hexValuesList); var payloadOffset = 16 + (frameOptionsOffset * 2); var macPayload = new MacPayload { FramePort = hexValuesList[7 + frameOptionsOffset], FramePayload = values.Substring(payloadOffset, values.Length - payloadOffset), FrameHeader = frameHeader }; // combine structures physicalPayload.MacPayload = macPayload; decodedPacket.PhysicalPayload = physicalPayload; return(decodedPacket); }
public LoRaMessageWrapper(LoRaPayload payload, LoRaMessageType type, byte[] physicalToken, string datr, uint rfch, double freq, long tmst) { LoRaPayloadMessage = payload; PktFwdPayload = new DownlinkPktFwdMessage(Convert.ToBase64String(LoRaPayloadMessage.GetByteMessage()), datr, rfch, freq, tmst); var jsonMsg = JsonConvert.SerializeObject(PktFwdPayload); Logger.Log(ConversionHelper.ByteArrayToString(payload.GetLoRaMessage().DevEUI.ToArray()), $"{((MType)(payload.Mhdr.Span[0])).ToString()} {jsonMsg}", Logger.LoggingLevel.Full); var messageBytes = Encoding.Default.GetBytes(jsonMsg); PhysicalPayload = new PhysicalPayload(physicalToken, PhysicalIdentifier.PULL_RESP, messageBytes); }
private byte[] ProcessNonLoraMessage(LoRaMessageWrapper loraMessage) { byte[] udpMsgForPktForwarder = new byte[0]; if (loraMessage.PhysicalPayload.identifier == PhysicalIdentifier.PULL_DATA) { PhysicalPayload pullAck = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PULL_ACK, null); udpMsgForPktForwarder = pullAck.GetMessage(); } return(udpMsgForPktForwarder); }
internal async Task <PhysicalPayload> SendAsync(byte[] syncHeader, byte[] data) { var rxpkgateway = this.CreateMessagePacket(data); var msg = "{\"rxpk\":[" + rxpkgateway + "]}"; var gatewayInfo = Encoding.UTF8.GetBytes(msg); byte[] packetData = new byte[syncHeader.Length + gatewayInfo.Length]; Array.Copy(syncHeader, packetData, syncHeader.Length); Array.Copy(gatewayInfo, 0, packetData, syncHeader.Length, gatewayInfo.Length); var physicalPayload = new PhysicalPayload(packetData); await this.udpClient.SendAsync(packetData, packetData.Length, this.networkServerIPEndpoint); return(physicalPayload); }
async Task ListenAsync(CancellationToken cts) { try { var currentToken = new byte[2]; while (!cts.IsCancellationRequested) { var receivedResults = await this.udpClient.ReceiveAsync(); // If 4, it may mean we received a confirmation if (receivedResults.Buffer.Length >= 4) { var identifier = PhysicalPayload.GetIdentifierFromPayload(receivedResults.Buffer); currentToken[0] = receivedResults.Buffer[1]; currentToken[1] = receivedResults.Buffer[2]; TestLogger.Log($"[PKTFORWARDER] Received {identifier.ToString()}"); if (identifier == PhysicalIdentifier.PULL_RESP) { if (this.subscribers.Count > 0) { Func <byte[], bool> subscriberToRemove = null; foreach (var subscriber in this.subscribers) { if (subscriber(receivedResults.Buffer)) { subscriberToRemove = subscriber; break; } } if (subscriberToRemove != null) { this.subscribers.Remove(subscriberToRemove); } } } } } } catch (ObjectDisposedException) { } }
/// <summary> /// This contructor is used in case of downlink message /// </summary> /// <param name="inputMessage"></param> /// <param name="type"> /// 0 = Join Request /// 1 = Join Accept /// 2 = Unconfirmed Data up /// 3 = Unconfirmed Data down /// 4 = Confirmed Data up /// 5 = Confirmed Data down /// 6 = Rejoin Request</param> public LoRaMessageWrapper(LoRaPayload payload, LoRaMessageType type, byte[] physicalToken) { LoRaPayloadMessage = payload; // construct a Join Accept Message if (type == LoRaMessageType.JoinAccept) { var downlinkmsg = new DownlinkPktFwdMessage(Convert.ToBase64String(payload.GetByteMessage())); PktFwdPayload = downlinkmsg; var jsonMsg = JsonConvert.SerializeObject(downlinkmsg); var messageBytes = Encoding.Default.GetBytes(jsonMsg); PhysicalPayload = new PhysicalPayload(physicalToken, PhysicalIdentifier.PULL_RESP, messageBytes); } else if (type == LoRaMessageType.UnconfirmedDataDown) { throw new NotImplementedException(); } else if (type == LoRaMessageType.ConfirmedDataDown) { throw new NotImplementedException(); } }
async Task PushDataAsync(CancellationToken cts) { try { while (!cts.IsCancellationRequested) { var sync = new PhysicalPayload(GetRandomToken(), PhysicalIdentifier.PUSH_DATA, null); var data = sync.GetSyncHeader(this.MacAddress); await udpClient.SendAsync(data, data.Length, this.networkServerIPEndpoint); await Task.Delay(10000, cts); } } catch (TaskCanceledException) { } catch (Exception ex) { TestLogger.Log($"Error in {nameof(PushDataAsync)}. {ex.ToString()}"); } }
/// <summary> /// This method is used as part of Simulated device for testing purposes. /// </summary> /// <param name="inputMessage">The Input Message bytes.</param> /// <param name="appKey">The appKey.</param> public static Txpk CreateTxpk(byte[] inputMessage, string appKey) { PhysicalPayload physicalPayload = new PhysicalPayload(inputMessage, true); var payload = Encoding.UTF8.GetString(physicalPayload.Message); // deserialize for a downlink message // checkwith franc var payloadDownObject = JsonConvert.DeserializeObject <DownlinkPktFwdMessage>(payload); if (payloadDownObject != null) { if (payloadDownObject.Txpk != null) { return(payloadDownObject.Txpk); } else { Logger.Log("Error: " + payloadDownObject.Txpk, LogLevel.Error); } } return(null); }
public async Task RunServer() { mac = GetMacAddress(); Logger.Log("Starting LoRaWAN Simulator...", Logger.LoggingLevel.Always); if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SIMULATOR_PORT"))) { PORT = Convert.ToInt32(Environment.GetEnvironmentVariable("SIMULATOR_PORT")); Logger.Log($"Changing port to {PORT}", Logger.LoggingLevel.Always); } // Creating the endpoint IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, PORT); udpClient = new UdpClient(endPoint); Logger.Log($"LoRaWAN Simulator started on port {PORT}", Logger.LoggingLevel.Always); //send first sync _ = Task.Factory.StartNew(async() => { while (true) { var sync = new PhysicalPayload(GetRandomToken(), PhysicalIdentifier.PUSH_DATA, null); await UdpSendMessage(sync.GetSyncHeader(mac)); await Task.Delay(10000); } }); // Reading the test configuration string json = System.IO.File.ReadAllText(@"testconfig.json"); var theObjects = JsonConvert.DeserializeObject <JObject>(json); var rxpk = theObjects["rxpk"]; gateway = new GatewayDevice(rxpk.ToString()); var devices = theObjects["Devices"]; // TODO: Need to create simulated devices foreach (var device in devices) { SimulatedDevice simulated = new SimulatedDevice(device.ToString()); listDevices.Add(simulated); // create a new thread that will post content _ = Task.Factory.StartNew(async() => { DateTimeOffset dt = DateTimeOffset.Now; while (true) { if (dt.AddSeconds(simulated.Interval) < DateTimeOffset.Now) { dt = DateTimeOffset.Now; // send a message if (simulated.LoRaDevice.IsJoined) { simulated.LastPayload = new PhysicalPayload(GetRandomToken(), PhysicalIdentifier.PUSH_DATA, null); var header = simulated.LastPayload.GetSyncHeader(mac); var simdata = simulated.GetUnconfirmedDataUpMessage(); var rxpkgateway = gateway.GetMessage(simdata); var msg = "{\"rxpk\":[" + rxpkgateway + "]}"; var gat = Encoding.Default.GetBytes(msg); byte[] data = new byte[header.Length + gat.Length]; Array.Copy(header, data, header.Length); Array.Copy(gat, 0, data, header.Length, gat.Length); Logger.Log(simulated.LoRaDevice.DevAddr, $"Sending data: {BitConverter.ToString(header).Replace("-", "")}{Encoding.Default.GetString(gat)}", Logger.LoggingLevel.Always); await UdpSendMessage(data); } else { simulated.LastPayload = new PhysicalPayload(GetRandomToken(), PhysicalIdentifier.PUSH_DATA, null); var header = simulated.LastPayload.GetSyncHeader(mac); var join = simulated.GetJoinRequest(); var rxpkgateway = gateway.GetMessage(join); var msg = "{\"rxpk\":[" + rxpkgateway + "]}"; var gat = Encoding.Default.GetBytes(msg); byte[] data = new byte[header.Length + gat.Length]; Array.Copy(header, data, header.Length); Array.Copy(gat, 0, data, header.Length, gat.Length); await UdpSendMessage(data); } } } }); } // TODO: Need to start this in a thread await RunUdpListener(); }
async Task RunUdpListener() { IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, PORT); this.udpClient = new UdpClient(endPoint); Logger.LogAlways($"LoRaWAN server started on port {PORT}"); while (true) { UdpReceiveResult receivedResults = await this.udpClient.ReceiveAsync(); var startTimeProcessing = DateTime.UtcNow; // Logger.LogAlways($"UDP message received ({receivedResults.Buffer[3]}) from port: {receivedResults.RemoteEndPoint.Port} and IP: {receivedResults.RemoteEndPoint.Address.ToString()}"); switch (PhysicalPayload.GetIdentifierFromPayload(receivedResults.Buffer)) { // In this case we have a keep-alive PULL_DATA packet we don't need to start the engine and can return immediately a response to the challenge case PhysicalIdentifier.PULL_DATA: if (this.pullAckRemoteLoRaAggregatorPort == 0) { this.pullAckRemoteLoRaAggregatorPort = receivedResults.RemoteEndPoint.Port; } this.SendAcknowledgementMessage(receivedResults, (int)PhysicalIdentifier.PULL_ACK, receivedResults.RemoteEndPoint); break; // This is a PUSH_DATA (upstream message). case PhysicalIdentifier.PUSH_DATA: this.SendAcknowledgementMessage(receivedResults, (int)PhysicalIdentifier.PUSH_ACK, receivedResults.RemoteEndPoint); // Message processing runs in the background var remoteEndPointAddress = receivedResults.RemoteEndPoint.Address.ToString(); _ = Task.Run(async() => { List <Rxpk> messageRxpks = Rxpk.CreateRxpk(receivedResults.Buffer); if (messageRxpks != null) { if (messageRxpks.Count == 1) { await this.ProcessRxpkAsync(receivedResults.RemoteEndPoint.Address.ToString(), messageRxpks[0], startTimeProcessing); } else if (messageRxpks.Count > 1) { Task toWait = null; for (int i = 0; i < messageRxpks.Count; i++) { var t = this.ProcessRxpkAsync(remoteEndPointAddress, messageRxpks[i], startTimeProcessing); if (toWait == null) { toWait = t; } } await toWait; } } }); break; // This is a ack to a transmission we did previously case PhysicalIdentifier.TX_ACK: if (receivedResults.Buffer.Length == 12) { Logger.Log( "UDP", $"Packet with id {ConversionHelper.ByteArrayToString(receivedResults.Buffer.RangeSubset(1, 2))} successfully transmitted by the aggregator", LogLevel.Debug); } else { var logMsg = string.Format( "Packet with id {0} had a problem to be transmitted over the air :{1}", receivedResults.Buffer.Length > 2 ? ConversionHelper.ByteArrayToString(receivedResults.Buffer.RangeSubset(1, 2)) : string.Empty, receivedResults.Buffer.Length > 12 ? Encoding.UTF8.GetString(receivedResults.Buffer.RangeSubset(12, receivedResults.Buffer.Length - 12)) : string.Empty); Logger.Log("UDP", logMsg, LogLevel.Error); } break; default: Logger.Log("UDP", "Unknown packet type or length being received", LogLevel.Error); break; } } }
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); }
async Task RunUdpListener() { IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, PORT); udpClient = new UdpClient(endPoint); Logger.Log($"LoRaWAN server started on port {PORT}", Logger.LoggingLevel.Always); while (true) { UdpReceiveResult receivedResults = await udpClient.ReceiveAsync(); //Logger.Log($"UDP message received ({receivedResults.Buffer[3]}) from port: {receivedResults.RemoteEndPoint.Port} and IP: {receivedResults.RemoteEndPoint.Address.ToString()}",LoggingLevel.Always); switch (PhysicalPayload.GetIdentifierFromPayload(receivedResults.Buffer)) { //In this case we have a keep-alive PULL_DATA packet we don't need to start the engine and can return immediately a response to the challenge case PhysicalIdentifier.PULL_DATA: if (pullAckRemoteLoRaAggregatorPort == 0) { pullAckRemoteLoRaAggregatorPort = receivedResults.RemoteEndPoint.Port; } SendAcknowledgementMessage(receivedResults, (int)PhysicalIdentifier.PULL_ACK, receivedResults.RemoteEndPoint); break; //This is a PUSH_DATA (upstream message). case PhysicalIdentifier.PUSH_DATA: SendAcknowledgementMessage(receivedResults, (int)PhysicalIdentifier.PUSH_ACK, receivedResults.RemoteEndPoint); // Message processing runs in the background MessageProcessor messageProcessor = new MessageProcessor(this.configuration, this.loraDeviceInfoManager); // do not wait for it to return _ = Task.Run(async() => { try { var resultMessage = await messageProcessor.ProcessMessageAsync(receivedResults.Buffer); if (resultMessage != null && resultMessage.Length > 0) { var port = pullAckRemoteLoRaAggregatorPort; if (port <= 0) { port = receivedResults.RemoteEndPoint.Port; } await this.UdpSendMessage(resultMessage, receivedResults.RemoteEndPoint.Address.ToString(), port); } } catch (Exception ex) { Logger.Log($"Error processing the message {ex.Message}, {ex.StackTrace}", Logger.LoggingLevel.Error); } }); break; //This is a ack to a transmission we did previously case PhysicalIdentifier.TX_ACK: if (receivedResults.Buffer.Length == 12) { Logger.Log(String.Format("Packet with id {0} successfully transmitted by the aggregator", ConversionHelper.ByteArrayToString(receivedResults.Buffer.RangeSubset(1, 2))) , LoggingLevel.Full); } else { Logger.Log(String.Format("Packet with id {0} had a problem to be transmitted over the air :{1}", receivedResults.Buffer.Length > 2 ? ConversionHelper.ByteArrayToString(receivedResults.Buffer.RangeSubset(1, 2)) : "", receivedResults.Buffer.Length > 12 ? Encoding.UTF8.GetString(receivedResults.Buffer.RangeSubset(12, receivedResults.Buffer.Length - 12)) : "") , LoggingLevel.Error); } break; default: Logger.Log("Unknown packet type or length being received", LoggingLevel.Error); break; } #pragma warning restore CS4014 } }
private async Task <byte[]> ProcessJoinRequest(LoRaMessageWrapper loraMessage) { byte[] udpMsgForPktForwarder = new Byte[0]; LoraDeviceInfo joinLoraDeviceInfo; var joinReq = (LoRaPayloadJoinRequest)loraMessage.LoRaPayloadMessage; joinReq.DevEUI.Span.Reverse(); joinReq.AppEUI.Span.Reverse(); string devEui = ConversionHelper.ByteArrayToString(joinReq.DevEUI.ToArray()); string devNonce = ConversionHelper.ByteArrayToString(joinReq.DevNonce.ToArray()); Logger.Log(devEui, $"join request received", Logger.LoggingLevel.Info); //checking if this devnonce was already processed or the deveui was already refused Cache.TryGetValue(devEui, out joinLoraDeviceInfo); //check if join request is valid. //we have a join request in the cache if (joinLoraDeviceInfo != null) { //we query every time in case the device is turned one while not yet in the registry //it is not our device so ingore the join //if (!joinLoraDeviceInfo.IsOurDevice) //{ // Logger.Log(devEui, $"join request refused the device is not ours", Logger.LoggingLevel.Info); // return null; //} //is our device but the join was not valid if (!joinLoraDeviceInfo.IsJoinValid) { //if the devNonce is equal to the current it is a potential replay attck if (joinLoraDeviceInfo.DevNonce == devNonce) { Logger.Log(devEui, $"join request refused devNonce already used", Logger.LoggingLevel.Info); return(null); } //Check if the device is trying to join through the wrong gateway if (!String.IsNullOrEmpty(joinLoraDeviceInfo.GatewayID) && joinLoraDeviceInfo.GatewayID.ToUpper() != GatewayID.ToUpper()) { Logger.Log(devEui, $"trying to join not through its linked gateway, ignoring join request", Logger.LoggingLevel.Info); return(null); } } } joinLoraDeviceInfo = await LoraDeviceInfoManager.PerformOTAAAsync(GatewayID, devEui, ConversionHelper.ByteArrayToString(joinReq.AppEUI.ToArray()), devNonce, joinLoraDeviceInfo); if (joinLoraDeviceInfo != null && joinLoraDeviceInfo.IsJoinValid) { //TODO to be fixed by Mik //if (!loraMessage.LoRaPayloadMessage.CheckMic(joinLoraDeviceInfo.AppKey)) //{ // Logger.Log(devEui, $"join request MIC invalid", Logger.LoggingLevel.Info); // return null; //} //join request resets the frame counters joinLoraDeviceInfo.FCntUp = 0; joinLoraDeviceInfo.FCntDown = 0; //in this case it's too late, we need to break and awoid saving twins if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(RegionFactory.CurrentRegion.join_accept_delay2 * 1000)) { Logger.Log(devEui, $"processing of the join request took too long, sending no message", Logger.LoggingLevel.Info); var physicalResponse = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PUSH_ACK, null); return(physicalResponse.GetMessage()); } //update reported properties and frame Counter await joinLoraDeviceInfo.HubSender.UpdateReportedPropertiesOTAAasync(joinLoraDeviceInfo); byte[] appNonce = ConversionHelper.StringToByteArray(joinLoraDeviceInfo.AppNonce); byte[] netId = ConversionHelper.StringToByteArray(joinLoraDeviceInfo.NetId); byte[] devAddr = ConversionHelper.StringToByteArray(joinLoraDeviceInfo.DevAddr); string appKey = joinLoraDeviceInfo.AppKey; Array.Reverse(netId); Array.Reverse(appNonce); LoRaPayloadJoinAccept loRaPayloadJoinAccept = new LoRaPayloadJoinAccept( //NETID 0 / 1 is default test ConversionHelper.ByteArrayToString(netId), //todo add app key management appKey, //todo add device address management devAddr, appNonce, new byte[] { 0 }, new byte[] { 0 }, null ); var datr = RegionFactory.CurrentRegion.GetDownstreamDR(loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0]); uint rfch = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].rfch; double freq = RegionFactory.CurrentRegion.GetDownstreamChannel(loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0]); //set tmst for the normal case long tmst = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].tmst + RegionFactory.CurrentRegion.join_accept_delay1 * 1000000; ////in this case it's too late, we need to break //if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(RegionFactory.CurrentRegion.join_accept_delay2 * 1000)) //{ // Logger.Log(devEui, $"processing of the join request took too long, sending no message", Logger.LoggingLevel.Info); // var physicalResponse = new PhysicalPayload(loraMessage.PhysicalPayload.token, PhysicalIdentifier.PULL_RESP, null); // Logger.Log(devEui, $"processing time: {DateTime.UtcNow - startTimeProcessing}", Logger.LoggingLevel.Info); // return physicalResponse.GetMessage(); //} //in this case the second join windows must be used if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(RegionFactory.CurrentRegion.join_accept_delay1 * 1000 - 100)) { Logger.Log(devEui, $"processing of the join request took too long, using second join accept receive window", Logger.LoggingLevel.Info); tmst = loraMessage.PktFwdPayload.GetPktFwdMessage().Rxpks[0].tmst + RegionFactory.CurrentRegion.join_accept_delay2 * 1000000; if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("RX2_DATR"))) { Logger.Log(devEui, $"using standard second receive windows for join request", Logger.LoggingLevel.Info); //using EU fix DR for RX2 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 { Logger.Log(devEui, $"using custom second receive windows for join request", Logger.LoggingLevel.Info); freq = double.Parse(Environment.GetEnvironmentVariable("RX2_FREQ")); datr = Environment.GetEnvironmentVariable("RX2_DATR"); } } LoRaMessageWrapper joinAcceptMessage = new LoRaMessageWrapper(loRaPayloadJoinAccept, LoRaMessageType.JoinAccept, loraMessage.PhysicalPayload.token, datr, 0, freq, tmst); udpMsgForPktForwarder = joinAcceptMessage.PhysicalPayload.GetMessage(); //add to cache for processing normal messages. This awoids one additional call to the server. Cache.AddToCache(joinLoraDeviceInfo.DevAddr, joinLoraDeviceInfo); Logger.Log(devEui, $"join accept sent", Logger.LoggingLevel.Info); } else { Logger.Log(devEui, $"join request refused", Logger.LoggingLevel.Info); } Logger.Log(devEui, $"processing time: {DateTime.UtcNow - startTimeProcessing}", Logger.LoggingLevel.Info); return(udpMsgForPktForwarder); }
public async Task RunServer() { mac = GetMacAddress(); Logger.LogAlways("Starting LoRaWAN Simulator..."); if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SIMULATOR_PORT"))) { PORT = Convert.ToInt32(Environment.GetEnvironmentVariable("SIMULATOR_PORT")); Logger.LogAlways($"Changing port to {PORT}"); } // Creating the endpoint IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, PORT); udpClient = new UdpClient(endPoint); Logger.LogAlways($"LoRaWAN Simulator started on port {PORT}"); // send first sync _ = Task.Factory.StartNew(async() => { while (true) { var sync = new PhysicalPayload(GetRandomToken(), PhysicalIdentifier.PULL_DATA, null); await UdpSendMessage(sync.GetSyncHeader(mac)); await Task.Delay(10000); } }); // Reading the test configuration string json = System.IO.File.ReadAllText(@"testconfig.json"); var theObjects = JsonConvert.DeserializeObject <JObject>(json); var rxpk = theObjects["rxpk"]; this.gateway = new GatewayDevice(rxpk.ToString()); var devices = theObjects["Devices"]; // TODO: Need to create simulated devices foreach (var device in devices) { SimulatedDevice simulated = new SimulatedDevice(device.ToString()); this.listDevices.Add(simulated); // create a new thread that will post content _ = Task.Factory.StartNew(async() => { simulated.dt = DateTimeOffset.Now; while (true) { Random random = new Random(); var rand = (random.NextDouble() - 0.5) * simulated.RandomInterval; if (simulated.dt.AddSeconds(simulated.Interval + rand) < DateTimeOffset.Now) { // simulated.dt = DateTimeOffset.Now; // check if the device is part of a group // if yes, then find all the devices and make the loop all together // send all the messages together simulated.dt = DateTimeOffset.Now; List <SimulatedDevice> devgroup = new List <SimulatedDevice>(); if (simulated.GroupRxpk != 0) { devgroup = this.listDevices.Where(x => x.GroupRxpk == simulated.GroupRxpk).ToList(); } else { devgroup.Add(simulated); } // Debug.WriteLine(JsonConvert.SerializeObject(devgroup)); var msg = "{\"rxpk\":["; var devicetosend = string.Empty; foreach (var simul in devgroup) { byte[] tosend; simul.LastPayload = new PhysicalPayload(GetRandomToken(), PhysicalIdentifier.PUSH_DATA, null); if (simul.LoRaDevice.IsJoined) { tosend = simul.GetUnconfirmedDataUpMessage(); } else { // simulated.LastPayload = new PhysicalPayload(GetRandomToken(), PhysicalIdentifier.PUSH_DATA, null); // var header = simulated.LastPayload.GetSyncHeader(mac); tosend = simul.GetJoinRequest(); // var rxpkgateway = gateway.GetMessage(tosend); // var msg = "{\"rxpk\":[" + rxpkgateway + "]}"; // var gat = Encoding.Default.GetBytes(msg); // byte[] data = new byte[header.Length + gat.Length]; // Array.Copy(header, data, header.Length); // Array.Copy(gat, 0, data, header.Length, gat.Length); // await UdpSendMessage(data); } var rxpkgateway = this.gateway.GetMessage(tosend); msg += rxpkgateway + ","; devicetosend += simul.LoRaDevice.DevEUI + ","; simul.dt = DateTimeOffset.Now; } byte[] header = simulated.LastPayload.GetSyncHeader(mac); // get rid of the the last "," msg = msg.Substring(0, msg.Length - 1); msg += "]}"; devicetosend = devicetosend.Substring(0, devicetosend.Length - 1); var gat = Encoding.Default.GetBytes(msg); byte[] data = new byte[header.Length + gat.Length]; Array.Copy(header, data, header.Length); Array.Copy(gat, 0, data, header.Length, gat.Length); await UdpSendMessage(data); Logger.LogAlways(devicetosend, $"Sending data: {BitConverter.ToString(header).Replace("-", string.Empty)}{Encoding.Default.GetString(gat)}"); } } }); } // TODO: Need to start this in a thread await this.RunUdpListener(); }
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[]> ProcessJoinRequest(LoRaMessage loraMessage) { byte[] udpMsgForPktForwarder = new Byte[0]; LoraDeviceInfo joinLoraDeviceInfo; var joinReq = (LoRaPayloadJoinRequest)loraMessage.payloadMessage; Array.Reverse(joinReq.devEUI); Array.Reverse(joinReq.appEUI); string devEui = BitConverter.ToString(joinReq.devEUI).Replace("-", ""); string devNonce = BitConverter.ToString(joinReq.devNonce).Replace("-", ""); Logger.Log(devEui, $"join request received", Logger.LoggingLevel.Info); //checking if this devnonce was already processed or the deveui was already refused Cache.TryGetValue(devEui, out joinLoraDeviceInfo); //we have a join request in the cache if (joinLoraDeviceInfo != null) { //it is not our device so ingore the join if (!joinLoraDeviceInfo.IsOurDevice) { Logger.Log(devEui, $"join request refused the device is not ours", Logger.LoggingLevel.Info); return(null); } //is our device but the join was not valid else if (!joinLoraDeviceInfo.IsJoinValid) { //if the devNonce is equal to the current it is a potential replay attck if (joinLoraDeviceInfo.DevNonce == devNonce) { Logger.Log(devEui, $"join request refused devNonce already used", Logger.LoggingLevel.Info); return(null); } //Check if the device is trying to join through the wrong gateway if (!String.IsNullOrEmpty(joinLoraDeviceInfo.GatewayID) && joinLoraDeviceInfo.GatewayID.ToUpper() != GatewayID.ToUpper()) { Logger.Log(devEui, $"trying to join not through its linked gateway, ignoring join request", Logger.LoggingLevel.Info); return(null); } } } joinLoraDeviceInfo = await LoraDeviceInfoManager.PerformOTAAAsync(GatewayID, devEui, BitConverter.ToString(joinReq.appEUI).Replace("-", ""), devNonce); if (joinLoraDeviceInfo.IsJoinValid) { byte[] appNonce = StringToByteArray(joinLoraDeviceInfo.AppNonce); byte[] netId = StringToByteArray(joinLoraDeviceInfo.NetId); byte[] devAddr = StringToByteArray(joinLoraDeviceInfo.DevAddr); string appKey = joinLoraDeviceInfo.AppKey; Array.Reverse(netId); Array.Reverse(appNonce); LoRaPayloadJoinAccept loRaPayloadJoinAccept = new LoRaPayloadJoinAccept( //NETID 0 / 1 is default test BitConverter.ToString(netId).Replace("-", ""), //todo add app key management appKey, //todo add device address management devAddr, appNonce ); 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; //set tmst for the normal case long _tmst = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].tmst + 5000000; //uncomment to force second windows usage //Thread.Sleep(4600-(int)(DateTime.Now - startTimeProcessing).TotalMilliseconds); //in this case it's too late, we need to break if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(6000)) { Logger.Log(devEui, $"processing of the join request took too long, sending no message", Logger.LoggingLevel.Info); var physicalResponse = new PhysicalPayload(loraMessage.physicalPayload.token, PhysicalIdentifier.PULL_RESP, null); return(physicalResponse.GetMessage()); } //in this case the second join windows must be used else if ((DateTime.UtcNow - startTimeProcessing) > TimeSpan.FromMilliseconds(4500)) { Logger.Log(devEui, $"processing of the join request took too long, using second join accept receive window", Logger.LoggingLevel.Info); _tmst = ((UplinkPktFwdMessage)loraMessage.loraMetadata.fullPayload).rxpk[0].tmst + 6000000; if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("RX2_DATR"))) { Logger.Log(devEui, $"using standard second receive windows for join request", 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 { Logger.Log(devEui, $"using custom DR second receive windows for join request", Logger.LoggingLevel.Info); _freq = double.Parse(Environment.GetEnvironmentVariable("RX2_FREQ")); _datr = Environment.GetEnvironmentVariable("RX2_DATR"); } } LoRaMessage joinAcceptMessage = new LoRaMessage(loRaPayloadJoinAccept, LoRaMessageType.JoinAccept, loraMessage.physicalPayload.token, _datr, 0, _freq, _tmst); udpMsgForPktForwarder = joinAcceptMessage.physicalPayload.GetMessage(); joinLoraDeviceInfo.HubSender = new IoTHubSender(joinLoraDeviceInfo.DevEUI, joinLoraDeviceInfo.PrimaryKey); //open the connection to iot hub without waiting for perf optimization in case of the device start sending a msg just after join request _ = joinLoraDeviceInfo.HubSender.OpenAsync(); //join request resets the frame counters joinLoraDeviceInfo.FCntUp = 0; joinLoraDeviceInfo.FCntDown = 0; //update the frame counter on the server _ = joinLoraDeviceInfo.HubSender.UpdateFcntAsync(joinLoraDeviceInfo.FCntUp, joinLoraDeviceInfo.FCntDown); //add to cache for processing normal messages. This awoids one additional call to the server. Cache.AddToCache(joinLoraDeviceInfo.DevAddr, joinLoraDeviceInfo); Logger.Log(devEui, $"join accept sent", Logger.LoggingLevel.Info); } //add to cache to avoid replay attack, btw server side does the check too. Cache.AddToCache(devEui, joinLoraDeviceInfo); return(udpMsgForPktForwarder); }
/// <summary> /// This contructor is used in case of uplink message, hence we don't know the message type yet /// </summary> /// <param name="inputMessage"></param> public LoRaMessageWrapper(byte[] inputMessage, bool server = false, string AppKey = "") { // packet normally sent by the gateway as heartbeat. TODO find more elegant way to integrate. PhysicalPayload = new PhysicalPayload(inputMessage, server); if (PhysicalPayload.message != null) { var payload = Encoding.Default.GetString(PhysicalPayload.message); // todo ronnie implement a better logging by message type if (!payload.StartsWith("{\"stat")) { Logger.Log($"Physical dataUp {payload}", Logger.LoggingLevel.Full); // Deserialized for uplink messages var payloadObject = JsonConvert.DeserializeObject <UplinkPktFwdMessage>(payload); PktFwdPayload = payloadObject; // set up the parts of the raw message // status message if (PktFwdPayload != null) { // if there is no packet, then it maybe a downlink message if (PktFwdPayload.GetPktFwdMessage().Rxpks.Count > 0) { if (PktFwdPayload.GetPktFwdMessage().Rxpks[0].data != null) { byte[] convertedInputMessage = Convert.FromBase64String(PktFwdPayload.GetPktFwdMessage().Rxpks[0].data); var messageType = convertedInputMessage[0] >> 5; LoRaMessageType = (LoRaMessageType)messageType; // Uplink Message if (messageType == (int)LoRaMessageType.UnconfirmedDataUp) { LoRaPayloadMessage = new LoRaPayloadData(convertedInputMessage); } else if (messageType == (int)LoRaMessageType.ConfirmedDataUp) { LoRaPayloadMessage = new LoRaPayloadData(convertedInputMessage); } else if (messageType == (int)LoRaMessageType.JoinRequest) { LoRaPayloadMessage = new LoRaPayloadJoinRequest(convertedInputMessage); } IsLoRaMessage = true; } } else { // deselrialize for a downlink message var payloadDownObject = JsonConvert.DeserializeObject <DownlinkPktFwdMessage>(payload); if (payloadDownObject != null) { if (payloadDownObject.txpk != null) { // if we have data, it is a downlink message if (payloadDownObject.txpk.data != null) { byte[] convertedInputMessage = Convert.FromBase64String(payloadDownObject.txpk.data); var messageType = convertedInputMessage[0] >> 5; LoRaMessageType = (LoRaMessageType)messageType; if (messageType == (int)LoRaMessageType.JoinAccept) { LoRaPayloadMessage = new LoRaPayloadJoinAccept(convertedInputMessage, AppKey); } IsLoRaMessage = true; } } else { Logger.Log("Error: " + payload, Logger.LoggingLevel.Full); } } } } } else { Logger.Log($"Statistic: {payload}", Logger.LoggingLevel.Full); IsLoRaMessage = false; } } else { IsLoRaMessage = false; } }