Beer IoT: Moving to ITemperatureClient interface

My previous blog post “Measuring temperature with Windows 10 IoT Core and Raspberry Pi” introduced you my simple solution for measuring temperatures. In this blog post we go step further and make some modifications to solution architecture so we don’t have to keep sensors connected all the time and as a result we can also emulate temperatures and situations that are not easy to produce in home or office.

Update. Source code of my TemperatureStation solution is available at Github. The solution contains IoT background service and web application you can use right away. Readings are reported to MSSQL or Azure IoT Hub. Documentation is available at TemperatureStation wiki. Feel free to try out the solution.

In short we do the following:

  1. We define interface for client classes that measure temperatures.
  2. We define model class for measurements (value, time, device id),
  3. We create one client to read sensors and one client to return fake values,
  4. We modify start-up class so it uses one of the client classes.

What we actually do is simple code organization task that results in better and more flexible arcitecture.

ITemperatureClient interface

To have more than one way to read temperatures I defined interface ITempratureClient.

internal interface ITemperatureClient : IDisposable
{
    IEnumerable<Measurement<double>> Read();
}

It has just one method to read temperatures from sensors. To avoid using Tuples and other unearthly code constructs I defined generic class Measurement<T>.

internal class Measurement<T>
{
    public string DeviceId { get; set; }
    public DateTime Time { get; set; }
    public T Value { get; set; }}

With measurement we provide also device ID so we can match measured value with device where it came from.

Client for DS18B20

First we write client class to get temperatures from real sensors.

internal class TemperatureClient : ITemperatureClient
{
    private OneWireDeviceHandler _handler;
    private IEnumerable<DS18B20> _devices;
 
    public IEnumerable<Measurement<double>> Read()
    {
        var temperatures = new List<Measurement<double>>();
 
        if (_handler == null)
        {
            _handler = new OneWireDeviceHandler(false, false);
            _devices = _handler.OneWireDevices.GetDevices<DS18B20>();
        }
 
        var stamp = DateTime.Now;
 
        foreach (var device in _devices)
        {
            var result = device.GetTemperature(); 
            var temp = new Measurement<double>();
            temp.DeviceId = device.OneWireAddressString;
            temp.Time = stamp;
            temp.Value = result;
             temperatures.Add(temp);
        }
 
        return temperatures;
    }
 
    public void Dispose()
    {
        if (_handler != null)
        {
            _handler.Dispose();
            _handler = null;
            _devices = null;
        }
    }
}

This code is just taken from StartupTask class we created in blog post Measuring temperature with Windows 10 IoT Core and Raspberry Pi.

Fake client

Now let’s create fake client we can use when thermal solution is not connected to RaspberryPi or when thermal solution is not working for some reason.

internal class TemperatureClientEmulator : ITemperatureClient
{
    public IEnumerable<Measurement<double>> Read()
    {
        var temperatures = new List<Measurement<double>>();
        var now = DateTime.Now;
 
        temperatures.Add(new Measurement<double> { 
DeviceId =
"1", Time = now, Value = 12 });
        temperatures.Add(new Measurement<double> {
DeviceId =
"2", Time = now, Value = 20 });
          return temperatures;     }       public void Dispose()     {     } }

This class just returns some numbers and this is minimum we need to make our code think that temperatures are coming in from somewhere.

Some scenarios for fake client:

  • emulate some previously measured set of temperatures,
  • emulate extreme temperatures you cannot produce in your home or office,
  • emulate faulty sensors.
  • emulate unexpected situations.

Startup task

As a last thing let’s make start-up task work with new client classes.

public sealed class StartupTask : IBackgroundTask
{
    private Timer _timer;
    private ITemperatureClient _tempClient;
    private bool _isClosing = false;
 
    public void Run(IBackgroundTaskInstance taskInstance)
    {
        taskInstance.Canceled += TaskInstance_Canceled;
 
        _tempClient = new TemperatureClient();
        _timer = new Timer(TemperatureCallback, null, 0, 5000);
 
        while (!_isClosing)
        {
            Task.Delay(2000).Wait();
        }
    }
 
    private void TemperatureCallback(object state)
    {
        foreach (var temp in _tempClient.Read())
        {
            Debug.WriteLine(temp.Time + " " + temp.DeviceId + ": " + temp.Value);
        }
    }
 
    private void TaskInstance_Canceled(IBackgroundTaskInstance sender, 
BackgroundTaskCancellationReason reason)
    {         _isClosing = true;           if (_timer != null)         {             _timer.Dispose();             _timer = null;         }           if (_tempClient != null)         {             _tempClient.Dispose();             _tempClient = null;         }           sender.GetDeferral().Complete();     } }

Now we can choose what implementation we want to use for thermal solution.

Wrapping up

Now we have way better architecture for our background application that runs on Windows 10 IoT Core. We can use real device if needed and when we don’t have it we can use fake client to emulate reading of sensors. Our fake class is extremely primitive but we can modify it so it also meets our more complex needs.

Gunnar Peipman

Gunnar Peipman is ASP.NET, Azure and SharePoint fan, Estonian Microsoft user group leader, blogger, conference speaker, teacher, and tech maniac. Since 2008 he is Microsoft MVP specialized on ASP.NET.

    One thought on “Beer IoT: Moving to ITemperatureClient interface

    Leave a Reply

    Your email address will not be published. Required fields are marked *