nRF24 Windows 10 IoT Core Test Harness

After modifying the Raspbery PI nRF24L01 shields I built a single page single button Universal Windows Platforms(UWP) test harness (using the techfooninja RF24 library) to check everything was working as expected.

I used a couple of Netduinos and Raspbery PI devices to as test clients.

public sealed partial class MainPage : Page
{
   private const byte ChipEnablePin = 25;
   private const byte ChipSelectPin = 0;
   private const byte InterruptPin = 17;
   private const byte Channel = 10;
   private RF24 radio;

   public MainPage()
   {
      this.InitializeComponent();

      this.radio = new RF24();

      this.radio.OnDataReceived += this.Radio_OnDataReceived;
      this.radio.OnTransmitFailed += this.Radio_OnTransmitFailed;
      this.radio.OnTransmitSuccess += this.Radio_OnTransmitSuccess;

      this.radio.Initialize(ChipEnablePin, ChipSelectPin, InterruptPin);
      this.radio.Address = Encoding.UTF8.GetBytes("Base1");
      this.radio.Channel = Channel;
      this.radio.PowerLevel = PowerLevel.Low;
      this.radio.DataRate = DataRate.DR250Kbps;

      this.radio.IsEnabled = true;

      Debug.WriteLine("Address: " + Encoding.UTF8.GetString(this.radio.Address));
      Debug.WriteLine("Channel: " + this.radio.Channel);
      Debug.WriteLine("DataRate: " + this.radio.DataRate);
      Debug.WriteLine("PA: " + this.radio.PowerLevel);
      Debug.WriteLine("IsAutoAcknowledge: " + this.radio.IsAutoAcknowledge);
      Debug.WriteLine("IsDynamicAcknowledge: " + this.radio.IsDynamicAcknowledge);
      Debug.WriteLine("IsDynamicPayload: " + this.radio.IsDynamicPayload);
      Debug.WriteLine("IsEnabled: " + this.radio.IsEnabled);
      Debug.WriteLine("IsInitialized: " + this.radio.IsInitialized);
      Debug.WriteLine("IsPowered: " + this.radio.IsPowered);
   }

   private void Radio_OnDataReceived(byte[] data)
   {
     string dataUTF8 = Encoding.UTF8.GetString(data);

     Debug.WriteLine(string.Format("Received: {0}", dataUTF8));
   }

   private void buttonSend_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
   {
      this.radio.SendTo(Encoding.UTF8.GetBytes("Duino"), Encoding.UTF8.GetBytes(DateTime.UtcNow.ToString("yy-MM-dd hh:mm:ss"))) ;
   }

   private void Radio_OnTransmitSuccess()
   {
      Debug.WriteLine("Radio_OnTransmitSuccess");
   }

   private void Radio_OnTransmitFailed()
   {
      Debug.WriteLine("Radio_OnTransmitFailed");
   }
}

Interrupt Triggered: FallingEdge
Data Sent!
Radio_OnTransmitSuccess
Interrupt Triggered: RisingEdge
Interrupt Triggered: FallingEdge
Received: 20.4 70.7
Interrupt Triggered: RisingEdge
Interrupt Triggered: FallingEdge
Data Sent!
Radio_OnTransmitSuccess
Interrupt Triggered: RisingEdge
Interrupt Triggered: FallingEdge
Received: 20.3 70.8
Interrupt Triggered: RisingEdge

The Raspberry PI could reliably receive and transmit messages.

MS Ignite Auckland NZ Presentation now available online

My presentation “All your device are belong to us” [M240] is now online at MSDN Channel 9

So much hype, so many different devices, so many protocols, so much data, so little security, welcome to the Internet of Things. Come and see how you can build an affordable, securable, scalable, interoperable, robust & reliable solution with embedded devices, Windows 10 IoT and Microsoft Azure. By 2020 there will be 26 Billion devices and 4.5 million developers building solutions so the scope is limitless.

