Introduction: Internet of Things

About: Dr James Moxham is a general medical practitioner in Blackwood, Australia. His interests include general family medicine, medical politics, microcontrollers and tending a rose garden. He lives on a property wi…

Measure real world things, turn a knob and move a servo on the other side of the world. This has been possible with a PC but now it is possible using inexpensive boards and low or battery power. This project uses pre-made Arduino boards and no soldering is required.


This project was inspired by this Instructable https://www.instructables.com/id/ARDUINO-ENERGY-METER/

Step 1: The Internet of Things - Smaller, Cheaper, Less Power

A standard Arduino board is now under $10. A W5100 Ethernet shield is also under $10. A LCD keypad display shield is under $6. Debugging Arduino can be done via the serial port, but for out in the field there are some advantages to having a display. Adding a display can be done in many ways, and for this project we are looking to use premade boards. There are two small catches - some pins clash with the Ethernet shield and the LCD shield, and also the display shield shorts out a few pins on the metal of the Ethernet plug, so it needs to be raised up higher.

To get a cheap board, search Arduino on ebay and sort on price+postage and “buy it now”. Then for the Ethernet board, search on Arduino Ethernet. For the display board, search Arduino LCD Keypad.

Step 2: A Small Hack - Snap Off a Pin

The Ethernet shield uses pins 10,11,12,13. The standard Arduino display (as per code on the Arduino site) uses pins 2,3,4,5,11,12. The pre- made display shield uses 4,5,6,7,8,9,10. That still clashes with the Ethernet shield, but fortunately pin 10 is not really needed, as it is used to turn off the backlight. There is a transistor controlling this light, and the simple hack is to break off pin 10 under the LCD board. The backlight now is always on, and there are no conflicts with the Ethernet shield. To remove this pin, grab it with some pliers and bend it back and forth until it breaks off. As another option, it may be possible to trace the track on the PCB and cut this.

The next issue is the height of the board. Search on ebay for Arduino Stackable Header. This project needs some 6s and some 8s, and these often come in kits. Get a few more – they are very cheap and will come in handy for other projects.

The hardware is almost done – just plug it all together.

Step 3: Wifi or Ethernet

There are 5 analog inputs on an Arduino and the LCD board uses analog 0 to read all the keypresses, so that leaves 4 analog channels and some digital ones too. The buttons could be disabled by removing analog pin 0 under the board.

It might at this point be worth mentioning wireless vs wifi. There are wifi Arduino boards available, but they are expensive – a quick check on ebay is around $57. A slightly cheaper option is to get a wifi router and configure it as a wifi repeater and use a short Ethernet patch cable. I used a TP Link router (search ebay for TP Link Repeater) which was $38. Add the $10 for the Ethernet board and it is cheaper. It is also a bit more flexible as you can add an Ethernet switch and have multiple Ethernet sockets and hence many Arduino boards. Configuring the router as a repeater is very simple – follow the instructions, log into the router, let it search for your wifi, add the password and save. The only small catch – the router has a dedicated IP address, and if it is then set up as a repeater, it asks the main router to allocate IP addresses. This means if you want to repeat a different main router it is hard to log back into the repeater! Do a factory reset if this happens.

Step 4: Talk to the Cloud

For software, there are many ways to configure things. Arduino has code to create a small webpage server, and other code to read this, so it can be done locally. Or, as in this example, we can upload to the cloud, and download at any location there is internet or wifi. This example uses Xively and their site will display the data in a graph format. This code reads 5 analog values, uploads these, then reads them back and extracts the actual values from the Xively text stream. Xively is free, and you need an account. Log in and click on Develop. Click on Add Device, and there are two numbers. The first is the device feed number which is around 9 numbers. Then there is the API key which is a longer number with numbers and letters. Copy and paste these into the code. Then add some channels – this project uses five channels and I called them Sensor1, Sensor2 etc.

Step 5: Program the Board

There are seveal important things to change in the code below. The first is the IP address. Each board has to have a unique IP address otherwise the router will get very confused. I started at IPAddress ip(192,168,1,178); and then added one to the last number. Some routers have different numbers eg 192.168.2.x and a quick check on a PC running IPCONFIG in a DOS shell will give the correct first 3 numbers.

