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)

Mikrobus.Net Quail and Click boards arrived

A couple of weeks ago I ordered a NetMF board from MikroElektronika in Serbia. Last night I downloaded the platform libraries and built my first application for the device (the obligatory flashing an LED). This afternoon I soldered the headers onto the clicks and hopefully didn’t make a mess of it.

MikroBusNet Quail board and a selection of click boards

MikroElektronika MikroBusNet Quail board and a selection of click boards

using MBN;
using MBN.Exceptions;
using System.Threading;
using Microsoft.SPOT.Hardware;

namespace MBFlashy
{
   public class Program
   {
      public static void Main()
      {
         while( true )
         {
            MBN.Hardware.Led1.Write(!MBN.Hardware.Led1.Read());
            Thread.Sleep(500);
         }
      }
   }
}

Next steps will be to get the nRF24L01 and joystick clicks working, maybe as a remote control for my 4WD robot.

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

Netduino pollution Monitor V0.1

As part of a project for Sensing City I had been helping with the evaluation of  PM2.5/PM10 sensors for monitoring atmospheric pollution levels. For my DIY IoT projects I use the SeeedStudio Grove system which has a couple of dust sensors. The Grove Dust Sensor which is based on a Shinyei Model PPD42 Particle Sensor looked like a cost effective option.

Seeedstudio Grove Dust Sensor

Seeedstudio Grove Dust Sensor

Bill of Materials for my engineering proof of concept (Prices as at June 2015)

I initially got the sensor running with one of my Arduino Uno R3  devices using the software from the seeedstudio wiki and the ratio values returned by my Netduino Plus 2 code (see below) look comparable. I have purchased a couple of extra dust sensors so I can run the Arduino & Netduino devices side by side. I am also trying to source a professional air quality monitor so I can see how reliable my results are

The thread ” (0x2) has exited with code 0 (0x0).

Ratio 0.012

Ratio 0.012

Ratio 0.020

Ratio 0.008

Ratio 0.031

Ratio 0.014

Ratio 0.028

Ratio 0.012

Ratio 0.013

Ratio 0.018

public class Program
{
private static long pulseStartTicks = 0;
private static long durationPulseTicksTotal = 0;
readonly static TimeSpan durationSample = new TimeSpan(0, 0, 0, 30);
readonly static TimeSpan durationWaitForBeforeFirstSample = new TimeSpan(0, 0, 0, 30);

public static void Main()
{
InterruptPort sensor = new InterruptPort(Pins.GPIO_PIN_D8, false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);
sensor.OnInterrupt += sensor_OnInterrupt;

Timer sampleTimer = new Timer(SampleTimerProc, null, durationWaitForBeforeFirstSample, durationSample);

Thread.Sleep(Timeout.Infinite);
}

static void sensor_OnInterrupt(uint data1, uint data2, DateTime time)
{
if (data2 == 1)
{
long pulseDuration = time.Ticks - pulseStartTicks;

durationPulseTicksTotal += pulseDuration;
}
else
{
pulseStartTicks = time.Ticks;
}
}

static void SampleTimerProc(object status)
{
double ratio = durationPulseTicksTotal / (double)durationSample.Ticks ;
durationPulseTicksTotal = 0;

Debug.Print("Ratio " + ratio.ToString("F3"));
}
}

Next steps will be, adding handling for edges cases, converting the ratio into a particle concentration per litre or 0.1 cubic feet, selecting a weather proof enclosure, smoothing/filtering the raw measurements, and uploading the values to Xively for presentation and storage.

EVolocity 3 Axis G-Meter

A telemetry system could be used to monitor the progress of your electric vehicle and provide feedback to the team & driver about how efficiently/fast it is being driven. As part of a telemetry system lateral, longitudinal, and vertical acceleration could be monitored using a cheap ADXL345 mems accelerometer

Netduino based 3D GMeter

Netduino based 3D G-Meter

Bill of Materials for my engineering proof of concept (Prices as at May 2015)

The sample code reads the acceleration data from the ADXL345 using a driver originally created by Love Electronics. It then displays the magnitude of the scaled acceleration on 3 x LED Bars using code written by Famoury Toure