I had 8 devices in my presentation so the scope for disaster was high.

The first demo was of how sensors could be connected across Arduino, Netduino and Raspberry PI platforms.

The Arduino demo used

The Netduino demo used

The Raspbery PI Windows 10 IoT Core demo used

The hobbyist data acquisition demo collected data from two devduino devices that were in passed around by the audience and were each equipped with a Temperature & Humidity sensor. They uploaded data to Xively over an NRF24L01 link to a gateway running on a Netduino 3 Ethernet and the data was displayed in real-time on my house information page

The professional data acquisition demo uploaded telemetry data to an Azure ServiceBus EventHub and retrieved commands from an Azure ServiceBus Queue. Both devices were running software based on Azure ServiceBus Lite by Paolo Paiterno

The telemetry stream was the temperature of some iced water.

The commands were processed by a Raspbery PI running Windows 10 IoT Core which turned a small fan on & off to illustrate how a FrostFan could be used in a vineyard to reduce frost damage to the vines.

Frost Fan demo

MS Ignite 2015 Frost Fan demo

My demos all worked on the day which was a major win as many other presenters struggled with connectivity. Thanks to the conference infrastructure support guys who helped me sort things out.

With the benefit of hindsight, I tried to fit too much in and the overnight partial rewrite post attending the presentation Mashup the Internet of Things, Azure App Service and Windows 10 to Deliver Business Value [M387] by Rob Tiffany was a bit rushed.

Netduino 3 Wifi xively nRF24L01 Gateway

The first version of this code acquired data from a number of *duino devices and uploaded it to xively for a week without any problems(bar my ADSL modem dropping out every so often which it recovered from without human intervention). The data streams are the temperature and humidity for the three bedrooms in my house (the most reliable stream is Bedroom 1). Next version will use the new Netduino.IP stack and run on a Netduino 2 Plus

Netduino 3 Wifi with nRF24L01 shield

Netduino 3 Wifi + nRF24L01 shield

To make the software easy to setup all the gateway configuration is stored on a MicroSD and can be modified with a text editor. When the application starts it looks for a file in the root directory of the MicroSD card called app.config. If the file does not exist an empty template is created.

httprequestreadwritetimeoutmsec=2500
httprequesttimeoutmsec=2500
webproxyaddress=
webproxyport=
xivelyapibaseurl=http://api.xively.com/v2/feeds/
xivelyapikey=XivelyAPIKeyGoesHere
xivelyapifeedid=XivelyFeedIDGoesHere
xivelyapicontenttype=text/csv
xivelyapiendpoint=.csv
nrf2l01address=AddressGoesHere
nrf2l01channel=ChannelGoesHere
nrf2l01datarate=0
channel1=Sensor1
channel2=Sensor2
channel3=Sensor3
channel4=Sensor4
channel5=Sensor5
...
...

The first byte of each (upto 32 byte) nRF24L01 message is used to determine the Xively channel.

For testing I used a simple *duino program which uploads temperature and humidity readings every 5 seconds. It’s not terribly efficient or elegant and is just to illustrate how to package up the data.

#include <RF24_config>
#include <nRF24L01.h>
#include <SPI.h>
#include <RF24.h>
#include "Wire.h"
#include <TH02_dev.h>

//UNO R3 with embedded coolness board
//RF24 radio(3, 7);
//devDuino  with onboard
RF24 radio(8, 7);

char payload[32] = "";
const uint64_t pipe = 0x3165736142LL; // Base1 pay attention to byte ordering and address length

void setup()
{
  Serial.begin(9600);

  radio.begin();
  radio.setPALevel(RF24_PA_MAX);
  radio.setChannel(10);
  radio.enableDynamicPayloads();
  radio.openWritingPipe(pipe);

  radio.printDetails();

  /* Power up,delay 150ms,until voltage is stable */
  delay(150);

  TH02.begin();

  delay(1000);
}

