This book will cover a series of projects for Raspberry Pi that cover very common and handy use cases within Internet of Things (IoT). These projects include the following:
Before delving into the different protocols used in Internet of Things, we will dedicate some time in this chapter to set up some of these projects, present circuit diagrams, and perform basic measurement and control operations, which are not specific to any communication protocol. The following chapters will then use this code as the basis for the new code presented in each chapter.
All of the source code presented in this book is available for download. The source code for this chapter and the next one can be downloaded from https://github.com/Clayster/Learning-IoT-HTTP.
Along with the project preparation phase, you will also learn about some of the following concepts in this chapter:
Our first project will be the sensor project. Since it is the first one, we will cover it in more detail than the following projects in this book. A majority of what we will explore will also be reutilized in other projects as much as possible. The development of the sensor is broken down into six steps, and the source code for each step can be downloaded separately. You will find a simple overview of this here:
I assume that you are familiar with Raspberry Pi and have it configured. If not, refer to http://www.raspberrypi.org/help/faqs/#buyingWhere.
In our examples, we will use Model B with the following:
All our examples will be developed on a remote PC (for instance, a normal working laptop) using C# (C + + + + if you like to think of it this way), as this is a modern programming language that allows us to do what we want to do with IoT. It also allows us to interchange code between Windows, Linux, Macintosh, Android, and iOS platforms.
Once a project is compiled, executable files are deployed to the corresponding Raspberry Pi (or Raspberry Pi boards) and then executed. Since the code runs on .NET, any language out of the large number of CLI-compatible languages can be used.
Development tools for C# can be downloaded for free from http://xamarin.com/.
To prepare Raspberry for the execution of the .NET code, we need to install Mono, which contains the Common Language Runtime for .NET that will help us run the .NET code on Raspberry. This is done by executing the following commands in a terminal window in Raspberry Pi:
$ sudo apt-get update $ sudo apt-get upgrade $ sudo apt-get install mono-complete
Your device is now ready to run the .NET code.
To facilitate the development of IoT applications, this book provides you with the right to use seven Clayster libraries for private and commercial applications. These are available on GitHub with the downloadable source code for each chapter. Of these seven libraries, two are provided with the source code so that the community can extend them as they desire. Furthermore, the source code of all the examples shown in this book is also available for download.
The following Clayster libraries are included:
Our sensor prototype will measure three things: light, temperature, and motion. To summarize, here is a brief description of the components:
For an introduction to GPIO on Raspberry Pi, please refer to http://www.raspberrypi.org/documentation/usage/gpio/.
Two guides on GPIO pins can be found at http://elinux.org/RPi_Low-level_peripherals.
For more information, refer to http://pi.gadgetoid.com/pinout.
The following figure shows a circuit diagram of our prototype board:
We also need to create a console application project in Xamarin. Appendix A, Console Applications, details how to set up a console application in Xamarin and how to enable event logging and then compile, deploy, and execute the code on Raspberry Pi.
Interaction with our hardware is done using corresponding classes defined in the Clayster.Library.RaspberryPi
library, for which the source code is provided. For instance, digital output is handled using the
DigitalOutput
class and digital input with the DigitalInput
class. Devices connected to an I2C bus are handled using the I2C
class. There are also other generic classes, such as
ParallelDigitalInput
and
ParallelDigitalOutput
, that handle a series of digital input and output at once. The
SoftwarePwm
class handles a software-controlled pulse-width modulation output. The
Uart
class handles communication using the UART port available on Raspberry Pi. There's also a subnamespace called Devices
where device-specific classes are available.
In the end, all classes communicate with the static GPIO
class, which is used to interact with the GPIO layer in Raspberry Pi.
Each class has a constructor that initializes the corresponding hardware resource, methods and properties to interact with the resource, and finally a Dispose
method that releases the resource.
It is very important that you release the hardware resources allocated before you terminate the application. Since hardware resources are not controlled by the operating system, the fact that the application is terminated is not sufficient to release the resources. For this reason, make sure you call the Dispose
methods of all the allocated hardware resources before you leave the application. Preferably, this should be done in the finally
statement of a try
-finally
block.
The hardware interfaces used for our LEDs are as follows:
private static DigitalOutput executionLed = new DigitalOutput (23, true); private static DigitalOutput measurementLed = new DigitalOutput (24, false); private static DigitalOutput errorLed = new DigitalOutput (25, false); private static DigitalOutput networkLed = new DigitalOutput (18, false);
We use a DigitalInput
class for our motion detector:
private static DigitalInput motion = new DigitalInput (22);
With our temperature sensor on the I2C bus, which limits the serial clock frequency to a maximum of 400 kHz, we interface as follows:
private static I2C i2cBus = new I2C (3, 2, 400000); private static TexasInstrumentsTMP102 tmp102 = new TexasInstrumentsTMP102 (0, i2cBus);
We interact with the light sensor using an analog-to-digital converter as follows:
private static AD799x adc = new AD799x (0, true, false, false, false, i2cBus);
The sensor data values will be represented by the following set of variables:
private static bool motionDetected = false; private static double temperatureC; private static double lightPercent; private static object synchObject = new object ();
Historical values will also be kept so that trends can be analyzed:
private static List<Record> perSecond = new List<Record> (); private static List<Record> perMinute = new List<Record> (); private static List<Record> perHour = new List<Record> (); private static List<Record> perDay = new List<Record> (); private static List<Record> perMonth = new List<Record> ();
Persisting data is simple. This is done using an
object database. This object database analyzes the class definition of objects to persist and dynamically creates the database schema to accommodate the objects you want to store. The object database is defined in the Clayster.Library.Data
library. You first need a reference to the object database, which is as follows:
internal static ObjectDatabase db;
Then, you need to provide information on how to connect to the underlying database. This can be done in the .config
file of the application or the code itself. In our case, we will specify a SQLite database and provide the necessary parameters in the code during the startup:
DB.BackupConnectionString = "Data Source=sensor.db;Version=3;"; DB.BackupProviderName = "Clayster.Library.Data.Providers." + "SQLiteServer.SQLiteServerProvider";
Finally, you will get a proxy object for the object database as follows. This object can be used to store, update, delete, and search for objects in your database:
db = DB.GetDatabaseProxy ("TheSensor");
By doing this, the sensor does not lose data if Raspberry Pi is restarted.
To facilitate the interchange of sensor data between devices, an interoperable sensor data format based on XML is provided in the Clayster.Library.IoT
library. There, sensor data consists of a collection of nodes that report data ordered according to the timestamp. For each timestamp, a collection of fields is reported. There are different types of fields available: numerical, string, date and time, timespan, Boolean, and enumeration-valued fields. Each field has a field name, field value of the corresponding type and the optional readout type (if the value corresponds to a momentary value, peak value, status value, and so on), a field status, or Quality of Service value and localization information.
The Clayster.Library.IoT.SensorData
namespace helps us export sensor data information by providing an abstract interface called ISensorDataExport
. The same logic can later be used to export to different sensor data formats. The library also provides a class named ReadoutRequest
that provides information about what type of data is desired. We can use this to tailor the data export to the desires of the requestor.
The export starts by calling the Start()
method on the sensor data export module and ends with a call to the End()
method. Between these two, a sequence of StartNode()
and EndNode()
method calls are made, one for each node to export. To simplify our export, we then call another function to output data from an array of Record
objects that contain our data. We use the same method to export our momentary values by creating a temporary Record
object that would contain them:
private static void ExportSensorData (ISensorDataExport Output, ReadoutRequest Request) { Output.Start (); lock (synchObject) { Output.StartNode ("Sensor"); Export (Output, new Record[] { new Record (DateTime.Now, temperatureC, lightPercent, motionDetected) },ReadoutType.MomentaryValues, Request); Export (Output, perSecond, ReadoutType.HistoricalValuesSecond, Request); Export (Output, perMinute, ReadoutType.HistoricalValuesMinute, Request); Export (Output, perHour, ReadoutType.HistoricalValuesHour, Request); Export (Output, perDay, ReadoutType.HistoricalValuesDay, Request); Export (Output, perMonth, ReadoutType.HistoricalValuesMonth, Request); Output.EndNode (); } Output.End (); }
For each array of Record
objects, we then export them as follows:
The
Export
method exports an enumeration of Record
objects as follows. First it checks whether the corresponding readout type is desired by the client before exporting data of this type. The method also checks whether the data is within any time interval requested and that the fields are of interest to the client. If a data field passes all these tests, it is exported by calling any of the instances of the overloaded method ExportField()
, available on the sensor data export object. Fields are exported between the StartTimestamp()
and EndTimestamp()
method calls, defining the timestamp that corresponds to the fields being exported:
private static void Export(ISensorDataExport Output, IEnumerable<Record> History, ReadoutType Type,ReadoutRequest Request) { if((Request.Types & Type) != 0) { foreach(Record Rec in History) { if(!Request.ReportTimestamp (Rec.Timestamp)) continue; Output.StartTimestamp(Rec.Timestamp); if (Request.ReportField("Temperature")) Output.ExportField("Temperature",Rec.TemperatureC, 1,"C", Type); if(Request.ReportField("Light")) Output.ExportField("Light",Rec.LightPercent, 1, "%", Type); if(Request.ReportField ("Motion")) Output.ExportField("Motion",Rec.Motion, Type); Output.EndTimestamp(); } } }
We can test the method by exporting some sensor data to XML using the SensorDataXmlExport
class. It implements the ISensorDataExport
interface. The result would look something like this if you export only momentary and historic day values.
<?xml version="1.0"?> <fields xmlns="urn:xmpp:iot:sensordata"> <node nodeId="Sensor"> <timestamp value="2014-07-25T12:29:32Z"> <numeric value="19.2" unit="C" automaticReadout="true" momentary="true" name="Temperature"/> <numeric value="48.5" unit="%" automaticReadout="true" momentary="true" name="Light"/> <boolean value="true" automaticReadout="true" momentary="true" name="Motion"/> </timestamp> <timestamp value="2014-07-25T04:00:00Z"> <numeric value="20.6" unit="C" automaticReadout="true" name="Temperature" historicalDay="true"/> <numeric value="13.0" unit="%" automaticReadout="true" name="Light" historicalDay="true"/> <boolean value="true" automaticReadout="true" name="Motion" historicalDay="true"/> </timestamp> ... </node> </fields>