/// <summary>
        /// Helper method that calls the API GetDevice method.
        /// </summary>
        private async Task <SearchDevicesResult> SearchDevicesAsync(string gatewayID = null, DevAddr?devAddr = null, DevEui?devEui = null, string appEUI = null, DevNonce?devNonce = null)
        {
            this.deviceLoadRequests?.Add(1);

            var client = this.serviceFacadeHttpClientProvider.GetHttpClient();

            var url = BuildUri("GetDevice", new Dictionary <string, string>
            {
                ["code"]      = AuthCode,
                ["GateWayId"] = gatewayID,
                ["DevAddr"]   = devAddr?.ToString(),
                ["DevEUI"]    = devEui?.ToString(),
                ["AppEUI"]    = appEUI,
                ["DevNonce"]  = devNonce?.ToString()
            });

            var response = await client.GetAsync(url);

            if (!response.IsSuccessStatusCode)
            {
                if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
                {
                    var badReqResult = await response.Content.ReadAsStringAsync();

                    if (string.Equals(badReqResult, "UsedDevNonce", StringComparison.OrdinalIgnoreCase))
                    {
                        return(new SearchDevicesResult
                        {
                            IsDevNonceAlreadyUsed = true,
                        });
                    }

                    if (badReqResult != null && badReqResult.StartsWith("JoinRefused", StringComparison.OrdinalIgnoreCase))
                    {
                        return(new SearchDevicesResult()
                        {
                            RefusedMessage = badReqResult
                        });
                    }
                }

                this.logger.LogError($"{devAddr} error calling get device function api: {response.ReasonPhrase}, status: {response.StatusCode}, check the azure function log");

                // TODO: FBE check if we return null or throw exception
                return(new SearchDevicesResult());
            }

            var result = await response.Content.ReadAsStringAsync();

            var devices = (List <IoTHubDeviceInfo>)JsonConvert.DeserializeObject(result, typeof(List <IoTHubDeviceInfo>));

            return(new SearchDevicesResult(devices));
        }
        public async Task <List <IoTHubDeviceInfo> > GetDeviceList(DevEui?devEUI, string gatewayId, DevNonce?devNonce, DevAddr?devAddr, ILogger log = null)
        {
            var results = new List <IoTHubDeviceInfo>();

            if (devEUI is { } someDevEui)
            {
                var joinInfo = await TryGetJoinInfoAndValidateAsync(someDevEui, gatewayId, log);

                // OTAA join
                using var deviceCache = new LoRaDeviceCache(this.cacheStore, someDevEui, gatewayId);
                var cacheKeyDevNonce = string.Concat(devEUI, ":", devNonce);

                if (this.cacheStore.StringSet(cacheKeyDevNonce, devNonce?.ToString(), TimeSpan.FromMinutes(5), onlyIfNotExists: true))
                {
                    var iotHubDeviceInfo = new IoTHubDeviceInfo
                    {
                        DevEUI     = someDevEui,
                        PrimaryKey = joinInfo.PrimaryKey
                    };

                    results.Add(iotHubDeviceInfo);

                    if (await deviceCache.TryToLockAsync())
                    {
                        deviceCache.ClearCache(); // clear the fcnt up/down after the join
                        log?.LogDebug("Removed key '{key}':{gwid}", someDevEui, gatewayId);
                    }
                    else
                    {
                        log?.LogWarning("Failed to acquire lock for '{key}'", someDevEui);
                    }
                }
                else
                {
                    log?.LogDebug("dev nonce already used. Ignore request '{key}':{gwid}", someDevEui, gatewayId);
                    throw new DeviceNonceUsedException();
                }
            }