The other number to change is the MAC address range. The range byte mac[] = {

0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEE}; is a default number – maybe change one to a different hex number on each board, eg the last one, count backwards in hex 0xED, 0xEC.

The final thing to change is whether the board is an uploader or downloader. This code does both and about half way down is this

if(!client.connected() && (millis() - lastConnectionTime > postingInterval)) {

sendData(Analog0,Analog1,Analog2,Analog3,Analog4);

// comment out either send or get data

//getData();

which is configured to send data. To fetch back that data, comment out senddata, and uncomment getdata.

There is some leftover code commented out for things like dumping out the entire text string from Xively which is handy for debugging to work out how to cut up the string and extract the individual sensor readings.

Xively can do other things such as send an SMS or other message when certain conditions are met.

Have Fun!

/*

Pachube sensor client

This sketch connects an analog sensor to Pachube (http://www.pachube.com)

using a Wiznet Ethernet shield. You can use the Arduino Ethernet shield, or

the Adafruit Ethernet shield, either one will work, as long as it's got

a Wiznet Ethernet module on board.

This example has been updated to use version 2.0 of the Pachube.com API.

To make it work, create a feed with a datastream, and give it the ID

sensor1. Or change the code below to match your feed.

Circuit:

* Analog sensor attached to analog in 0 for pushbuttons

* Ethernet shield attached to pins 10, 11, 12, 13

The LCD circuit: standard is 12,11,5,4,3,2 change to 8,9,4,5,6,7

* LCD RS pin to digital pin 12

* LCD Enable pin to digital pin 11

* LCD D4 pin to digital pin 5

* LCD D5 pin to digital pin 4

* LCD D6 pin to digital pin 3

* LCD D7 pin to digital pin 2

and cut the header pin to D10 going to the LCD display (ethernet board needs this, and on the LCD display only used to turn backlight off

Serial debug commented out now and send to LCD instead

Change IP address to a different number for each board

created 15 March 2010

modified 9 Apr 2012

by Tom Igoe with input from Usman Haque and Joe Saavedra

http://arduino.cc/en/Tutorial/PachubeClient

This code is in the public domain.

*/

#include <SPI.h>

#include <Ethernet.h>

#include <LiquidCrystal.h>

#define APIKEY "shsCNFtxuGELLZx8ehqglXAgDo9lkyBam5Zj22p3g3urH2FM" // replace your pachube api key here

#define FEEDID 970253233 // replace your feed ID

#define USERAGENT "Arduino1" // user agent is the project name

// assign a MAC address for the ethernet controller.

// Newer Ethernet shields have a MAC address printed on a sticker on the shield

// fill in your address here:

byte mac[] = {

0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEE};

// fill in an available IP address on your network here,

// for manual configuration:

IPAddress ip(192,168,1,178);

// initialize the library instance:

EthernetClient client;

// initialize the lcd library with the numbers of the interface pins

LiquidCrystal lcd(8,9,4,5,6,7);

// if you don't want to use DNS (and reduce your sketch size)

// use the numeric IP instead of the name for the server:

//IPAddress server(216,52,233,122); // numeric IP for api.pachube.com

char server[] = "api.xively.com"; // name address for xively API

unsigned long lastConnectionTime = 0; // last time you connected to the server, in milliseconds

boolean lastConnected = false; // state of the connection last time through the main loop

const unsigned long postingInterval = 10*1000; //delay between updates to Pachube.com

int counter;

String stringOne,stringTwo; // built string when data comes back

boolean stringData = false; // reset when a new block of data comes back

void setup() {

//delay(1000); // in case the serial port causes a latchup

// Open serial communications and wait for port to open:

Serial.begin(9600);

while (!Serial) {

; // wait for serial port to connect. Needed for Leonardo only

}

Serial.println("api.xively.com"); // if go more than ?32s then integer calc doesn't work

lcd.begin(16, 2);

Cls(); // clear the screen

delay(1000);

lcd.setCursor(0,1); // x,y top left corner is 0,0

lcd.print("api.xively.com");

lcd.setCursor(0,0);

lcd.print("Start Ethernet");

////Serial.println("Start Ethernet");

delay(1000);

// start the Ethernet connection:

if (Ethernet.begin(mac) == 0) {

//Serial.println("Failed to configure Ethernet using DHCP");

// DHCP failed, so use a fixed IP address:

//lcd.setCursor(0,1);

//lcd.print("Failed to configure");

Ethernet.begin(mac, ip);

}

Serial.println("Wait 10s");

lcd.setCursor(0,1);

lcd.print("Wait 10s ");

}

void loop() {

// read the analog sensor:

int Analog0 = analogRead(A0); // with a LCD display analog0 is all the buttons

int Analog1 = analogRead(A1);

int Analog2 = analogRead(A2);

int Analog3 = analogRead(A3);

int Analog4 = analogRead(A4);

// int sensorReading = analogRead(A2);

// if there's incoming data from the net connection.

// send it out the serial port. This is for debugging

// purposes only:

if (client.available()) {

char c = client.read();

Serial.print(c);

if (stringData == false)

{

stringData = true; // some data has come in

}

if (stringData == true)

{

stringOne += c; // build the string

}

if ((c>32) and (c<127))

{

lcd.print(c);

counter +=1;

}

if (counter==16)

{

lcd.setCursor(0,1);

//lcd.print(" ");

//lcd.setCursor(0,1);

counter = 0;

//delay(100);

}

}

// if there's no net connection, but there was one last time

// through the loop, then stop the client:

if (!client.connected() && lastConnected) {

//Serial.println();

//Serial.println("Disconnect");

client.stop();

lcd.setCursor(0,0);

lcd.print("Disconnect ");

lcd.setCursor(0,1);

counter = 0;

if (stringData == true)

{

PrintResults(); // extract the values and print out

stringData = false; // reset the flag

stringOne = ""; // clear the string

}

}

// if you're not connected, and ten seconds have passed since

// your last connection, then connect again and send data:

if(!client.connected() && (millis() - lastConnectionTime > postingInterval)) {

//sendData(Analog0,Analog1,Analog2,Analog3,Analog4);

// comment out either send or get data

getData();

}

// store the state of the connection for next time through

// the loop:

lastConnected = client.connected();

}

void PrintResults() // print results of the GET from xively

{

int n = 292; // start at the sensor data

int i;

char lf = 10;

int v;

Cls();

lcd.setCursor(0,0);

stringOne += lf; // add an end of line character

for(i=0;i<5;i++)

{

while (stringOne.charAt(n) != 44) // find first comma

{

n +=1;

}

n += 1;

while (stringOne.charAt(n) != 44) // find second comma

{

n+=1 ;

}

n+=1;

stringTwo = "";

while (stringOne.charAt(n) != 10) // find the end of the line which is a line feed ascii 10

{

//lcd.print(stringOne.charAt(n));

stringTwo+=stringOne.charAt(n);

n+=1;

}

v=stringTwo.toInt();

lcd.print(v);

lcd.print(" "); // space at end

if (i==1)

{

lcd.setCursor(0,1);

}

}

}

void Cls() // clear LCD screen

{

lcd.setCursor(0,0);

lcd.print(" "); // clear lcd screen

lcd.setCursor(0,1);

lcd.print(" ");

}

void PrintValues(int n0,int n1,int n2, int n3, int n4)

{

//Serial.print(n0);

//Serial.print(" ");

//Serial.print(n1);

//Serial.print(" ");

//Serial.print(n2);

//Serial.print(" ");

//Serial.print(n3);

//Serial.print(" ");

//Serial.println(n4);

Cls();

lcd.setCursor(0,0);

lcd.print(n0);

lcd.print(" ");

lcd.print(n1);

lcd.setCursor(0,1);

lcd.print(n2);

lcd.print(" ");

lcd.print(n3);

lcd.print(" ");

lcd.print(n4);

delay(2000);

}

// this method makes a HTTP connection to the server:

void sendData(int data0,int data1,int data2,int data3, int data4) {

PrintValues(data0,data1,data2,data3,data4);

//Serial.println("Connecting...");

lcd.setCursor(0,0);

lcd.print("Connecting... ");

lcd.setCursor(0,1);

lcd.print("No reply "); // if there is a reply this will very quickly get overwritten

lcd.setCursor(0,1);

counter = 0;

// if there's a successful connection:

if (client.connect(server, 80)) {

client.print("PUT /v2/feeds/");

client.print(FEEDID);

client.println(".csv HTTP/1.1");

client.println("Host: api.pachube.com");

client.print("X-PachubeApiKey: ");

client.println(APIKEY);

client.print("User-Agent: ");

client.println(USERAGENT);

client.print("Content-Length: ");

// calculate the length of the sensor reading in bytes:

// 8 bytes for "sensor1," + number of digits of the data:

//int thisLength = 8 + getLength(thisData);

//client.println(thisLength);

// 8 is length of sensor1 and 2 more for crlf

int stringLength = 8 + getLength(data0) + 10 + getLength(data1) + 10 + getLength(data2) + 10 + getLength(data3) + 10 + getLength(data4);

client.println(stringLength);

// last pieces of the HTTP PUT request:

client.println("Content-Type: text/csv");

client.println("Connection: close");

client.println();

// here's the actual content of the PUT request:

client.print("sensor1,");

client.println(data0);

client.print("sensor2,");

client.println(data1);

client.print("sensor3,");

client.println(data2);

client.print("sensor4,");

client.println(data3);

client.print("sensor5,");

client.println(data4);

//Serial.println("Wait for reply"); // xively responds with some text, if nothing then there is an error

lcd.setCursor(0,1);

lcd.print("Wait for reply ");

}

else {

// if you couldn't make a connection:

//Serial.println("connection failed");

//Serial.println();

//Serial.println("so disconnecting.");

client.stop();

//lcd.setCursor(0,1);

//lcd.print("Connect Fail");

}

// note the time that the connection was made or attempted:

lastConnectionTime = millis();

}

// this method makes a HTTP connection to the server:

void getData() {

// if there's a successful connection:

if (client.connect(server, 80)) {

//Serial.println("connecting to request data...");

lcd.setCursor(0,0);

lcd.print("Connect ");

client.print("GET /v2/feeds/");

client.print(FEEDID);

client.println(".csv HTTP/1.1");

client.println("Host: api.pachube.com");

client.print("X-PachubeApiKey: ");

client.println(APIKEY);

client.print("User-Agent: ");

client.println(USERAGENT);

client.println("Content-Type: text/csv");

client.println("Connection: close");

client.println();

//Serial.println("Finished requesting, wait for response.");

lcd.setCursor(0,1);

lcd.print("Finish request");

}

else {

// if you couldn't make a connection:

//Serial.println("connection failed");

//Serial.println();

//Serial.println("so disconnecting.");

client.stop();

}

// note the time that the connection was made or attempted:

lastConnectionTime = millis();

}

// This method calculates the number of digits in the

// sensor reading. Since each digit of the ASCII decimal

// representation is a byte, the number of digits equals

// the number of bytes:

int getLength(int someValue) {

// there's at least one byte:

int digits = 1;

// continually divide the value by ten,

// adding one to the digit count for each

// time you divide, until you're at 0:

int dividend = someValue /10;

while (dividend > 0) {

dividend = dividend /10;

digits++;

}

// return the number of digits:

return digits;

}

Step 6: VB Net and Xively

After testing this out for several months I have come across some problems with the ethernet shield reliability. This is mainly if there are multiple router/repeater hops and might be due to timeout delays. There are problems with coping with semi-reliable connections which of course includes radio links that may be subject to interference. There may also be bugs in the standard Arduino ethernet shield code - there do seem to be a number of fixes on the internet but I am not sure which ones work. It is not the easiest to debug as the whole system will run for several days and then hang.

A hardware hack is to have one Arduino controlling a relay and turning on the power to a second Arduino that has an ethernet shield. Then the whole system can be powered down and then powered back up again.

Another option might be to look at wifi modules - this year (2014) they have come down to as low as $5, and these might eventually come with code that hopefully fails more gracefully, or perhaps can be reset with software.

Another fix is to use a computer as the internet interface. A small netbook will do. The following code is vb.net and listens to the arduino on a com port and then uploads the data to xively.


Imports System
Imports System.IO Imports System.Net Imports System.Text ' create a form. From the toolbox add button1, textbox1, textbox2, timer1, serialport1 ' change the timer1 ticks to 4000. Change timer1 enabled to True ' in the opencomport routine, change the com port number ' add checkbox1, name it upload continuously
' Arduino test code
'// sends an increasing number every 5 secs
'int n;
'void setup()  
'{
'  Serial.begin(9600); // also talk at a slow 1200 baud - easier debugging if all baud rates the same
'  while (!Serial) {} ; //wait to connect
'}
'void loop() // run over and over
'{
'  Serial.println(n);
'  n += 1;
'  delay(5000);
'}
Public Class Form1
    Public InPacket(0 To 2000) As Byte
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        OpenComPort()
    end sub
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        xivelyFeedUpdate("shsCNFtxuGELLZx8ehqglXAgDo9lkyBam5Zj22p3g3urH2FM", "970253233", "sensor1", "14")
    End Sub
    Sub xivelyFeedUpdate(ByVal ApiKey As String, ByVal feedId As String, ByVal channel As String, ByVal value As String)
        Dim request As WebRequest = WebRequest.Create("http://api.xively.com/v2/feeds/" + feedId + ".csv")
        Dim postData As String
        postData = channel + "," + value ' eg sensor1,5       ' build string to send
        Dim byteArray As Byte() = Encoding.UTF8.GetBytes(postData)
        request.Method = "PUT"                                ' PUT or GET
        request.ContentLength = byteArray.Length              ' the length of channel and value
        request.ContentType = "text/csv"                      ' text and comma separated data
        request.Headers.Add("X-ApiKey", ApiKey)               ' send the header
        request.Timeout = 5000
        Try
            Dim dataStream As Stream = request.GetRequestStream() ' Get the request stream.
            dataStream.Write(byteArray, 0, byteArray.Length)      ' Write the data to the request stream.
            dataStream.Close()                                    ' Close the Stream object.
            Dim response As WebResponse = request.GetResponse()   ' Get the response - usually just Ok
            ' need to add a try/catch error routine here in case the internet connection goes down
            TextBox1.Text += CType(response, HttpWebResponse).StatusDescription        ' Display the status.
            dataStream = response.GetResponseStream()             ' Get the stream containing content returned by the server.
            Dim reader As New StreamReader(dataStream)            ' Open the stream using a StreamReader for easy access.
            Dim responseFromServer As String = reader.ReadToEnd() ' Read the content.
            TextBox1.Text += responseFromServer                   ' Display the content.
            reader.Close()                                        ' close the streams
            dataStream.Close()
            response.Close()
        Catch ex As Exception
            TextBox1.Text = "No connection"
        End Try
    End Sub
    Sub OpenComPort()
        Try
            SerialPort1.PortName = "COM9" ' windows key, "control panel", device manager, serial ports to find the number
            SerialPort1.BaudRate = "9600"
            SerialPort1.Parity = IO.Ports.Parity.None ' no parity
            SerialPort1.DataBits = 8 ' 8 bits
            SerialPort1.StopBits = IO.Ports.StopBits.One ' one stop bit
            'SerialPort1.ReadTimeout = 1000 ' milliseconds so times out in 1 second if no response
            SerialPort1.Open() ' open the port
            SerialPort1.DiscardInBuffer() ' clear the input buffer
            'SerialPort1.Handshake = System.IO.Ports.Handshake.RequestToSend  'handshaking on (or .None to turn off)
        Catch ex As Exception
            MsgBox("Error opening serial port - is another program using the selected COM port?")
        End Try
    End Sub
    Private Sub Timer1_Tick_1(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        Dim BytesToRead As Integer
        Dim i As Integer
        Dim Character As String
        ' collect bytes from the serial port
        Timer1.Enabled = False
        TextBox2.Clear() ' clear the text box
        If SerialPort1.IsOpen = True Then
            Do
                If SerialPort1.BytesToRead = 0 Then Exit Do ' no more bytes
                BytesToRead = SerialPort1.BytesToRead
                If BytesToRead > 2000 Then BytesToRead = 2000
                SerialPort1.Read(InPacket, 0, BytesToRead) ' read in a packet
                For i = 1 To BytesToRead
                    Character = Strings.Chr(InPacket(i - 1))
                    TextBox2.Text += Character ' add to the text box
                Next
            Loop
            If CheckBox1.Checked = True Then
                TextBox1.Clear()
                xivelyFeedUpdate("shsCNFtxuGELLZx8ehqglXAgDo9lkyBam5Zj22p3g3urH2FM", "970253233", "sensor1", Str(Val(TextBox2.Text)))
            End If
        End If
        Timer1.Enabled = True
    End Sub
End Class