OutputPort Xcin = new OutputPort(Pins.GPIO_PIN_D0, false);
OutputPort Xdin = new OutputPort(Pins.GPIO_PIN_D1, false);
OutputPort Ycin = new OutputPort(Pins.GPIO_PIN_D3, false);
OutputPort Ydin = new OutputPort(Pins.GPIO_PIN_D4, false);
OutputPort Zcin = new OutputPort(Pins.GPIO_PIN_D5, false);
OutputPort Zdin = new OutputPort(Pins.GPIO_PIN_D6, false);

GroveLedBarGraph Xbar = new GroveLedBarGraph(Xcin, Xdin);
GroveLedBarGraph Ybar = new GroveLedBarGraph(Ycin, Ydin);
GroveLedBarGraph Zbar = new GroveLedBarGraph(Zcin, Zdin);

using (OutputPort i2cPort = new OutputPort(Pins.GPIO_PIN_SDA, true))
{
   i2cPort.Write(false);
}

ADXL345 accel = new ADXL345(0x53);
accel.EnsureConnected();
accel.Range = 2;
accel.FullResolution = true;
accel.EnableMeasurements();
accel.SetDataRate(0x0F);

while (true)
{
   accel.ReadAllAxis();

   uint xValue = (uint)(((accel.ScaledXAxisG / 1.0 ) + 1.0) * 5.0) ;
   uint xbar = 1;
   xbar = xbar << (int)xValue;
   Xbar.setLED(xbar);

   uint yValue = (uint)(((accel.ScaledYAxisG / 1.0) + 1.0) * 5.0);
   uint ybar = 1;
   ybar = ybar << (int)yValue;
   Ybar.setLED(ybar);

   uint zValue = (uint)((-(accel.ScaledZAxisG / 1.0) + 2.0) * 5.0);
   uint zbar = 1;
   zbar = zbar << (int)zValue;
   Zbar.setLED(zbar);

   Thread.Sleep(20);
   }
}

St Margaret’s CodeClub information

If you want to follow along at home all the software is free (Visual Studio Express 2013) or open source (Netduino software & Hardware + NetMF) and can be downloaded from the following locations. The packages need to be sequentially installed in the order below.

If you want to purchase your own hardware, we use Netduino 2 Plus devices (we use them mainly for their integrated networking and MicroSD card) and Seeedstudio Grove sensors. (Prices as at 05/2015)

We are looking into Apple friendly options for later this term.

Netduino 3 wifi Azure Event Hub client

Over the last couple of weeks I have been beta testing a Netduino 3 Wifi board. One of the great features of the new board is baked in support for SSL 3.0 and TLS 1.2 which enables direct connection to services which require https.

EDIT: The device has been running under my desk powered by a wall wart for a week. It has been monitoring the temperature of my office and the air gap between the curtains and the glass. My ADSL has gone down a couple of times but the N3 has recovered all by itself and kept on going.

In a couple of previous blog posts I have shown how to upload data to an Azure Event Hub from other NetMFdevices. I built an application that runs on a FEZ Spider and a lightweight Service Gateway so I was keen to see how well a Netduino 3 Wifi based solution worked.

To get something working on my Netduino 3 device I started with the application I had written for the FEZ spider. The code is based on OBD Recorder for .Net Micro Framework with ServiceBus, AMQP (for IoT) samples. The test client used a couple of DS18B20 temperature sensors to monitor the temperature of my fridge and freezer.

Netduino 3 device with temperature sensors

Netduino 3 device with Seeedstudio Shield and two temperature sensors

I created an Event Hub and associated device access keys and fired up Service Bus Explorer so I could see what was happening.ServiceBus Explorer showing my fridge and freezer temperatures

In the application the first step was to add code to wait for the device to acquire an IP address. (Will replace this code with a more efficient approach)

// Wait for Network address if DHCP
NetworkInterface networkInterface = NetworkInterface.GetAllNetworkInterfaces()[0];

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

   while (NetworkInterface.GetAllNetworkInterfaces()[0].IPAddress == IPAddress.Any.ToString()) ;
}

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

deviceId = BytesToHexString(networkInterface.PhysicalAddress);

Then to send the message to the event hub the request has to have a authorisation token attached

