/// <summary> /// Method to update reported properties at OTAA time /// </summary> /// <param name="loraDeviceInfo"> the LoRa info to report</param> public async Task <bool> UpdateReportedPropertiesOTAAasync(LoraDeviceInfo loraDeviceInfo) { try { CreateDeviceClient(); deviceClient.OperationTimeoutInMilliseconds = 4000; setRetry(true); Logger.Log(DevEUI, $"saving join properties twins", Logger.LoggingLevel.Full); TwinCollection reportedProperties = new TwinCollection(); reportedProperties["NwkSKey"] = loraDeviceInfo.NwkSKey; reportedProperties["AppSKey"] = loraDeviceInfo.AppSKey; reportedProperties["DevEUI"] = loraDeviceInfo.DevEUI; reportedProperties["DevAddr"] = loraDeviceInfo.DevAddr; reportedProperties["NetId"] = loraDeviceInfo.NetId; reportedProperties["FCntUp"] = loraDeviceInfo.FCntUp; reportedProperties["FCntDown"] = loraDeviceInfo.FCntDown; reportedProperties["DevNonce"] = loraDeviceInfo.DevNonce; await deviceClient.UpdateReportedPropertiesAsync(reportedProperties); Logger.Log(DevEUI, $"done saving join properties twins", Logger.LoggingLevel.Full); setRetry(false); return(true); } catch (Exception ex) { Logger.Log(DevEUI, $"could not save twins with error: {ex.Message}", Logger.LoggingLevel.Error); return(false); } }
public static void AddToCache(string devAddr, LoraDeviceInfo loraDeviceInfo) { using (var entry = Cache.MemoryCache.CreateEntry(devAddr)) { entry.Value = loraDeviceInfo; entry.SlidingExpiration = new TimeSpan(1, 0, 0, 0); } }
public static bool TryGetJoinRequestValue(string key, out LoraDeviceInfo loraDeviceInfo) { if (Cache.MemoryCache.TryGetValue(key, out object loraDeviceInfoCache)) { loraDeviceInfo = (LoraDeviceInfo)loraDeviceInfoCache; return(true); } else { loraDeviceInfo = null; return(false); } }
public static void AddRequestToCache(string devAddr, LoraDeviceInfo deviceInfo) { var concurrentdict = Cache.MemoryCache.GetOrCreate (devAddr, entry => { entry.SlidingExpiration = new TimeSpan(1, 0, 0, 0); return(new ConcurrentDictionary <string, LoraDeviceInfo>() { }); }); concurrentdict.GetOrAdd(deviceInfo.DevEUI, devinf => { return(deviceInfo); }); }
public static async Task <LoraDeviceInfo> PerformOTAAAsync(string GatewayID, string DevEUI, string AppEUI, string DevNonce) { var client = new HttpClient(); var url = $"{FacadeServerUrl}PerformOTAA?code={FacadeAuthCode}&GatewayID={GatewayID}&DevEUI={DevEUI}&DevNonce={DevNonce}&AppEUI={AppEUI}"; HttpResponseMessage response = await client.GetAsync(url); if (!response.IsSuccessStatusCode) { Console.WriteLine($"Error calling façade api: {response.ReasonPhrase} check the azure function log"); return(null); } var result = response.Content.ReadAsStringAsync().Result; LoraDeviceInfo loraDeviceInfo = (LoraDeviceInfo)JsonConvert.DeserializeObject(result, typeof(LoraDeviceInfo)); return(loraDeviceInfo); }
public static async Task <LoraDeviceInfo> GetLoraDeviceInfoAsync(string DevAddr) { var client = new HttpClient(); var url = $"{FacadeServerUrl}GetNwkSKeyAppSKey?code={FacadeAuthCode}&devAddr={DevAddr}"; HttpResponseMessage response = await client.GetAsync(url); if (!response.IsSuccessStatusCode) { Console.WriteLine($"Error calling façade api: {response.ReasonPhrase} check the azure function log"); return(null); } var result = response.Content.ReadAsStringAsync().Result; LoraDeviceInfo loraDeviceInfo = (LoraDeviceInfo)JsonConvert.DeserializeObject(result, typeof(LoraDeviceInfo)); return(loraDeviceInfo); }
// Validate cloud to device message private bool ValidateCloudToDeviceMessage(LoraDeviceInfo loraDeviceInfo, Message c2dMessage) { // ensure fport property has been set if (!c2dMessage.Properties.TryGetValueCaseInsensitive(FPORT_MSG_PROPERTY_KEY, out var fportValue)) { Logger.Log(loraDeviceInfo.DevEUI, $"missing {FPORT_MSG_PROPERTY_KEY} property in C2D message '{c2dMessage.MessageId}'", Logger.LoggingLevel.Error); return(false); } if (byte.TryParse(fportValue, out var fport)) { // ensure fport follows LoRa specification // 0 => reserved for mac commands // 224+ => reserved for future applications if (fport != LORA_FPORT_RESERVED_MAC_MSG && fport < LORA_FPORT_RESERVED_FUTURE_START) { return(true); } } Logger.Log(loraDeviceInfo.DevEUI, $"invalid fport '{fportValue}' in C2D message '{c2dMessage.MessageId}'", Logger.LoggingLevel.Error); return(false); }
public async Task <LoraDeviceInfo> GetLoraDeviceInfoAsync(string DevAddr, string GatewayId, LoRaTools.LoRaMessage.LoRaMessageWrapper loraMessage) { var client = this.serviceFacadeHttpClientProvider.GetHttpClient(); var url = $"{FacadeServerUrl}GetDevice?code={FacadeAuthCode}&DevAddr={DevAddr}&GatewayId={GatewayId}"; HttpResponseMessage response = await client.GetAsync(url); if (!response.IsSuccessStatusCode) { Logger.Log(DevAddr, $"error calling façade api: {response.ReasonPhrase} check the azure function log", Logger.LoggingLevel.Error); return(null); } var result = await response.Content.ReadAsStringAsync(); //TODO enable multi devices with the same devaddr List <IoTHubDeviceInfo> iotHubDeviceInfos = ((List <IoTHubDeviceInfo>)JsonConvert.DeserializeObject(result, typeof(List <IoTHubDeviceInfo>))); LoraDeviceInfo loraDeviceInfo = new LoraDeviceInfo(); loraDeviceInfo.DevAddr = DevAddr; //we did not find a device with this devaddr so we assume is not ours if (iotHubDeviceInfos.Count == 0) { loraDeviceInfo.IsOurDevice = false; } else { foreach (IoTHubDeviceInfo iotHubDeviceInfo in iotHubDeviceInfos) { loraDeviceInfo.DevEUI = iotHubDeviceInfo.DevEUI; loraDeviceInfo.PrimaryKey = iotHubDeviceInfo.PrimaryKey; loraDeviceInfo.HubSender = new IoTHubConnector(iotHubDeviceInfo.DevEUI, iotHubDeviceInfo.PrimaryKey, this.configuration); var twin = await loraDeviceInfo.HubSender.GetTwinAsync(); if (twin != null) { //ABP Case if (twin.Properties.Desired.Contains("AppSKey")) { loraDeviceInfo.AppSKey = twin.Properties.Desired["AppSKey"]; loraDeviceInfo.NwkSKey = twin.Properties.Desired["NwkSKey"]; loraDeviceInfo.DevAddr = twin.Properties.Desired["DevAddr"]; //in this case it is not the correct device we would need to iterate if (!loraMessage.CheckMic(loraDeviceInfo.NwkSKey)) { break; } } //OTAA Case else if (twin.Properties.Reported.Contains("AppSKey")) { loraDeviceInfo.AppSKey = twin.Properties.Reported["AppSKey"]; loraDeviceInfo.NwkSKey = twin.Properties.Reported["NwkSKey"]; loraDeviceInfo.DevAddr = twin.Properties.Reported["DevAddr"]; loraDeviceInfo.DevNonce = twin.Properties.Reported["DevNonce"]; //todo check if appkey and appeui is needed in the flow loraDeviceInfo.AppEUI = twin.Properties.Desired["AppEUI"]; loraDeviceInfo.AppKey = twin.Properties.Desired["AppKey"]; //in this case it is not the correct device we would need to iterate if (!loraMessage.CheckMic(loraDeviceInfo.NwkSKey)) { break; } } else { Logger.Log(loraDeviceInfo.DevEUI, $"AppSKey not present neither in Desired or in Reported properties", Logger.LoggingLevel.Error); } if (twin.Properties.Desired.Contains("GatewayID")) { loraDeviceInfo.GatewayID = twin.Properties.Desired["GatewayID"]; } if (twin.Properties.Desired.Contains("SensorDecoder")) { loraDeviceInfo.SensorDecoder = twin.Properties.Desired["SensorDecoder"]; } loraDeviceInfo.IsOurDevice = true; if (twin.Properties.Reported.Contains("FCntUp")) { loraDeviceInfo.FCntUp = twin.Properties.Reported["FCntUp"]; } if (twin.Properties.Reported.Contains("FCntDown")) { loraDeviceInfo.FCntDown = twin.Properties.Reported["FCntDown"]; } Logger.Log(loraDeviceInfo.DevEUI, $"done getting twins", Logger.LoggingLevel.Info); return(loraDeviceInfo); } } Logger.Log(loraDeviceInfo.DevEUI, $"an exact matching device could not be found on IoT Hub", Logger.LoggingLevel.Info); } return(new LoraDeviceInfo() { DevAddr = DevAddr }); }
/// <summary> /// Code Performing the OTAA /// </summary> /// <param name="GatewayID"></param> /// <param name="DevEUI"></param> /// <param name="AppEUI"></param> /// <param name="DevNonce"></param> /// <returns></returns> public async Task <LoraDeviceInfo> PerformOTAAAsync(string GatewayID, string DevEUI, string AppEUI, string DevNonce, LoraDeviceInfo joinLoraDeviceInfo) { string AppSKey; string NwkSKey; string DevAddr; string AppNonce; IoTHubDeviceInfo iotHubDeviceInfo; if (DevEUI == null || AppEUI == null || DevNonce == null) { string errorMsg = "Missing devEUI/AppEUI/DevNonce in the OTAARequest"; //log.Error(errorMsg); Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Error); return(null); } if (joinLoraDeviceInfo == null) { joinLoraDeviceInfo = new LoraDeviceInfo(); } joinLoraDeviceInfo.DevEUI = DevEUI; ////we don't have the key to access iot hub query the registry //if (joinLoraDeviceInfo.PrimaryKey == null) //{ Logger.Log(DevEUI, $"querying the registry for device key", Logger.LoggingLevel.Info); var client = this.serviceFacadeHttpClientProvider.GetHttpClient(); var url = String.Concat($"{FacadeServerUrl}GetDevice?", $"{FacadeAuthCode}" == "" ? "" : $"code={FacadeAuthCode}&", $"DevEUI={DevEUI}&DevNonce={DevNonce}&GatewayId={GatewayID}"); HttpResponseMessage response = await client.GetAsync(url); if (!response.IsSuccessStatusCode) { if (response.StatusCode == System.Net.HttpStatusCode.BadRequest) { var badReqResult = await response.Content.ReadAsStringAsync(); if (!String.IsNullOrEmpty(badReqResult) && badReqResult == "UsedDevNonce") { Logger.Log(DevEUI, $"DevNonce already used by this device", Logger.LoggingLevel.Info); return(null); } } Logger.Log(DevEUI, $"error calling façade api, check the azure function log. {response.ReasonPhrase}", Logger.LoggingLevel.Error); return(null); } var result = await response.Content.ReadAsStringAsync(); List <IoTHubDeviceInfo> iotHubDeviceInfos = ((List <IoTHubDeviceInfo>)JsonConvert.DeserializeObject(result, typeof(List <IoTHubDeviceInfo>))); if (iotHubDeviceInfos.Count == 0) { joinLoraDeviceInfo.IsJoinValid = false; joinLoraDeviceInfo.IsOurDevice = false; return(joinLoraDeviceInfo); } else { iotHubDeviceInfo = iotHubDeviceInfos[0]; joinLoraDeviceInfo.PrimaryKey = iotHubDeviceInfo.PrimaryKey; } //} joinLoraDeviceInfo.HubSender = new IoTHubConnector(joinLoraDeviceInfo.DevEUI, joinLoraDeviceInfo.PrimaryKey, this.configuration); //we don't have yet the twin data so we need to get it if (joinLoraDeviceInfo.AppKey == null || joinLoraDeviceInfo.AppEUI == null) { Logger.Log(DevEUI, $"getting twins for OTAA for device", Logger.LoggingLevel.Info); var twin = await joinLoraDeviceInfo.HubSender.GetTwinAsync(); if (twin != null) { joinLoraDeviceInfo.IsOurDevice = true; if (!twin.Properties.Desired.Contains("AppEUI")) { string errorMsg = $"missing AppEUI for OTAA for device"; Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Error); return(null); } else { joinLoraDeviceInfo.AppEUI = twin.Properties.Desired["AppEUI"].Value; } //Make sure that there is the AppKey if not we cannot do the OTAA if (!twin.Properties.Desired.Contains("AppKey")) { string errorMsg = $"missing AppKey for OTAA for device"; Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Error); return(null); } else { joinLoraDeviceInfo.AppKey = twin.Properties.Desired["AppKey"].Value; } //Make sure that is a new request and not a replay if (twin.Properties.Reported.Contains("DevNonce")) { joinLoraDeviceInfo.DevNonce = twin.Properties.Reported["DevNonce"]; } if (twin.Properties.Desired.Contains("GatewayID")) { joinLoraDeviceInfo.GatewayID = twin.Properties.Desired["GatewayID"].Value; } if (twin.Properties.Desired.Contains("SensorDecoder")) { joinLoraDeviceInfo.SensorDecoder = twin.Properties.Desired["SensorDecoder"].Value; } Logger.Log(DevEUI, $"done getting twins for OTAA device", Logger.LoggingLevel.Info); } else { Logger.Log(DevEUI, $"failed getting twins for OTAA device", Logger.LoggingLevel.Error); return(null); } } else { Logger.Log(DevEUI, $"using cached twins for OTAA device", Logger.LoggingLevel.Info); } //We add it to the cache so the next join has already the data, important for offline Cache.AddJoinRequestToCache(DevEUI, joinLoraDeviceInfo); //Make sure that there is the AppEUI and it matches if not we cannot do the OTAA if (joinLoraDeviceInfo.AppEUI != AppEUI) { string errorMsg = $"AppEUI for OTAA does not match for device"; Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Error); return(null); } //Make sure that is a new request and not a replay if (!String.IsNullOrEmpty(joinLoraDeviceInfo.DevNonce) && joinLoraDeviceInfo.DevNonce == DevNonce) { string errorMsg = $"DevNonce already used by this device"; Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Info); joinLoraDeviceInfo.IsJoinValid = false; return(joinLoraDeviceInfo); } //Check that the device is joining through the linked gateway and not another if (!String.IsNullOrEmpty(joinLoraDeviceInfo.GatewayID) && joinLoraDeviceInfo.GatewayID.ToUpper() != GatewayID.ToUpper()) { string errorMsg = $"not the right gateway device-gateway:{joinLoraDeviceInfo.GatewayID} current-gateway:{GatewayID}"; Logger.Log(DevEUI, errorMsg, Logger.LoggingLevel.Info); joinLoraDeviceInfo.IsJoinValid = false; return(joinLoraDeviceInfo); } byte[] netId = new byte[3] { 0, 0, 1 }; AppNonce = OTAAKeysGenerator.getAppNonce(); AppSKey = OTAAKeysGenerator.calculateKey(new byte[1] { 0x02 }, ConversionHelper.StringToByteArray(AppNonce), netId, ConversionHelper.StringToByteArray(DevNonce), ConversionHelper.StringToByteArray(joinLoraDeviceInfo.AppKey)); NwkSKey = OTAAKeysGenerator.calculateKey(new byte[1] { 0x01 }, ConversionHelper.StringToByteArray(AppNonce), netId, ConversionHelper.StringToByteArray(DevNonce), ConversionHelper.StringToByteArray(joinLoraDeviceInfo.AppKey));; DevAddr = OTAAKeysGenerator.getDevAddr(netId); joinLoraDeviceInfo.DevAddr = DevAddr; joinLoraDeviceInfo.NwkSKey = NwkSKey; joinLoraDeviceInfo.AppSKey = AppSKey; joinLoraDeviceInfo.AppNonce = AppNonce; joinLoraDeviceInfo.DevNonce = DevNonce; joinLoraDeviceInfo.NetId = ConversionHelper.ByteArrayToString(netId); //Accept the JOIN Request and the futher messages joinLoraDeviceInfo.IsJoinValid = true; return(joinLoraDeviceInfo); }
public static void TryGetValue(string key, out LoraDeviceInfo loraDeviceInfo) { Cache.MemoryCache.TryGetValue(key, out object loraDeviceInfoCache); loraDeviceInfo = (LoraDeviceInfo)loraDeviceInfoCache; }
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; LoraDeviceInfo loraDeviceInfo = null; if (Cache.TryGetRequestValue(devAddr, out ConcurrentDictionary <string, LoraDeviceInfo> loraDeviceInfoCacheList)) { loraDeviceInfo = loraDeviceInfoCacheList.Values.FirstOrDefault(x => x.NwkSKey != null?loraMessage.CheckMic(x.NwkSKey):false); } if (loraDeviceInfo == null) { loraDeviceInfo = await this.loraDeviceInfoManager.GetLoraDeviceInfoAsync(devAddr, this.configuration.GatewayID, loraMessage); if (loraDeviceInfo != null && loraDeviceInfo.DevEUI != null) { if (loraDeviceInfo.DevEUI != null) { Logger.Log(loraDeviceInfo.DevEUI, $"processing message, device not in cache", Logger.LoggingLevel.Info); } if (loraDeviceInfo.IsOurDevice) { Cache.AddRequestToCache(devAddr, loraDeviceInfo); } } } else { Logger.Log(devAddr, $"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) || string.Compare(loraDeviceInfo.GatewayID, this.configuration.GatewayID, ignoreCase: true) == 0) { if (loraDeviceInfo.HubSender == null) { loraDeviceInfo.HubSender = new IoTHubConnector(loraDeviceInfo.DevEUI, loraDeviceInfo.PrimaryKey, this.configuration); } 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 this.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; // ACK from device or no decoder set in LoRa Device Twin. Simply return decryptedMessage if (isAckFromDevice || String.IsNullOrEmpty(loraDeviceInfo.SensorDecoder)) { if (String.IsNullOrEmpty(loraDeviceInfo.SensorDecoder)) { Logger.Log(loraDeviceInfo.DevEUI, $"no decoder set in device twin. port: {fportUp}", Logger.LoggingLevel.Full); } jsonDataPayload = Convert.ToBase64String(decryptedMessage); fullPayload.data = jsonDataPayload; } // Decoder is set in LoRa Device Twin. Send decrpytedMessage (payload) and fportUp (fPort) to decoder. else { Logger.Log(loraDeviceInfo.DevEUI, $"decoding with: {loraDeviceInfo.SensorDecoder} port: {fportUp}", Logger.LoggingLevel.Full); fullPayload.data = await LoraDecoders.DecodeMessage(decryptedMessage, fportUp, loraDeviceInfo.SensorDecoder); } fullPayload.eui = loraDeviceInfo.DevEUI; fullPayload.gatewayid = this.configuration.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 = string.Empty; if (fullPayload.data is JValue jvalue) { fullPayloadAsString = jvalue.ToString(); } else if (fullPayload.data is JObject jobject) { fullPayloadAsString = jobject.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); } byte[] fctrl = new byte[1] { 0 }; //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)) { fctrl[0] = (int)FctrlEnum.Ack; ushort newFCntDown = await this.loraDeviceInfoManager.NextFCntDown(loraDeviceInfo.DevEUI, loraDeviceInfo.FCntDown, fcntup, this.configuration.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 { 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(null); } } //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(500)); if (c2dMsg != null && !ValidateCloudToDeviceMessage(loraDeviceInfo, c2dMsg)) { _ = loraDeviceInfo.HubSender.CompleteAsync(c2dMsg); c2dMsg = null; } byte[] bytesC2dMsg = null; byte[] fport = null; //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(40)); 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[0] += (int)FctrlEnum.FpendingOrClassB; Logger.Log(loraDeviceInfo.DevEUI, $"Additional C2D messages waiting, setting FPending to 1", Logger.LoggingLevel.Info); } bytesC2dMsg = c2dMsg.GetBytes(); //default fport 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) { if (loraMessage.LoRaMessageType == LoRaMessageType.ConfirmedDataUp) { fctrl[0] += (int)FctrlEnum.Ack; } //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 this.loraDeviceInfoManager.NextFCntDown(loraDeviceInfo.DevEUI, loraDeviceInfo.FCntDown, fcntup, this.configuration.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 { 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(null); } } 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)) { if (string.IsNullOrEmpty(configuration.Rx2DataRate)) { 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 = configuration.Rx2DataFrequency; datr = configuration.Rx2DataRate; 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; byte[] rndToken = new byte[2]; Random rnd = new Random(); rnd.NextBytes(rndToken); //check if the c2d message has a mac command byte[] macbytes = null; if (c2dMsg != null) { if (c2dMsg.Properties.TryGetValueCaseInsensitive("cidtype", out var cidTypeValue)) { Logger.Log(loraDeviceInfo.DevEUI, $"Cloud to device MAC command received", Logger.LoggingLevel.Info); MacCommandHolder macCommandHolder = new MacCommandHolder(Convert.ToByte(cidTypeValue)); macbytes = macCommandHolder.macCommand[0].ToBytes(); } if (c2dMsg.Properties.TryGetValueCaseInsensitive("confirmed", out var confirmedValue) && confirmedValue.Equals("true", StringComparison.OrdinalIgnoreCase)) { requestForConfirmedResponse = true; Logger.Log(loraDeviceInfo.DevEUI, $"Cloud to device message requesting a confirmation", Logger.LoggingLevel.Info); } if (c2dMsg.Properties.TryGetValueCaseInsensitive("fport", out var fPortValue)) { int fportint = int.Parse(fPortValue); fport[0] = BitConverter.GetBytes(fportint)[0]; Logger.Log(loraDeviceInfo.DevEUI, $"Cloud to device message with a Fport of " + fPortValue, Logger.LoggingLevel.Info); } Logger.Log(loraDeviceInfo.DevEUI, String.Format("Sending a downstream message with ID {0}", ConversionHelper.ByteArrayToString(rndToken)), Logger.LoggingLevel.Full); } if (requestForConfirmedResponse) { fctrl[0] += (int)FctrlEnum.FpendingOrClassB; } 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); //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 { //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) { ////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] { 0 }; 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, $"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 ?? devAddr, $"processing time: {DateTime.UtcNow - startTimeProcessing}", Logger.LoggingLevel.Info); return(udpMsgForPktForwarder); }