I use Netduino devices for teaching and my students often build projects which need a cloud based service like AdaFruit.IO to capture, store and display their sensor data.
My Proof of Concept (PoC) which uses a slightly modified version of the AdaFruit.IO basic desktop HTTP client code has been running on several Netduino 2 Plus, Netduino 3 Ethernet and Netduino 3 Wifi devices for the last couple of days and looks pretty robust.
The Netduino 3 Wifi device also supports https for improved security and privacy. They also make great field gateways as they can run off solar/battery power.
The devices have been uploading temperature and humidity measurements from a Silicon labs Si7005 sensor. (Outside sensor suffering from sunstrike)
program.cs
* Copyright ® 2017 December 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.Net; using System.Threading; using Microsoft.SPOT; using Microsoft.SPOT.Hardware; using Microsoft.SPOT.Net.NetworkInformation; using SecretLabs.NETMF.Hardware.Netduino; using devMobile.NetMF.Sensor; using devMobile.IoT.NetMF; namespace devMobile.IoT.AdaFruitIO.NetMF.Client { public class Program { private const string adaFruitIOApiBaseUrl = @"https://IO.adafruit.com/api/v2/"; private const string group = "netduino3"; private const string temperatureFeedKey = "t"; private const string humidityFeedKey = "h"; private const string adaFruitUserName = "YourUserName"; private const string adaFruitIOApiKey = "YourAPIKey"; private static readonly TimeSpan timerDueAfter = new TimeSpan(0, 0, 15); private static readonly TimeSpan timerPeriod = new TimeSpan(0, 0, 30); private static OutputPort led = new OutputPort(Pins.ONBOARD_LED, false); private static SiliconLabsSI7005 sensor = new SiliconLabsSI7005(); private static AdaFruitIoClient adaFruitIoClient = new AdaFruitIoClient(adaFruitUserName, adaFruitIOApiKey, adaFruitIOApiBaseUrl); public static void Main() { // Wait for Network address if DHCP NetworkInterface networkInterface = NetworkInterface.GetAllNetworkInterfaces()[0]; if (networkInterface.IsDhcpEnabled) { Debug.Print(" Waiting for DHCP IP address"); while (NetworkInterface.GetAllNetworkInterfaces()[0].IPAddress == IPAddress.Any.ToString()) { Debug.Print(" ."); led.Write(!led.Read()); Thread.Sleep(250); } led.Write(false); } // Display network config for debugging Debug.Print("Network configuration"); Debug.Print(" Network interface type : " + networkInterface.NetworkInterfaceType.ToString()); Debug.Print(" MAC Address : " + BytesToHexString(networkInterface.PhysicalAddress)); Debug.Print(" DHCP enabled : " + networkInterface.IsDhcpEnabled.ToString()); Debug.Print(" Dynamic DNS enabled : " + networkInterface.IsDynamicDnsEnabled.ToString()); Debug.Print(" IP Address : " + networkInterface.IPAddress.ToString()); Debug.Print(" Subnet Mask : " + networkInterface.SubnetMask.ToString()); Debug.Print(" Gateway : " + networkInterface.GatewayAddress.ToString()); foreach (string dnsAddress in networkInterface.DnsAddresses) { Debug.Print(" DNS Server : " + dnsAddress.ToString()); } Timer humidityAndtemperatureUpdates = new Timer(HumidityAndTemperatureTimerProc, null, timerDueAfter, timerPeriod); Thread.Sleep(Timeout.Infinite); } static private void HumidityAndTemperatureTimerProc(object state) { led.Write(true); try { double humidity = sensor.Humidity(); Debug.Print(" Humidity " + humidity.ToString("F0") + "%"); adaFruitIoClient.FeedUpdate(group, humidityFeedKey, humidity.ToString("F0")); } catch (Exception ex) { Debug.Print("Humidifty read+update failed " + ex.Message); return; } try { double temperature = sensor.Temperature(); Debug.Print(" Temperature " + temperature.ToString("F1") + "°C"); adaFruitIoClient.FeedUpdate(group, temperatureFeedKey, temperature.ToString("F1")); } catch (Exception ex) { Debug.Print("Temperature read+update failed " + ex.Message); return; } led.Write(false); } private static string BytesToHexString(byte[] bytes) { string hexString = string.Empty; // Create a character array for hexidecimal conversion. const string hexChars = "0123456789ABCDEF"; // Loop through the bytes. for (byte b = 0; b < bytes.Length; b++) { if (b > 0) hexString += "-"; // Grab the top 4 bits and append the hex equivalent to the return string. hexString += hexChars[bytes[b] >> 4]; // Mask off the upper 4 bits to get the rest of it. hexString += hexChars[bytes[b] & 0x0F]; } return hexString; } } }
AdaFruit.IO client.cs, handles feed groups and individual feeds
/* Copyright ® 2017 December 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.IO; using System.Net; using System.Text; using Microsoft.SPOT; namespace devMobile.IoT.NetMF { public class AdaFruitIoClient { private const string apiBaseUrlDefault = @"http://IO.adafruit.com/api/v2/"; private string apiBaseUrl = ""; private string userName = ""; private string apiKey = ""; private int httpRequestTimeoutmSec; private int httpRequestReadWriteTimeoutmSec; public AdaFruitIoClient(string userName, string apiKey, string apiBaseUrl = apiBaseUrlDefault, int httpRequestTimeoutmSec = 2500, int httpRequestReadWriteTimeoutmSec = 5000) { this.apiBaseUrl = apiBaseUrl; this.userName = userName; this.apiKey = apiKey; this.httpRequestReadWriteTimeoutmSec = httpRequestReadWriteTimeoutmSec; this.httpRequestTimeoutmSec = httpRequestTimeoutmSec; } public void FeedUpdate(string group, string feedKey, string value) { string feedUrl; if (group.Trim() == string.Empty) { feedUrl = apiBaseUrl + userName + @"/feeds/" + feedKey + @"/data"; } else { feedUrl = apiBaseUrl + userName + @"/feeds/" + group.Trim() + "." + feedKey + @"/data"; } 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 = this.httpRequestTimeoutmSec; request.ReadWriteTimeout = this.httpRequestReadWriteTimeoutmSec; using (Stream stream = request.GetRequestStream()) { stream.Write(buffer, 0, buffer.Length); } using (var response = (HttpWebResponse)request.GetResponse()) { Debug.Print(" Status: " + response.StatusCode + " : " + response.StatusDescription); } TimeSpan duration = DateTime.UtcNow - httpRequestedStartedAtUtc; Debug.Print(" Duration: " + duration.ToString()); } } } }
Bill of materials for PoC