void loop()
{
  float temperature = TH02.ReadTemperature();
  float humidity = TH02.ReadHumidity();

  radio.powerUp();

  payload[0] = 'A';
  dtostrf(temperature, 5, 1, &payload[1]);
  Serial.println(payload);
  boolean result = radio.write(payload, strlen(payload));
  if (result)
    Serial.println("T Ok...");
  else
    Serial.println("T failed.");

  payload[0] = 'B';
  dtostrf(humidity, 5, 1, &payload[1]);
  Serial.println(payload);
  result = radio.write(payload, strlen(payload));
  if (result)
    Serial.println("H Ok...");
  else
    Serial.println("H failed.");

  radio.powerDown();

  delay(5000);
}

The gateway code creates a thread for each call to the Xively REST API. (In future the code may need to limit the number of concurrent requests)

private void OnReceive(byte[] data)
{
   activityLed.Write(!activityLed.Read());

   // Ensure that we have a valid payload
   if ( data.Length == 0 )
   {
      Debug.Print( "ERROR - Message has no payload" ) ;
      return ;
   }

   // Extract the device id
   string deviceId = xivelyApiChannleIDPrefix + data[0].ToString();
   string message = new String(Encoding.UTF8.GetChars(data, 1, data.Length - 1));

   string xivelyApiChannel = appSettings.GetString( deviceId, string.Empty ) ;
   if ( xivelyApiChannel.Length == 0 )
   {
      Debug.Print("ERROR - Inbound message has unknown channel " + deviceId);
      return ;
   }
   Debug.Print(DateTime.Now.ToString("HH:mm:ss") + " " + xivelyApiChannel + " " + message); ;

   Thread thread = new Thread(() =&gt; xivelyFeedUpdate(xivelyApiChannel, message ));
   thread.Start();
   }

private void xivelyFeedUpdate( string channel, string value)
{
   #region Assertions
   Debug.Assert(channel != null);
   Debug.Assert(channel != string.Empty );
   Debug.Assert(value != null);
   #endregion

   try
   {
      WebProxy webProxy = null;

      if (webProxyAddress.Length &gt; 1)
      {
         webProxy = new WebProxy(webProxyAddress, webProxyPort);
      }

      using (HttpWebRequest request = (HttpWebRequest)WebRequest.Create(xivelyApiBaseUrl + xivelyApiFeedID + xivelyApiEndpoint))
      {
         byte[] buffer = Encoding.UTF8.GetBytes(channel + "," + value);

         DateTime httpRequestedStartedAtUtc = DateTime.UtcNow;

         if (webProxy != null)
         {
            request.Proxy = webProxy;
         }
         request.Method = "PUT";
         request.ContentLength = buffer.Length;
         request.ContentType = xivelyApiContentType;
         request.Headers.Add("X-ApiKey", xivelyApiKey);
         request.KeepAlive = false;
         request.Timeout = httpRequestTimeoutmSec;
         request.ReadWriteTimeout = httpRequestReadWriteTimeoutmSec;

         // request body
         Debug.Print("HTTP request");
         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());
      }
   }
   catch (Exception ex)
   {
      Debug.Print(ex.Message);
   }
}

To use this code download the Nordic nRF24L01 library from Codeplex then include that plus my Netduino NRF24L01 Xively Gateway in a new solution and it should just work.

Deploy the application to a Netduino 2 Plus or Netduino 3 Wifi device and run it to create the app.config file, then use a text editor to update the file with your Xively & device settings.

I’ll upload this and a couple of other projects to GitHub shortly.

Bill of materials (prices as at July 2015)

Netduino 3 Wifi xively nRF24L01 Gateway data stream live

The gateway is now live, I’m regularly updating the Netduino 3 wifi code and the client arduino, devDuino + netduino devices so there maybe short periods of downtime and/or missing data points.

The stream is available here and is currently just temperature and humidity readings from two bedrooms updating roughly once a minute.

