AdaFruit IO Swagger based desktop HTTP client

Manually building clients for complex RESTful APIs (like AdaFruit.IO) can be a bit tedious so I figured I would try generating a C# http client from the Swagger OpenAPI specification(OAS) metadata.

My initial attempts using the Swagger Editor and NSwag on the AdaFruit.IO public API description didn’t go so well. (for more info see this AdaFruit.IO support forum thread) You may need to manually modify the type of the id field in Data & DataResponse, plus possibly other responses.

After figuring out how to set the API key, my code which uploads simulates three individual feeds and one feed group appears to work reliably.

/*

Copyright ® 2018 Jan devMobile Software, All Rights Reserved

THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR
PURPOSE.

http://www.devmobile.co.nz

 */
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using AdaFruit.IO;

namespace AdaFruit.IO
{
   public partial class Client
   {
      string adaFruitIOApiKey = "yourAPIKey";

      partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url)
      {
         client.DefaultRequestHeaders.Add("X-AIO-Key", adaFruitIOApiKey);
      }
   }
}

namespace devMobile.IoT.Adafruit.IO.Desktop
{
   class Program
   {
      static void Main(string[] args)
      {
         string userName = "YourUserName"; // This is mixed case & case sensitive
         // The feed group and feed key are forced to lower case by UI
         const string feedGroup = "devduinov2-dot-2";
         const string temperatureKey = "t";
         const double temperatureBase = 20.0;
         const double temperatureRange = 10.0;
         const string humidityKey = "h";
         const double humidityBase = 70.0;
         const double humidityRange = 20.0;
         const string batteryVoltageKey = "v";
         const double batteryVoltageBase = 3.00;
         const double batteryVoltageRange = -1.00;
         TimeSpan feedUpdateDelay = new TimeSpan(0, 0, 15);
         TimeSpan groupUpdateDelay = new TimeSpan(0, 0, 30);
         Random random = new Random();

         while (true)
         {
            Client client = new Client();
            double temperature = temperatureBase + random.NextDouble() * temperatureRange;
            double humidity = humidityBase + random.NextDouble() * humidityRange;
            double batteryVoltage = batteryVoltageBase + random.NextDouble() * batteryVoltageRange;

            Debug.WriteLine("Temperature {0}°C  Humidity {1}%  Battery Voltage {2}V", temperature.ToString("F1"), humidity.ToString("F0"), batteryVoltage.ToString("F2"));

            // First Update the 3 feeds individually
            // Temperature
            Datum temperatureDatum = new Datum()
            {
               Value = temperature.ToString("F1"),
            };
            client.CreateDataAsync(userName, temperatureKey, temperatureDatum).Wait();
            Task.Delay(feedUpdateDelay).Wait();

            // Humidity
            Datum humidityDatum = new Datum()
            {
               Value = humidity.ToString("F0"),
            };
            client.CreateDataAsync(userName, humidityKey, humidityDatum).Wait();
            Task.Delay(feedUpdateDelay).Wait();

            // Battery
            Datum batteryDatum = new Datum()
            {
               Value = batteryVoltage.ToString("F2"),
            };
            client.CreateDataAsync(userName, batteryVoltageKey, batteryDatum).Wait();
            Task.Delay(feedUpdateDelay).Wait();

            // Then update a feed in a group
            Group_feed_data devDuinoData = new Group_feed_data();

            devDuinoData.Feeds.Add(new Anonymous2() { Key = temperatureKey, Value = temperature.ToString("F1")});
            devDuinoData.Feeds.Add(new Anonymous2() { Key = humidityKey, Value = humidity.ToString("F0")});
            devDuinoData.Feeds.Add(new Anonymous2() { Key = batteryVoltageKey, Value = batteryVoltage.ToString("F2")});

            client.CreateGroupDataAsync(userName, feedGroup, devDuinoData).Wait();
            Task.Delay(groupUpdateDelay).Wait();
         }
      }
   }
}

AdaFruit IO basic desktop HTTP client

AdaFruit IO meets my basic criteria as it has support for HTTP/S clients (it also has an MQTT interface which I will look at in a future post) and the API is well documented.

My first Proof of Concept (PoC) was to build a desktop client which used the HttpWebRequest (for ease of porting to NetMF) classes to upload data.

The program uploaded one of three simulated values to AdaFruit.IO every 10 seconds.