private void EventHubSendMessage(string eventHubAddressHttps, string messageBody)
{
   string token = CreateSasToken(eventHubAddressHttps + "/messages", sasKeyName, sasKeyText);

   try
   {
      using( HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(eventHubAddressHttps + "/messages" + "?timeout=60" + ApiVersion))
      {
         request.Timeout = 2500;
         request.Method = "POST";

         // Enable these options to suit your environment
         //request.Proxy = new WebProxy("myproxy.myorganisation.com", true);
         //request.Credentials = new NetworkCredential("myusername", "mytopsecretpassword"); 

         request.Headers.Add("Authorization", token);
         request.Headers.Add("ContentType", "application/json;charset=utf-8");

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

         request.ContentLength = buffer.Length;

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

         using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
         {
            Debug.Print("HTTP Status:" + response.StatusCode + " : " + response.StatusDescription);
         }
      }
   }
   catch (WebException we)
   {
      Debug.Print(we.Message);
   }
}

// Create a SAS token for a specified scope. SAS tokens are described in http://msdn.microsoft.com/en-us/library/windowsazure/dn170477.aspx.
private static string CreateSasToken(string uri, string keyName, string key)
{
   // Set token lifetime to 20 minutes. When supplying a device with a token, you might want to use a longer expiration time.
   uint tokenExpirationTime = GetExpiry(20 * 60);

   string stringToSign = HttpUtility.UrlEncode(uri) + "\n" + tokenExpirationTime;

   var hmac = SHA.computeHMAC_SHA256(Encoding.UTF8.GetBytes(key), Encoding.UTF8.GetBytes(stringToSign));
   string signature = Convert.ToBase64String(hmac);

   signature = Base64NetMf42ToRfc4648(signature);

   string token = "SharedAccessSignature sr=" + HttpUtility.UrlEncode(uri) + "&sig=" + HttpUtility.UrlEncode(signature) + "&se=" + tokenExpirationTime.ToString() + "&skn=" + keyName;

   return token;
}

private static string Base64NetMf42ToRfc4648(string base64netMf)
{
   var base64Rfc = string.Empty;

   for (var i = 0; i < base64netMf.Length; i++)
   {
      if (base64netMf[i] == '!')
      {
         base64Rfc += '+';
      }
      else if (base64netMf[i] == '*')
      {
         base64Rfc += '/';
      }
      else
      {
         base64Rfc += base64netMf[i];
      }
   }
   return base64Rfc;
}

static uint GetExpiry(uint tokenLifetimeInSeconds)
{
   const long ticksPerSecond = 1000000000 / 100; // 1 tick = 100 nano seconds

   DateTime origin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
   TimeSpan diff = DateTime.Now.ToUniversalTime() - origin;

   return ((uint)(diff.Ticks / ticksPerSecond)) + tokenLifetimeInSeconds;
}

The initial version of the Netduino TI CC3100 driver has some limitations e.g. no server certificate validation but these should be attended to in future releases.

The software was based on Brad’s One-Wire and DS18B20 library with fixes from here.

CodeClub Internet of Things Boxes sponsored by Microsoft NZ

A few months ago Microsoft NZ donated NZ6K to CodeClub NZ for the purchase of kits for our basic electronics and programming classes.

Over the last couple of months I have been assembling these so we now have 15 kits ready to go. Each one has enough gear for 2-6 students, fits into a 7L Sistema plastic box and contains the following items

2 x Netduino 2 Plus devices
2 x Seeedstudio Grove Starter kits for Arduino which contain

  • 1xBase Shield
  • 1xGrove – LCD RGB Backlight
  • 1xGrove – Smart Relay
  • 1xGrove – Buzzer
  • 1xGrove – Sound Sensor
  • 1xGrove – Touch Sensor
  • 1xGrove – Rotary Angle Sensor
  • 1xGrove – Temperature Sensor
  • 1xGrove – Light Sensor
  • 1xGrove – Button
  • 1xGrove LED Blue-Blue
  • 1xGrove LED Green-Green
  • 1xGrove  LED Red-Red
  • 1xMini Servo
  • 10xGrove Cables
  • 1x9V to Barrel Jack Adapter
  • 1xGrove starter kit Manual
  • 1xGreen Plastic Box
  • 1 x ultrasonic ranger