I live in New Zealand which is currently UTC + 12.

Netduino 3 Wifi xively nRF24L01 Gateway introduction

Around home I have a number of Arduino, devDuino and Netduino devices collecting power consumption, temperature & humidity measurements. Previously I had built an Azure event hub gateway which runs on Windows 7(or later) which acts as a gateway forwarding local http requests to an Microsoft Azure event hub.

Not all my embedded devices are capable of making an http request but an nRF24l01 based approach is supported.

For this application I wanted something a bit simpler than an Azure Event hub which could plot basic graphs and as I didn’t require massive scale Xively looked ideal.

Netduino 3 Wifi xively gateway + duino clients

Netduino 3 Wifi xively gateway and *duino clients

Over the next few blog postings I will show how I built the Netduino 3 wifi application and the Arduino based clients.

Bill of materials for the Xively gateway (prices at June 2015)

First step is to configure the network

NetworkInterface networkInterface = NetworkInterface.GetAllNetworkInterfaces()[0];

if (networkInterface.IsDhcpEnabled)
{
   Debug.Print(" Waiting for IP address ");

   while (NetworkInterface.GetAllNetworkInterfaces()[0].IPAddress == IPAddress.Any.ToString()) 
   {
      Thread.Sleep(100);
   }
}

// 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());
}

_module = new NRF24L01Plus();

Then setup the nRF24l01 driver

_module.OnDataReceived += OnReceive;
_module.OnTransmitFailed += OnSendFailure;
_module.OnTransmitSuccess += OnSendSuccess;

_module.Initialize(SPI.SPI_module.SPI1, Pins.GPIO_PIN_D7, Pins.GPIO_PIN_D3, Pins.GPIO_PIN_D2);
_module.Configure(myAddress, channel, NRFDataRate.DR1Mbps);
_module.Enable();

The setup required for the Xively API and mapping the devices highlighted the need for a means of storing configuration which could be modified using a simple text editor.

Netduino 3 Wifi with nRF24L01 shield

Netduino 3 Wifi + nRF24L01 shield

This software was built using tooling created and shared by others.

Big thanks to

Jakub Bartkowiak – Gralin.NETMF.Nordic.NRF24L01Plus

Azure Event Hub Service Gateway V0.5

In a previous post I had developed a simple Microsoft Azure EventHubs Service Gateway for Arduino and Netduino devices which required Internet Information Server(IIS). Running IIS in some home environments could be a bit awkward so I have got a basic light weight version going which is hosted in a self installing Windows Service. The gateway supports four command line parameters.(The install and uninstall must be run as administrator otherwise the requested process will fail)

  • Install – install the service
  • Uninstall – uninstalls the service
  • Debug – runs the service in interactive mode
  • Name – allows you to specify the name of the service in the Services Manager.

The code does some basic logging to the Windows Event log and can be configured to start automatically when the computer starts.

The configuration file has two settings

<appSettings>
<add key="Microsoft.ServiceBus.ConnectionString" value="YourConnectionStringGoesHere" />
<add key="Microsoft.ServiceBus.EventHub" value="myhomemonitor" />
<add key="baseAddress" value="http://your.URL.goes.Here:8080/" />
</appSettings>

The code is based on the self installing service sample code that ships with the Wix# toolkit.