I found the username, group, and feed keys to be case sensitive so pay close attention to the values displayed in the webby UI or copy n paste.

program.cs

 class Program
   {
      static void Main(string[] args)
      {
         string adaFruitIOApiBaseUrl = "https://IO.adafruit.com/api/v2/";
         string adaFruitIOUserName = "YourUserName"; // This is mixed case & case sensitive
         string adaFruitIOApiKey = "YourAPIKey";
         // The feed group and feed key are forced to lower case by UI
         const string feedGroup = "";
         //const string feedGroup = "devduinov2-dot-2";
         const string temperatureKey = "t";
         const double temperatureBase = 20.0;
         const double temperatureRange = 10.0;
         const string humidityKey = "h";
         const double humidityBase = 70.0;
         const double humidityRange = 20.0;
         const string batteryVoltageKey = "v";
         const double batteryVoltageBase = 3.00;
         const double batteryVoltageRange = -1.00;
         TimeSpan dataUpdateDelay = new TimeSpan(0, 0, 10);
         Random random = new Random();

         while (true)
         {
            double temperature = temperatureBase + random.NextDouble() * temperatureRange;
            Console.WriteLine("Temperature {0}°C", temperature.ToString("F1"));
            AdaFruitIoFeedUpdate(adaFruitIOApiBaseUrl, adaFruitIOUserName, adaFruitIOApiKey, feedGroup, temperatureKey, temperature.ToString("F1"));

            Thread.Sleep(dataUpdateDelay);

            double humidity = humidityBase + random.NextDouble() * humidityRange;
            Console.WriteLine("Humidity {0}%", humidity.ToString("F0"));
            AdaFruitIoFeedUpdate(adaFruitIOApiBaseUrl, adaFruitIOUserName, adaFruitIOApiKey, feedGroup, humidityKey, humidity.ToString("F0"));

            Thread.Sleep(dataUpdateDelay);

            double batteryVoltage = batteryVoltageBase + random.NextDouble() * batteryVoltageRange;
            Console.WriteLine("Battery voltage {0}V", batteryVoltage.ToString("F2"));
            AdaFruitIoFeedUpdate(adaFruitIOApiBaseUrl, adaFruitIOUserName, adaFruitIOApiKey, feedGroup, batteryVoltageKey, batteryVoltage.ToString("F2"));

            Thread.Sleep(dataUpdateDelay);
         }
      }

client.cs

      public void AdaFruitIoFeedUpdate(string apiBaseUrl, string userName, string apiKey, string group, string feedKey, string value, int httpRequestTimeoutmSec = 2500, int httpRequestReadWriteTimeoutmSec = 5000)
      {
         string feedUrl;

         if (group.Trim() == string.Empty)
         {
            feedUrl = apiBaseUrl + userName + @"/feeds/" + feedKey + @"/data";
         }
         else
         {
            feedUrl = apiBaseUrl + userName + @"/feeds/" + group.Trim() + "." + feedKey + @"/data";
         }

         Console.WriteLine(" Feed URL :{0}", feedUrl);

         try
         {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(feedUrl);
            {
               string payload = @"{""value"": """ + value + @"""}";

               byte[] buffer = Encoding.UTF8.GetBytes(payload);

               DateTime httpRequestedStartedAtUtc = DateTime.UtcNow;

               request.Method = "POST";
               request.ContentLength = buffer.Length;
               request.ContentType = @"application/json";
               request.Headers.Add("X-AIO-Key", apiKey);
               request.KeepAlive = false;
               request.Timeout = httpRequestTimeoutmSec;
               request.ReadWriteTimeout = httpRequestReadWriteTimeoutmSec;

               using (Stream stream = request.GetRequestStream())
               {
                  stream.Write(buffer, 0, buffer.Length);
               }

               using (var response = (HttpWebResponse)request.GetResponse())
               {
                  Console.WriteLine(" Status: " + response.StatusCode + " : " + response.StatusDescription);
               }

               TimeSpan duration = DateTime.UtcNow - httpRequestedStartedAtUtc;
               Console.WriteLine(" Duration: " + duration.ToString());
            }
         }
         catch (Exception ex)
         {
            Console.WriteLine(ex.Message);
            throw;
         }
      }
   }

This approach seemed to work pretty reliably

DesktopHTTPRequest