In addition to the Netduino devices and the Grove starter kits, we also include

Thanks to Embedded coolness, Secret Labs, and Seeedstudio which discounted their products so our funding went further.

CodeClub Programming and electronics kits

CodeClub Programming and electronics kits

Silicon Labs Si7005 Device Driver oddness

I have been working on a Netduino I2C driver for the Silicon Labs Si7005 Digital I2C Humidity & Temperature Sensor for weather station and building monitoring applications as it looks like a reasonably priced device which is not to complex to interface with.I’m using a SeeedStudio Grove – Temperature&Humidity Sensor (High-Accuracy & Mini) for development.

The first time I try and read anything from the device it fails. Otherwise my driver works as expected.

Netduino 2 Plus & Silicon Labs Si7005

Bill of materials (prices as at April 2015)

  • Netduino Plus 2 USD60 NZD108
  • Grove – Temperature&Humidity Sensor (High-Accuracy & Mini) USD11.50
  • Grove – Base Shield USD8.90

This code just shows the flow, I’ll package into a driver shortly

I strobe the I2C line which seems to help

using (OutputPort i2cPort = new OutputPort(Pins.GPIO_PIN_SDA, true))
{
   i2cPort.Write(false);
   Thread.Sleep(1000);
}

I then try and read the Device ID (0x50) from register 0X11 but this (and any other read fails)

byte[] writeBuffer = { RegisterIdDeviceId };
byte[] readBuffer = new byte[1];

I2CDevice.I2CTransaction[] action = new I2CDevice.I2CTransaction[] 
{ 
   I2CDevice.CreateWriteTransaction(writeBuffer),
   I2CDevice.CreateReadTransaction(readBuffer)
};

int length = device.Execute(action, TransactionTimeoutMilliseconds);
Debug.Print(&quot;Byte count &quot; + length.ToString());
foreach (byte Byte in readBuffer)
{
   Debug.Print(Byte.ToString(&quot;X2&quot;));
}

I can read the temperature and humidity by writing to the command register

byte[] writeBuffer = { RegisterIdConiguration, CMD_MEASURE_TEMP };

I2CDevice.I2CTransaction[] action = new I2CDevice.I2CTransaction[] 
{ 
   I2CDevice.CreateWriteTransaction(writeBuffer),
};

int length = device.Execute(action, TransactionTimeoutMilliseconds);
Debug.Print(&quot;Byte count&quot; + length.ToString());

Then poll for measurement process to finish

conversionInProgress = true
do
{
   byte[] writeBuffer = { RegisterIdStatus };
   byte[] readBuffer = new byte[1];

   I2CDevice.I2CTransaction[] action = new I2CDevice.I2CTransaction[] 
   { 
      I2CDevice.CreateWriteTransaction(writeBuffer4),
      I2CDevice.CreateReadTransaction(readBuffer4)
   };

   int length = device.Execute(action, TransactionTimeoutMilliseconds);
   Debug.Print(&quot;Byte count &quot; + length.ToString());
   foreach (byte Byte in readBuffer)
   {
      Debug.Print(Byte.ToString());
   }

   if ((readBuffer[RegisterIdStatus] &amp;&amp; STATUS_RDY_MASK) != STATUS_RDY_MASK)
   {
      conversionInProgress = false;
   }
} while (conversionInProgress);

Then finally read and convert the value

byte[] writeBuffer = { REG_DATA_H };
byte[] readBuffer = new byte[2];

I2CDevice.I2CTransaction[] action = new I2CDevice.I2CTransaction[] 
{ 
   I2CDevice.CreateWriteTransaction(writeBuffer),
   I2CDevice.CreateReadTransaction(readBuffer)
};

int length = device.Execute(action, TransactionTimeoutMilliseconds);
Debug.Print(&quot;Byte count &quot; + length.ToString());
foreach (byte Byte in readBuffer)
{
   Debug.Print(Byte.ToString());
}

int temp = readBuffer[0];

temp = temp &lt;&lt; 8;
temp = temp + readBuffer[1];
temp = temp &gt;&gt; 2;

double temperature = (temp / 32.0) - 50.0;

Debug.Print(&quot; Temp &quot; + temperature.ToString(&quot;F1&quot;));