try
{
   string connectionString = ConfigurationManager.AppSettings["Microsoft.ServiceBus.ConnectionString"];
   string eventHubName = ConfigurationManager.AppSettings["Microsoft.ServiceBus.EventHub"];

   NamespaceManager namespaceManager = NamespaceManager.CreateFromConnectionString(connectionString);

   EventHubClient client = EventHubClient.Create(eventHubName);

   EventData data = new EventData(request.Content.ReadAsByteArrayAsync().Result);

   // Set user properties if needed
   data.Properties.Add("UploadedAtUTC", DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss"));
   data.Properties.Add("UploadedBy", "devMobileAzureEventHubGateway");

   client.Send(data);
}
catch (Exception ex)
{
   eventLog.WriteEntry(string.Format("Application initialisation failed {0}", ex.Message), EventLogEntryType.Error);
}

The Azure EventHub Service Gateway Code V0.5 is a bit rough but I will enhance it as time allows. First steps will be improving logging then creating a WIX# based installer.

Electric Longboard devDuino V2 Controller

I really wanted to get the longboard working so I had a look at buying Wiiceiver from AustinDavid.com.

The source code is available on Github and I had a spare devicter devDuino Sensor Node V2 sitting on my desk. With some modification (changing pins numbers and removing all references to the second LED) I got the wiiceiver code running on my devDuino.

The Electronic Speed Controller(ESC) and the plastic lunch box (containing the batteries and devDuino) are attached to the deck with 3M Command adhesive strips. The first set of command adhesive strips I tried were for hanging pictures and had a Velcro quick release system. This approach was a failure and the ESC & electronics box fell off after 10-15 minutes use. The Velcro backing tape was getting pulled in the wrong direction so was unable to hold the weight of the electronics when vibration levels increased. I tried them because a “quick release” capability would be handy but I have gone back to using conventional 3M Command adhesive strips and these are working well.

devDuino V2 and ESC on longboard

devDuino based controller interfaced with ESC and wireless WiiChuk

Initial rides went well, though I need to recalibrate the acceleration and braking ramp up/down settings to suit my hardware and riding style.

Bill of Materials for this project (Prices as at Feb 2015)

  • Single Motor Mechanical Electric Longboard Kit USD223
  • Turnigy Aerodrive SK3-6364-245kv Brushless Outrunner Motor USD70.68
  • HobbyKing 150A High Performance Brushless Car ESC USD68.99
  • ZIPPY Flightmax 5000mAh battery X 2 USD31.99 each
  • HXT4mm Battery Harness 14AWG for 2 Packs in Series USD2.43
  • HXT 4mm Gold Connector with Protector (10pcs/set)
  • devDuino Sensor Node V2 USD15.99
  • Grove Nunchuck adaptor USD2.90
  • Grove Branch Cable for Servo USD4.90
  • Wireless Nunchuck NZD25.00
  • Moose 9.5×42 Longboard Flush Mount Deck Green Stain NZD57

WARNING – Disconnect the power supply pin on the Grove Branch Cable for Servos as the ESC will supply sufficient current to make the batteries on the devDuino go pop. Wrap some tape around the other servo connector so it can’t cause a short circuit.

Thanks to Austin David for making the code for the Wiiciever open source, if anyone is interested in my code I can tidy it up and share.

/*
 * Pin IDs -- NOT LOCATIONS !!!
 * don't change these ever; see &quot;pinLocation&quot; below for
 * actual locations
 */
#define RED_LED_ID   0
//#define GREEN_LED_ID 1

#define ESC_PPM_ID   2
//#define ESC2_PPM_ID   6
#define ESC_GROUND 00     // hard-wired

//#define WII_POWER_ID 3
//#define WII_GROUND 00     // hard-wired

#define WII_SCL_ID   4
#define WII_SDA_ID   5

I made my devDuino look like a V3 wiiceiver

int pinLocation(int pinID) {
  int pinMap[7][3] = {
  // v1, v2, v3
    {8,   8,  9},  // RED_LED     any digital pin
    {7,   6,  8},  // GREEN_LED   any digital pin
    {10,  9,  3},  // ESC_PPM     PWM required
    {9,  11,  5},  // WII_POWER   any digital pin
    {19, 19, 19}, // WII_SCL     A5, don't change
    {18, 18, 18}, // WII_SDA     A4, don't change
    {0,  10, 0}, // ESC2_PPM    PWM required
  };

The rest of my changes were commenting out all references to the Green LED as the devDuino only has one onboard LED.