Introduction: Household Environmental Monitor IoT Solution

About: I am an IT professional working in Big Data. I've also been messing around with IoT devices such as Arduino, Raspberry PI and electronics for decades. I also have a passion for both giving and receiving knowle…

In my previous apartment, we found ourselves in a bit of a, lets say, "debate" with the building's owners corporation about the effectiveness of the air conditioning system.

Our contention was that the air conditioner barely gave any relief from our hot summers. Their retort was that no one else complained.

So, I decided to collect evidence. This Instructable describes a simple "IoT solution" that I built to gather that evidence. It features:

  • Multiple Arduino based sensor devices scattered around the apartment .
  • A Raspberry PI based server operating 24x7 that collected the data.
  • A MS-Excel based report to visualise the collected data.

In my case I was only interested in temperature. But I also captured humidity because the sensor I was using (DHT-22) measured both temperature and humidity. The project can easily be extended to capture as many (or few) environmental parameters as you can find sensors.

The project could also be extended to actuate environmental controls. For example, when you look at the graphs, you will notice sudden spikes in one of the sensor's temperature readings. This is not a glitch. It is in fact the sun coming out of a shadow and shining directly through the window onto the sensor. We quickly resolve the situation by closing the blind. But, if I had the right hardware, there is no reason why the remote sensor device (i.e. the Arduino) couldn't close the blind for us automatically when the temperature spiked.

I hope that this project might give you some ideas for your own home automation. Let me know if you build it and/or extend it.

The instructions and samples are for three Arduinos with one DHT-22 connected to each one. I recommend getting the 3 working first, then add on to the network if that is your goal. If you only have 1 or 2 Arduinos and sensors, then that is OK, you will just get some invalid values showing in the report (i.e. the graph) for the missing devices.

If you do not have a Raspberry PI or Excel, that is OK also, but you will need to tweak the project to allow for that. I give some tips as to some alternatives in the steps below. This Instructable is already pretty long, so unfortunately I could not provide details for all possible configurations.


If you find my projects helpful, please consider supporting me by buying me a coffee.


Step 1: What Will You Need

There are many, many, many different ways you could build this project. I will outline what I did because it worked for me. If you build it another way, please post your solution, so that others can benefit from it.

To build my sensor network you will need the following:

Sensor device(s)

You will need one of these for each location you intend to monitor.

  • Arduino with Ethernet, so some of any of these (ideally just use one configuration instead of mixing and matching):
    • Arduino Ethernet
    • Arduino Etherten (I tested these)
    • Arduino Uno + Ethernet shield (I tested this combination)
    • Arduino Leonardo + Ethernet shield (I tested this combination)
    • Any other Arduino that can support an Ethernet shield/module
      NB: I did not use Wireless (e.g. WiFi) because I did not have WiFi hardware. I had Ethernet hardware and I had network ports everywhere I wanted to capture data, so I used Ethernet.
  • A DHT22 temperature and humidity sensor (normally these are white)
    You can also use DHT11 (normally blue) but the range and accuracy isn't as great as the DHT22. DHT11's will require a minor change in the Arduino code.
    I used the 3 pin DHT22 "module". You can also get 4 pin sensor only DHT22's - I'm sure they will work just as well as the 3 pin DHT22 module - as they are basically the same thing. The DHT22 modules are probably a little easier to wire up as the pins are normally labelled and you must connect each one of them to Arduino.
    If you look closely in the photo, you might be able to see the 4 pins at the base of the DHT22 sensor (the big white box) on the module, but note that there are only three pins at the base of the DHT22 module.
  • A LED (optional)
  • A 470 ohm resistor (only required if you use the LED)
  • A small breadboard
  • Ethernet cable
  • Power supply (I just used the USB chargers from old mobile phones and tablets).

Note the number 5 on the Ethernet jack on the Ethernet shield. I wrote these numbers on each of the Ethernet jacks so I would know which device was going to get which Mac and IP address. I recommend you do the same. Identifying the Arduino's now will save you from having to diagnose network problems later.

Data Collector

  • Raspberry PI

Reporting

You can use any visualisation tool that you like. In my case, I used Microsoft Excel (part of Microsoft Office).

You could also use MS-Excel compatible programs such as Libre office.

You will also need

Network

  • Ethernet Switch
  • Ethernet cabling

Note for simplicity, all devices should be connected to the same sub-network. The examples in this Instructable assume the network is 192.168.1.0 with a subnet mask of 255.255.255.0. This means that all of my devices will have an IP address of 192.168.1.X where X is in the range 1 to 254. Each device will have it's own unique value for X. In my case:

  • Raspberry PI Data collector is 192.168.1.10
  • Arduino Sensors are 192.168.1.160, 192.168.1.161 and 192.168.1.163
  • Reporting PC is randomly assigned by my DHCP server. At the time of writing it was 192.168.1.106

Variations

Here are a couple of ideas for variations to the project - I won't document them any further other than mentioning them here:

  • Use WiFi instead of Ethernet (I do not have WiFi Hardware - so I used Ethernet).
  • Use another form of Wireless such as LoRa or XBee (I recently got some XBee's and am keen to try them out on something).
  • Instead of using a Raspberry PI as the data collector, run the collector on your PC's (ideally the PC will be running 24x7 to continuously collect data).
    • For windows, install Cygwin or create a virtual machine running Linux. The shell script can be run in Cygwin or the Virtual Machine.
    • For MacBook or Linux systems, simply run the shell script directly to collect the data.
  • If you want to take readings from a few locations in the vicinity of a single point (e.g. a couple of readings from different locations in a single room), simply connect 2 (or more) DHT-22's to your Arduino.
    You will need to tag the second (and third etc) readings as T1 & H1 (and T2 & H2 etc). The spreadsheet will also need to be updated to tease out the multiple readings from a single Arduino's messages.
  • Instead of polling the Arduinos from the shell script, implement a web server (i.e. setup a REST API) on your Linux system (or run a command such as nc as a server) and modify the Arduino code to be a client instead of a server. The client Arduino will periodically send it's current reading to the web server. If you do this, you do not need to allocate IP addresses to your Arduino's (you can use DHCP).
    Also, If you did this, you could probably even run the Arduino from Battery and utilise low power mode to ensure the battery lasts a long time. I would also include a battery level meter if you chose this option and bundle the reading into the message transmitted to the web server.
    However, you will need to assemble the individual messages (which will effectively be received at random times) into a single "point in time reading". The assembly of the "point in time reading" would also be done in the web server when it records the "last known status" of the Arduinos as the "current observation record" used in the reporting tools.
  • To mention but a few...

Step 2: Hooking Up the Electrical Stuff

The circuit is pretty basic. The only required component is the DHT-22 which needs just 3 wires. It is also helpful to connect up the LED which can be used to identify various conditions (without the need of the IDE's USB monitor).

I will assume you will connect the LED and therefore be using a small breadboard. If you are not using an LED, simply connect the DHT22 to the Arduino directly (no breadboard required).

Connecting the LED

  • Connect +5V to a rail on the breadboard (red wire)
  • Connect the Anode of the LED to the same rail
  • Connect the cathode of the LED to another rail on the breadboard
  • Connect the 470 Ohm resistor to the same rail as the LED's cathode.
  • Connect the other end of the resistor to Digital Pin 3 on the Arduino (Blue wire)

Connecting the DHT22

Make sure you check the pinout on your sensor. This is especially important if you elect to use a DHT11 instead of the DHT22. The DHT11 typically has a blue sensor block whereas the DHT22 typically has a white sensor block.

  • Connect - (or GND) on the DHT22 to GND on Arduino (Black wire)
  • Connect + (or 5V) on the DHT22 to the 5V rail on the breadboard (red wire)
  • Connect S (or Signal) to Digital Pin 2 on the Arduino (Yellow wire)

Raspberry PI

Apart from an Ethernet cable, no wiring is required.

Reporting PC

Apart from the Network connection (i.e. Ethernet cable or WiFi), no wiring is required. You really don't want to be hooking up wires inside your PC if you can avoid it unless you really know what you are doing. Lucky we don't need to do that this time!

Just to be safe, double check your connections.

Step 3: Overview of All Steps

Setting up the system is pretty straight forward if you are familiar with all of the technologies used in this Instructable. If you are familiar with all of the technologies, then this summary should be all you need to know to get up and running.

If you are unsure or need a recap, the steps following with one will give you more detailed guidance as to how to set it up. I would suggest starting with the summary, so you are aware of the big picture - before diving in to the details.

I recommend using just 3 (or less) Arduino's in the initial instance as this is how the sample files in this Instructable are configured. You can modify and/or extend to suit you needs once the initial version is working.

Here we go:

  1. Determine a range of contiguous IP Addresses in the same subnet as the computer you will be working on to generate the reports.
    You will need one IP address for each of the Arduino's plus 1 for the Raspberry PI (i.e. 4 addresses in total).
  2. Assign ID's to each of the Arduinos (i.e. 0, 1 and 2).
    Write this ID on a sticky label and attach it to the Arduino.
  3. Enter the base address (the lowest IP address) of your range of addresses from step 1 into the Arduino program (sketch) at about line 82, which currently looks like this:
    IPAddress ip(192, 168, 1, 160 + NW_OFFSET);
  4. Configure each of the Arduino's with a unique network address (IP and MAC). Enter the individual Arduino's unique ID (the one allocated in step 2 above) by modifying the Arduino program (sketch) at about line 31, which currently looks like this (replace the 0 with the Arduinio's ID):
    #define NW_OFFSET 0
  5. Program the Arduino with the version of the code containing its unique IP (and MAC) Address. These are determined from a combination of Steps 3 and 4.
  6. Configure the Raspberry PI with a static IP address as determined in step 1 above. This can be any available address (it doesn't have to be contiguous with the Arduinos)
  7. Modify the Raspberry PI's hosts file to contain the IP addresses of each of the Arduinos.
    Use host names S0, S1 and S2
  8. Run the Raspberry PI script (monitor.sh).
  9. Wait for some data to be collected (it will be in /tmp/monitor.txt).
  10. Open the Excel spreadsheet.
  11. Transfer the collected data (from /tmp/monitor.txt) to the computer running the Excel spreadsheet (e.g. use an FTP program such as WinSCP or similar).

  12. Copy all of the data from /tmp/monitor.txt (from step 7) and paste the entire data file into the Excel spreadsheet at cell U4.
    Refer to the sample data supplied with the spreadsheet to see what it should look like.
    You can delete the sample data tab(s) if you wish.

If you need them, the rest of this instructable expands upon the above steps. There are no additional steps below - but there is more detail. Feel free to refer to just those steps where you might need more details and skip those that you are comfortable with.

The Arduino source code can be found on my GitHub account at:

 https://github.com/gm310509/Arduino/tree/master/Household/TemperatureAndHumidityMonitor/HouseSensorEthernetService

Step 4: Program the Sensor (Arduino)

Following is the entire code for the Arduino.

Important Note

You will need to modify the code for each sensor device (Arduino) that you program.

In the parts list, I made mention of the number 5 written on the Ethernet Jack on the Ethernet Shield. This is to identify the device ID and hence the IP address of the Arduino. You should have written the numbers 0, 1 and 2 on each of your Arduino's Ethernet ports by now. If not, then I recommend that you do it now.

The number that is written on the Ethernet Jack must be entered into the program (sketch) before programming the Arduino. The number must be unique in your network. You can have as many as you like - just ensure that the IP addressed derived from this number (in the program/sketch) will result in a unique IP address in your network.

Configuring the code

There are two steps required to configure the code.

  1. Modify the base IP address to match your network.
    You only need to do this one time (unless you screw it up the first time, then you need to do it again).
  2. Allocate a unique IP address to each of your Arduino sensor devices.
    You need to do this each and every time you program an Arduino in the network. It is based upon the ID you wrote on the Ethernet Jack (e.g. 0, 1 or 2).

Initial Setup - modifying the Base IP address

At approximately line 81 in the code, you will find the following lines:

// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xA0 + NW_OFFSET };
IPAddress ip(192, 168, 1, 160 + NW_OFFSET);

The MAC address

More often that not, you can leave the mac value alone. However, if you experience random undesirable behaviour try changing one of the 2 digit hexadecimal numbers. The value doesn't really matter, so long as it doesn't work out to be the same as one of your other devices.

The IP Address

The IP Address must be configured to match your network. In my case, the network is 192.168.1.0, the subnet mask is 255.255.255.0. This means that any device that wants to talk to any other device within this network must have an address in the range of 192.168.1.1 to 192.168.1.254 inclusive.

You can determine your computers ip address easily from a command prompt as follows:

  • Windows - run the "ifconfig" command
  • Mac and Linux - run the "ifconfig" command or "ip addr"

If you want to know a bit more about how IP addresses work with subnet masks, have a look at this Wikipedia Article, this Explanation using an analogy, check out the final step in this Instructable or just Google it.

Note: using an online service to determine your IP address will most likely give you the wrong information. These online services will tell your Internet address not your computer's address. The Internet address is the address that your Router / Modem is known by to the rest of the world. These online services (normally) do not tell you the local addresses of the computer within your office/home - which is what we need.

Once you work out your IP Address replace the first three numbers in the IPAddress part of the code so that it looked like the following (in this case the my IP address might be 192.168.1.106):

IPAddress ip(192, 168, 1, 160 + NW_OFFSET);

If you found that your computers IP address was, for example, 192.10.20.255 and your subnet mask was 255.255.255.0, then modify the same line as follows:

IPAddress ip(192, 10, 20, 160 + NW_OFFSET);

Next, identify a contiguous block of available (unused) IP addresses within your network. If for example, you identified that there is a block of addresses starting at, say, 192.10.20.200) then modify the last number as follows:

IPAddress ip(192, 10, 20, 200+ NW_OFFSET);

Every Unit Setup - allocating a unique address

Once you have worked out your IP address range (and if need be a MAC address range), you will need to configure the Arduino program to correctly program the address into each individual Arduino device. To do this:

  1. Label your Arduino Ethernet ports with sequential numbers starting at 0 (i.e. 0, 1, 2, 3 etc) as per the example in the photo (which has a 5 written on it) in the parts list step.
  2. Locate the definition of the NW_OFFSET constant (at about line 29 of the program).
  3. Replace the number with the with the label you assigned in step 1 above.
  4. Important: In the Arduino IDE, ensure that you have selected the correct COM port for the Arduino you intend to program (in case you have multiple Arduinos connected to your computer).
    Better yet, only ever connect one Arduino when programming to avoid any confusion.
  5. Program the Arduino in the normal way.

As you can see from the previous section, the NW_OFFSET value will be added to the last digit in both the MAC and IP address to ensure that the Arduino get's a unique address. Trust me, you do not want to allocate the same address to two Arduino's. The troubleshooting headache isn't worth it.

Note: the maximum value of each of the four numbers is 254. So if you start the last octet of the IP address at 200, then the maximum value of NW_OFFSET is 54 (200 + 54 = 254). If you start the last octet of the IP Address at 160, then the maximum value of NW_OFFSET will be 94 (160 + 94 = 254). You can not use an octet of 255 (and I would recommend not using 0) for the last octet of the IP address. The values 200 and 160 are examples only - you must locate a starting address that is available in your network. Having said that, generally the higher numbers (like 200 and above) are available.

Tip: Try to find an IP address that is well clear of all of the Arduino IP addresses - that way, if you add more Arduinos, there is less chance of a collision.

The Arduino code

Finally here is the code.

You will need to install the following libraries via the library manager in the Arduino IDE:

  • Adafruit DHT Sensor Library
  • Adafruit Unified Sensor Library
  • Ethernet library (which should be standard "builtin" library shipped with the IDE).

/*
* HomeSensor - Ethernet Web Server
* --------------------------------
*
* A project that monitors environmental conditions in our home.
* Readings are extracted periodically via a REST API call.
*
* Intended to be used with Arduino - EtherTen, but can also be used
* with any Arduino and an Ethernet sheild.
*
* Circuit:
* EtherTen (or Ethernet shield attached to pins 10, 11, 12, 13)
* DHT Temperature and Humidity sensor Pin 2.
* Optional LED, connected to PIN 3.
*
*

*******************************************************************
* IMPORTANT IMPORTANT IMPORTANT
*
* Update the following NW_OFFSET to properly allocate a Mac address
* and an IP address.
* The first device should be 0, the next 1, the one after that 2
* and so on.
* Also, make sure your network (IP) address range is available on
* your network.
*******************************************************************/
// A value added to the MAC and TCP/IP address to ensure uniqueness
// on the network.
#define NW_OFFSET 0
/*******************************************************************
* IMPORTANT IMPORTANT
*
* Update the above NW_OFFSET to properly allocate a Mac address
* and an IP address.
*******************************************************************/

#define DEBUG
//#define STARTUP_DEBUG
#ifdef DEBUG
#define STARTUP_DEBUG
#endif

#define LED_ACTIVITY 3
#define SD_CARD 4

// See guide for details on sensor wiring and usage (DHT22):
// https://learn.adafruit.com/dht/overview

#define DHTTYPE DHT22 // DHT 22 (AM2302)
#define DHTPIN 2 // Pin which is connected to the DHT sensor.

#include
#include

//#include
#include

// Tracks the last recorded millisecond value.
// Used to track when a millisecond (or more) has passed.
unsigned long lastMillis;

#define SERVER_PORT 4000
/*******************************************************************
* IMPORTANT IMPORTANT IMPORTANT
*
* Enter a MAC address and IP address for your controller below.
* The IP address will be dependent on your local network.
* Ensure that the addresses you select are:
* a) In your network as defined by the subnet mask and
* b) Not used by another device.
*
* For simplicity, try to identify a contiguous block of addresses
* of sufficient size to have 1 address for each Arduino that you
* wish to connect.
*/
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xA0 + NW_OFFSET };
IPAddress ip(192, 168, 1, 160 + NW_OFFSET);
/*******************************************************************
* IMPORTANT IMPORTANT
*
* Update the above IP Address to properly reflect your Local Area
* Network (LAN) address range.
*******************************************************************

/ Initialize the Ethernet server library
// with the IP address and port you want to use
// (port 80 is default for HTTP):
EthernetServer server(SERVER_PORT);

String httpRequestText; // string used to capture the HTTP Request Text.

DHT_Unified dht(DHTPIN, DHTTYPE);
uint32_t delayMsDHTSensor;

/************************************************
* Class TimedTask.
*
* An abstract (incomplete) class that manages the scheduling of sub tasks.
*
* This class tracks the elapsed amount of time on behalf of it's subclasses.
* When the alotted time has passed, the "execute" method will be invoked to
* allow the task to do whatever it needs to do.
* NB: This TimedTask class would ideally be "factorised" out into a library.
* If we did this, then the entire functionality could be accessed by a single
* line of code (as opposed to the 70 odd lines here. The single line of code
* would be something like: #include .
*/
class TimedTask {
public:
// Constructor - capture the time that has to pass until the task needs to be invoked.
TimedTask(unsigned long nextEventTime) {
this->nextEventTime = nextEventTime;
if (nextEventTime == 0) { // If we construct with a nextEventTime of zero,
enabled = false; // disable the task for now.
}
}

// Set the next event time.
void setNextEventTime(unsigned long nextEventTime) {
this->nextEventTime = nextEventTime;
}

// Abstract methods which must be implemented (defined) in a subclass.
virtual unsigned long execute(); // Execute the task.
virtual void outputTaskName();
virtual void disableTask(); // Invoked when this task is being disabled.
virtual void enableTask(); // Invoked when this task is being enabled.

// Enable this task.
void enable() {
enabled = true;
timeSinceLastEvent = 0; // Reset the elapsed time counter.
enableTask(); // Notify the subclass that the task has been enabled.
}

// disable this task.
void disable() {
enabled = false;
disableTask(); // Notify the subclass that the task has been disabled.
}

// Return the enabled/disabled state of the task.
boolean isEnabled() {
return enabled;
}

// Record the fact that time has passed.
void recordTime(unsigned long delta) {
timeSinceLastEvent += delta; // Record the time and check if this task is due to be
// executed. NB: the task is only executed if it is enabled.
if (timeSinceLastEvent >= nextEventTime && enabled) {
#ifdef DEBUG
// Serial.print ("Executing task: ");
// outputTaskName();
#endif
unsigned long nev = execute(); // Notify the subclass to do it's thing.
if (nev > 0) { // Record the next event time if it is non zero.
nextEventTime = nev;
}
timeSinceLastEvent = 0; // Reset the time counter.
}
}

private:
unsigned long nextEventTime; // Time that must pass before we invoke the subtask.
unsigned long timeSinceLastEvent = 0; // The time has passed since the last invocation of the subtask.
boolean enabled = true;
};

class ActivityLED : public TimedTask {
public:
ActivityLED(int ledPin)
: TimedTask(HeartbeatLedOffTime) {
this->ledPin = ledPin; // remember the pin that the LED is connected to.
pinMode(ledPin, OUTPUT); // Set the pin as output.
digitalWrite(ledPin, HIGH); // turn the LED off.
ledOnInd = false;
mode = HeartbeatMode; // set the LED to Heartbeat mode.
}

// Time to update the status of the LED.
unsigned long execute() {
// It's time to invert the LED status (i.e. On -> off and Off -> On).
ledOnInd = !ledOnInd; // Invert the LED>
digitalWrite(ledPin, ledOnInd ? LOW : HIGH);

// If we are in Heartbeat mode (briefly flash the LED on once
// every two seconds or so.
if (mode == HeartbeatMode) {
return ledOnInd ? HeartbeatLedOnTime : HeartbeatLedOffTime;
}

// Otherwise we are either in Response mode or Ident mode.
// In these cases, Activate the LED in a pattern that includes
// multiple repeats (multiple repeats can = 1).
// If the LED is now on, count one of the repeats.
// Otherwise the LED is now off, so
// check if we need to continue the pattern
// or revert to the heartbeat pattern.
if (ledOnInd) { // Led is on. Subtract one from the cycle
cycleCntr--; // counter. Each time the LED is turned on.
} else if (cycleCntr == 0) { // LED is off and we've reached the end of the cycleCntr
mode = HeartbeatMode; // so, revert to Heartbeat mode (which is intercepted above).
return HeartbeatLedOffTime; // and return the Heatbeat led off time.
}
return cycleTime; // Otherwise just return the base delay time as defined by the setmode function.
}

// Not used, but in case it is, just turn the LED off.
void disableTask() {
digitalWrite(ledPin, HIGH); // turn the LED off.
ledOnInd = false;
}

void enableTask() {
// Nothing special to do.
}

void outputTaskName() {
Serial.print ("Activity LED. Mode: ");
Serial.println(mode);
}

// Constants for the modes.
static const int TransmittingLedOnTime = 750; // When transmiting - on for 750 ms.
static const int IdentLedTime = 150; // In IDENT mode, blink for 150 on, 150ms off.
static const int DefaultLedTime = 500; // if an unknown mode is activated, blink for 500ms on/off (should never happen)
static const int HeartbeatLedOnTime = 200; // In heartbeat mode, LED is on for 200ms and
static const int HeartbeatLedOffTime = 2300; // LED is off for 2300ms.

static const int TransmittingDataMode = 0; // Constant that identifies transmitting mode.
static const int IdentMode = 1; // Constant that identifies ident mode.
static const int HeartbeatMode = 2; // Constant that identifies heartbeat (idle) mode.

void setMode(int mode) {
this->mode = mode;
switch(mode) {
case TransmittingDataMode:
cycleCntr = 1; // Transmitting data is one cycle of LED ON
cycleTime = TransmittingLedOnTime;
break;
case IdentMode:
cycleCntr = 20; // Ident mode is 20 cycles of 150 on, 150ms off (total 3 seconds)
cycleTime = IdentLedTime;
break;
case HeartbeatMode:
break; // No need to do anything special for heartbeat mode. This is
// handled entirely in the execute method.
default:
cycleCntr = 3; // Unknown mode is 3 cycles of 500ms on / off.
cycleTime = DefaultLedTime;
break;
}
execute();
}

private:
int ledPin; // the led pin.
int mode; // the current mode of operation.
int cycleCntr; // the number of blink cycles.
unsigned long cycleTime; // the time to remain on and off.
boolean ledOnInd = false;

};

// Define the Activity LED task.
ActivityLED activityLed(LED_ACTIVITY);

void setup() {

// Open serial communications and wait for port to open (but not too long).
Serial.begin(9600);
int cnt = 0;
while (!Serial && cnt < 100) {
cnt++; // wait for serial port to connect. Needed for native USB port only
delay(1);
}
#ifdef STARTUP_DEBUG
Serial.println("HouseSensorEthernetServer");
Serial.print("LED: "); Serial.println(LED_ACTIVITY);
#endif

delay(50); // allow some time (50 ms) after powerup and sketch start,
// for the Wiznet W5100 Reset IC to release and come out of reset.

// start the Ethernet connection and the server:
Ethernet.begin(mac, ip);

// Check for Ethernet hardware present
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
Serial.println("Ethernet shield was not found. Sorry, can't run without hardware. :(");
pinMode(LED_ACTIVITY, OUTPUT);
while (true) { // do nothing, no point running without Ethernet hardware
digitalWrite(LED_ACTIVITY, LOW);
delay(200); // Fast blink the LED as an error indication in case Serial not enabled
digitalWrite(LED_ACTIVITY, HIGH);
delay(200);
}
}
if (Ethernet.linkStatus() == LinkOFF) {
Serial.println("Ethernet cable is not connected.");
}

// start the web server
server.begin();

#ifdef STARTUP_DEBUG
Serial.print("server is at ");
Serial.print(Ethernet.localIP());
Serial.print(":");
Serial.println(SERVER_PORT);
Serial.print("MAC addr: ");
for (int i = 0; i < sizeof(mac); i++) {
Serial.print(mac[i], HEX);
if (i < sizeof(mac) - 1) {
Serial.print(", ");
} else {
Serial.println();
}
}
Serial.println("Initialising DHT...");
#endif

// disable the SD card by switching pin 4 high
// not using the SD card in this program, but if an SD card is left in the socket,
// it may cause a problem with accessing the Ethernet chip, unless disabled
#ifdef STARTUP_DEBUG
Serial.println("Disabling SD Card");
#endif
pinMode(SD_CARD, OUTPUT);
digitalWrite(SD_CARD, HIGH);

#ifdef STARTUP_DEBUG
Serial.println("Initialising the DHT sensor");
#endif
dht.begin();

sensor_t sensor;
dht.temperature().getSensor(&sensor);
// Set delay between sensor readings based on sensor details.
delayMsDHTSensor = sensor.min_delay / 1000;

#ifdef DEBUG
Serial.println(" Done");
#endif

#ifdef DEBUG
Serial.println("------------------------------------");
Serial.println("Temperature");
Serial.print ("Sensor: "); Serial.println(sensor.name);
Serial.print ("Driver Ver: "); Serial.println(sensor.version);
Serial.print ("Unique ID: "); Serial.println(sensor.sensor_id);
Serial.print ("Max Value: "); Serial.print(sensor.max_value); Serial.println(" *C");
Serial.print ("Min Value: "); Serial.print(sensor.min_value); Serial.println(" *C");
Serial.print ("Resolution: "); Serial.print(sensor.resolution); Serial.println(" *C");
Serial.println("------------------------------------");
delay(delayMsDHTSensor);
// Print humidity sensor details.
dht.humidity().getSensor(&sensor);
Serial.println("------------------------------------");
Serial.println("Humidity");
Serial.print ("Sensor: "); Serial.println(sensor.name);
Serial.print ("Driver Ver: "); Serial.println(sensor.version);
Serial.print ("Unique ID: "); Serial.println(sensor.sensor_id);
Serial.print ("Max Value: "); Serial.print(sensor.max_value); Serial.println("%");
Serial.print ("Min Value: "); Serial.print(sensor.min_value); Serial.println("%");
Serial.print ("Resolution: "); Serial.print(sensor.resolution); Serial.println("%");
Serial.println("------------------------------------");
#endif

lastMillis = millis(); // Initialise the "timer".
}

void loop() {
// listen for incoming clients

EthernetClient client = server.available();
if (client) {
#ifdef DEBUG
Serial.println("new client");
#endif
// an http request ends with a blank line
boolean currentLineIsBlank = true;
// while the client is connected
while (client.connected()) {
// Check to see if some data is available.
if (client.available()) {
// If so, read the next character and assemble it into a string (the request)
char c = client.read();
httpRequestText += c; // Capture the request one character at a time.
#ifdef DEBUG
Serial.write(c); // Display the character to the monitor if debugging.
#endif
// if you've gotten to the end of the line (received a newline
// character) and the line is blank, the http request has ended,
// so you can send a reply
if (c == '\n' && currentLineIsBlank) {
// Set the activity LED to "response mode".
activityLed.setMode(ActivityLED::TransmittingDataMode);

// Process the request and obtain the response.
String msg = processRequest();

// Send the reply message as is directly back to the requesting client.
client.println(msg);
httpRequestText = ""; // Clear the request text once done with the client.
break;
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
} else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
// give the web browser time to receive the data
delay(1);
// close the connection:
client.stop();
#ifdef DEBUG
Serial.println("client disconnected");
#endif
}

// Check to see if a millisecond has passed.
// If it has, tick all of the timer handlers.
unsigned long currTime = millis();
if (lastMillis != currTime) {
unsigned long deltaTime = currTime - lastMillis;
lastMillis = currTime;
activityLed.recordTime(deltaTime);
}
}

/**
* The process request method determines what request has been made
* and executes it.
* The possible actions include:
* - Identify the device (return the MAC address and rapidly blink the LED)
* - Read the sensor (return the sensor readings)
* - Request help (return the help message)
* - Unrecognised request (return a message telling the client how to request help).
*/
String processRequest() {
String msg = "";
// Is the request an "ident" request?
if (httpRequestText.indexOf("ident ") > -1) {
// formulate a response containing the MAC address.
msg = "mac:";
for (int i = 0; i < sizeof(mac); i++) {
msg += " 0x";
msg += String (mac[i], HEX);
}
// Blink the LED in IDENT mode.
activityLed.setMode(ActivityLED::IdentMode);

// Otherwise is the request for "help"?
} else if (httpRequestText.indexOf("help") > -1) {
// Formulate a "help" response.
msg = "use: \n /help for help\n /ident to identify";
// Otherwise is the request the "main request" - i.e. no modifier on the URL.
} else if (httpRequestText.indexOf(" / HTTP") > -1) {
// Return the sensor reading.
msg = getSensorMsg();
// Otherwise, the request is unrecognised, so return advice for help.
} else {
msg = "Invalid Request try /help";
}
return msg;
}

// for sensors_event_t field definitions:
// https://github.com/adafruit/Adafruit_Sensor/blob/...
// for DHT methods:
// https://github.com/adafruit/Adafruit_Sensor/blob/...
// for DHT_Unified methods:
// https://github.com/adafruit/Adafruit_Sensor/blob/...
/*
* Read the sensor and formulate the message as follows:
* - Reading Type e.g. T for temperature, H for Humidity or any other value you care to define
* for other sensors (e.g. L for LIGHT reading, P for pressure and so on).
* - ID of the sensor (0 for the first one, 1, for the second and so on).
* - The value for the reading (or 9999.9 for an invalid reading e.g. device unavailable).
*
* - Each reading is seperated by a semi-colon. The reading type & sensor ID (2 characters) and value are separated by a comma.
* For example,
* T0,12.5;P0,1013.0;H0,87;T1,18.9;P1,9999.9;H1,50.2;
* would be interpreted as follows:
* - For sensor group 0:
* - Temperature (T0) is 12.5 degrees C
* - Humidity (H0) is 87%
* - Pressure (P0) is 1013.0 millibar
* - For sensor group 1:
* - Temperature (T1) is 18.9 degrees C
* - Humidity (H1) is 50.2%
* - Pressure (P1) is unavailable (9999.9)
*
* The following routine will return Temperature and Humidity values for a single
* sensor group (T0 and H0).
*
* The String returned from this function is returned to the client as is.
*/
String getSensorMsg() {
String msg = "";
String msg1 = "";

sensors_event_t event;

dht.temperature().getEvent(&event);
msg = "T0,";
msg += (isnan(event.temperature) ? 9999.9 : event.temperature);
msg += ";";

dht.humidity().getEvent(&event);
msg1 = "H0,";
msg1 += (isnan(event.relative_humidity) ? 9999.9 : event.relative_humidity);
msg1 += ";";
msg += msg1;

return msg;
}

Step 5: Program the Data Collector - Raspberry PI

The data collector is simply a shell script running on a Raspberry PI.

It can run on any Linux system or compatible including, but not limited to:

I used a Raspberry PI because:

  • I had one.
  • I can leave it running 24x7.
  • I could put it in my LAN that I've connected the Arduino's and reporting PC.
  • It can more than adequately do the job.

I will leave it to you to setup your Linux environment. However you will need the following:

  • The curl command (sudo apt-get install curl).
  • Host names entries added to the hosts file (S0, S1 and S2).
  • Configure the Raspberry PI with an available static IP address.

The following instructions relate to Raspberry PI.

The following instructions assume you have setup your Raspberry PI and can log in as the "super user".

Installing Curl

After logging on as the super user (user pi), check to see if curl is already installed:

curl --help

If the response is "curl: command not found" or similar, install it using the following commands (Note, your system will need to be able to connect to the internet for the following commands to work):

sudo apt-get update
sudo apt-get install curl

After you have done that, test by entering "curl --help" at the command prompt. This should now output lots of "helpful" information.

If not, google any error messages shown when running the "sudo apt-get" commands.

Add Entries to the hosts file

To make life easier for us, we will create some host names for the IP addresses that we used for our Arduino Sensor devices.

The names I used were S0, S1, S2 and so on.

  • S is for Sensor.
  • 0, 1, 2 etc corresponds to the numbers I wrote on the Arduino's Ethernet ports.
    If you haven't done that yet, go back and do it now and reprogram the Arduino's with the correct NW_OFFSET values because if you didn't label them, sooner or later they will be mixed up.

I suggest that you use the same names that I have used as the Excel Spreadsheet used to graph the data is looking for these names (i.e. it will be easier for you). Once you see how this project hangs together, feel free to change the host names to whatever you like.

The easiest way to enter the host names is to modify the hosts file using the following command:

sudo cp /etc/hosts /etc/hosts.bak            # Only do this command one time!
sudo nano /etc/hosts

The first command (sudo cp ...) creates a backup of the hosts file (only do this once). If you need to, you can restore the hosts file by entering the "reverse". That is: sudo cp /etc/hosts.bak /etc/hosts

Move the cursor to the end of the hosts file in the editor. Create a new blank line (hit enter at the end of the last line).

Enter the full IP addresses (Not just the number written on the Ethernet ports) into the editor. Hit Tab (or a few spaces) then enter your host name. In my case, I entered the following:

192.168.1.160   S0
192.168.1.161 S1
192.168.1.162 S2

Make sure you used the address programmed into the Arduino's (i.e. don't simply copy the IP addresses I used unless yours happen to be the same as mine).

Quit the nano editor (control-X). It should prompt you to save the changes - choose yes.

View the revised hosts file:

sudo cat /etc/hosts

Check that your entries are there, the IP addresses match what you entered into your Arduinos (i.e. the previous step) and that the structure of your entries looks like the above (i.e. IP address, a few spaces or tabs then the host name).

Setting a static IP address

This is similar to what we did with the Arduinos. That is, each device gets its own fixed IP address. How to do this will depend upon the version of software that you are running (and the Operating System and/or the Virtual Machine you are running).

For recent Rasberry PI editions, check this forum entry: Raspberry PI static IP address

Otherwise just google "configure static IP address on Enter-your-Operating-System-Here".

Before getting too carried away, you will need to determine an unused static IP address within your network. The steps are exactly the same as when you determined the block of IP addresses for the Arduino's. Tip, the next few address following the block you found for the Arduino's are likely to be available (but make sure you check).

Tip: Try to find an IP address that is well clear of all of the Arduino IP addresses - that way, if you add more Arduinos, there is less chance of a collision.

The monitor script

The last step is to create the monitor script on your Rasberry PI.

The easiest way is to log on as the super user (user: pi).

Then enter the following command (note, there is no "sudo" in the following command):

nano monitor.sh

Paste the following code into the editor:

#!/bin/bash</p><p>SLEEP_TIME=120
ERR=0
CNT=0
LOG_FILE=/tmp/monitor.txt
STOP_FILE=/tmp/monitorStop.txt

if [ -e $STOP_FILE ]
then
rm $STOP_FILE
fi

> $LOG_FILE</p><p>while `true`
do
MSG=`date "+%Y-%m-%d %H:%M:%S.%N"`
for HST in S0 S1 S2
do
RAW_DATA=`curl -s --connect-timeout 2 $HST:4000`
STAT=$?
SENSOR_DATA=`echo "$RAW_DATA" | tr -d '\r\n'`
if [ $STAT -ne 0 ]
then
ERR=$((ERR+1))
fi
MSG="$MSG~$HST~$SENSOR_DATA"
done

CNT=$((CNT+1))
echo "${MSG}CNT:$CNT,ERR:$ERR"
echo "${MSG}CNT:$CNT,ERR:$ERR" >> $LOG_FILE
if [ -e $STOP_FILE ]
then
exit 0
fi

sleep $SLEEP_TIME
done

Configure the script

The final step is to configure the script to retrieve data from your devices. If you created three devices and you gave them the same names as per this instructable (i.e. S0, S1, S2) then the script is ready to go.

Otherwise, Look for the following line at about line 20 in the above shell script.

for HST in S0 S1 S2

Note the names of the hosts. They are listed one after another with a single space separating them. Simply list your host names using the same structure. For example if you made four Arduino sensor devices and decided to call them K1, K2, K3 and K4, then modify the line to read:

for HST in K1 K2 K3 K4

Exit the editor (remember to save your changes).

Testing what we have so far

Restart your Data Collector (to ensure that the changes you made have truly been applied).

Connect your Arduinos (maybe just one at a time) to your network.

Log on to your Data collector and at a command prompt (a.k.a. shell/bash prompt) try the following. Note that I will use my host names (S0, S1 etc). Substitute your host names for mine in the following commands. I've underlined what you must enter (i.e. don't type in something that is not underlined):

# Retrieve Temperature and Humidity from S0
pi@MasterController ~ $ curl -s --connect-timeout 2 S0:4000
T0,24.90;H0,65.20;
pi@MasterController ~ $ curl -s --connect-timeout 2 S1:4000
T0,23.80;H0,71.20
pi@MasterController ~ $ curl -s --connect-timeout 2 S2:4000
T0,23.80;H0,65.80

# Get S1 to execute the "identify" routing - rapidly blink LED for 3 seconds
pi@MasterController ~ $ curl -s --connect-timeout 2 S1:4000/ident
mac: 0xde 0xad 0xbe 0xef 0xfe 0xa1

# Execute "identify" on S0
pi@MasterController ~ $ curl -s --connect-timeout 2 S0:4000/ident
mac: 0xde 0xad 0xbe 0xef 0xfe 0xa0

# Get help message
pi@MasterController ~ $ curl -s --connect-timeout 2 S0:4000/help
use:
/help for help
/ident to identify

# finally, get the "unrecognised input" message
pi@MasterController ~ $ curl -s --connect-timeout 2 S1:4000/gibberish
Invalid Request try /help
pi@MasterController ~ $

The temperature and Humidity readings returned from the Individual devices can be decoded as follows. Look at the reply from S0 (the first command shown above). The reply is "T0,24.90;H0,65.20;". This is read:

  • as two readings separated by semi-colons (i.e. T0,24.90 and H0,65.20)
  • The individual readings consist of a label and a value separated by a comma:
    • T0,24.90 is T0 (temperature from sensor 0) measured as 24.90 C
    • H0,65.20 is H0 (Humidity from sensor 0) measured as 65.20%

Running the monitor.sh script

Once you are happy that the Data collector can communicate with all of your Arduinos, simply run the monitor.sh from the command prompt as follows:

bash monitor.sh

You should observe output similar to the following. One line will be produced roughly every two minutes.

2019-03-16 18:02:22.228513844~S0~T0,25.00;H0,67.40;~S1~T0,23.80;H0,73.20;...CNT:1234,ERR:0

This is read as follows:

  • The date and time that the reading was taken.
  • The readings from each of the Arduino sensors programmed into the monitor.sh. These consist of the following:
    • The ID of the system from which the reading was taken enclosed in tildes (e.g. ~S0~)
    • The actual readings as described in the testing section above (e.g. T0,24.90;H0,65.20;)
  • A count of the number of readings captured since the script was started (e.g. CNT:1234)
  • A count of the number of errors encountered communicating with the Arduinos (e.g. ERR:0)

The Monitor.sh script will capture all of the readings into a single log file called /tmp/monitor.txt

You can run the script in the background by entering the following commands:

rm nohup.out
nohup bash monitor.sh &

You can check to see if the script is running by entering the following command:

ps -efl |grep monitor.sh

Which, if the monitor.sh script is running, should produce something like this:

0 S pi   16455   880  0  80   0 -  1075 wait   18:14 pts/1    00:00:00 /bin/bash /home/pi/monitor.sh

The reporting spreadsheet is configured to process 3000 lines of data (just over four days worth of data). So periodically you should stop the script, process the data and then run it again (in the background). Note when you run the monitor.sh script, the first thing it will do is delete the previous data file (/tmp/monitor.txt). So make sure you have made a copy of it somewhere else (e.g. in Excel) first.

To stop the script, you can kill it (if you know how), or perform an orderly shutdown. To perform an orderly shutdown of the monitor.sh script, simply create a stop file using the following command:

touch /tmp/monitorStop.txt

You will need to wait for the (2 minute) timer to expire before it will actually stop, but you can start processing the data straight away if you want to (you may miss the final reading for that run - but no big deal).

Note: when you run the monitor.sh script, it will truncate (empty / delete / destroy) the data previously recorded in the /tmp/monitor.txt file. So, make sure you have made a copy of the previous data set before running monitor.sh again.

Step 6: Generating the Report

This is the "pretty part", we import the captured data into Excel and it will draw a nice graph for us.

To begin, download and save the "House Temperature Readings" Excel spreadsheet somewhere on your computer. The spreadsheet works best with Microsoft Excel, but I've tried it with Libre Office Calc and that also seems to work quite well. The instructions below are for Excel.

Once you have started collecting data on the Raspberry PI (previous step), you can import it into the spreadsheet. There are three tabs in the spreadsheet:

  • Instructions - how to import the data and how to add additional sensors.
  • Daily Chart - a worksheet that can process about 1 day's worth of data this chart is easier to read.
  • Weekly Chart - a worksheet that can process about 7 day's worth of data this chart provides more of an overview.

Select the chart (daily or weekly) that you wish to use. Copy the data collected on the Raspberry PI into cell U4 (the green one). The individual data elements should be extracted and the chart updated.

I've included some sample data so you can see what the chart will look like. Simply paste your data over any existing data starting at the green cell at U4. Optionally, you can delete any pre-existing data - see below for an easy way to do that.

Transferring data from PI to Excel

The easiest way to get data from the Raspberry PI to Excel is to use an FTP program such as Win-SCP. I personally use UEStudio because it has an "open file from FTP". UEStudio is not free (and I do not get a commission for mentioning it). There are others that you could use for example Atom is free and has a plugin to open from FTP (although it didn't work so well for me).

Once you have selected your preferred FTP program, connect it to the Raspberry PI and open (or transfer) the file /tmp/monitor.txt. If using an FTP program (i.e. one that simply copies the remote file to your local computer - but does not open it for you), then you will need to open the transferred file in a text editor such as Notepad, Notepad++, Atom, Sublime or whatever you choose to use.

As mentioned above, copy the entire contents of the file into the spreadsheet at cell U4.

Clearing old data from the Spreadsheet

To clear old data and make way for new, on Windows simply:

  • Select (click on) cell U4 (the green one),
  • Hit the "End" Key,
  • Hold "Shift" and hit the "down arrow" key, this will select the entire data set,
  • Hit the "Delete" or "Del" key.

On Mac the process is similar, but I am not sure of the exact key strokes - perhaps someone could add the key strokes on Mac as a tip.

Step 7: Appendix - Finding an Available Network Address

The IP Address

The IP Address must be configured to match your network. In my case, the network is 192.168.1.0, the subnet mask is 255.255.255.0. This means that any device that wants to talk to any other device within this network must have an address in the range of 192.168.1.1 to 192.168.1.254 inclusive. While this seems complicated and daunting, it actually isn't too hard and only requires a couple of simple steps. Unfortunately, every operating system has it's own way of doing this, so you may need to do a bit of googling to muddle through.

If you want to know a bit more about how IP addresses work with subnet masks, have a look at this Wikipedia Article, this Explanation using an analogy, or just Google it.

The easiest way to work out what your network number is, is to run:

  • the "ipconfig" command from a command prompt (windows)
  • the "ifconfig" command from a shell prompt (linux, macbook).
  • the "ip addr" command from a shell prompt (linux).
  • You may also be able to find it in the control panel (the process will vary from one system to another - so I won't attempt to explain how to do this google how to find my ip address).

Note: using an online service to determine your IP address will give you the wrong information. These online services will tell you the IP address of the high order link of your router / modem that is known to the Internet. These online services (normally) do not tell you the local addresses in your office/home.

Following is the relevant but of output from a Windows 10 system (in total 85 lines were displayed):

Ethernet adapter Ethernet 2:
Connection-specific DNS Suffix . :
Link-local IPv6 Address . . . . . : fe80::c2a:8c37:5979:59a9%31
IPv4 Address. . . . . . . . . . . : 192.168.1.106
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.1.1

The relevant bit is the label "Ethernet adapter Ethernet 2". This tells me that it is information about an Ethernet adapter (which is what I am interested in). Others say things like "Wireles LAN adapter WiFi" - which may also be relevant if that is how you are connecting to your network.

The next pieces of relevant information is the IPv4 Address (192.168.1.106) and the Subnet mask (255.255.255.0).

Given the above data, I would replace the first three numbers in the IPAddress part of the code so that it looked like the following:

IPAddress ip(192, 168, 1, 160 + NW_OFFSET);

If you found that your computers IP address was, for example, 192.10.20.255 and your subnet mask was 255.255.255.0, then modify the same line as follows:

IPAddress ip(192, 10, 20, 160 + NW_OFFSET);

As for the last number (160), this will be a bit harder. Basically you need to find a free set of addresses in your network. This will likely be a trial and error process. However, there is a very good chance that the higher numbers (i.e. numbers above 200 are available for use).

To check availability, use the ping command. The ping command is run from a command (MS-DOS or shell/bash) prompt. If you see the following, the address is currently not in use (i.e. you can use it for Arduino or the Raspberry PI). This first example is from a Windows system (simply type in the underlined bit into a command prompt):

ping 192.168.1.200

Pinging 192.168.1.200 with 32 bytes of data:
Reply from 192.168.1.106: Destination host unreachable.
Reply from 192.168.1.106: Destination host unreachable.
Reply from 192.168.1.106: Destination host unreachable.
Reply from 192.168.1.106: Destination host unreachable.

Ping statistics for 192.168.1.200:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),

Following is the desired output from a Linux system (note I had to kill the command with control-C otherwise it will just run forever):

ping 192.168.1.200
PING 192.168.1.200 (192.168.1.200) 56(84) bytes of data.
From 192.168.1.10 icmp_seq=1 Destination Host Unreachable
From 192.168.1.10 icmp_seq=2 Destination Host Unreachable
From 192.168.1.10 icmp_seq=3 Destination Host Unreachable

^C
--- 192.168.1.200 ping statistics ---
9 packets transmitted, 0 received, +6 errors, 100% packet loss, time 8004ms
pipe 3

If you see output similar to the above, then the address (in this example 192.168.1.200) is available. Try the next set of addresses (e.g. 192.168.1.201, 202 and 203) as you want to find a block of available addresses (1 for each Arduino and one for the Raspberry PI). Assuming that these addresses are available, enter the first address (in this example 200) as the fourth of number in the IPAddress line of the Arduino program, so that it looks like the following:

IPAddress ip(192, 168, 1, 200 + NW_OFFSET);

If you see either of the following, try another address. Firstly the windows output which shows the address (192.168.1.1) is in use:

ping 192.168.1.1

Pinging 192.168.1.1 with 32 bytes of data:
Reply from 192.168.1.1: bytes=32 time<1ms TTL=64
Reply from 192.168.1.1: bytes=32 time<1ms TTL=64
Reply from 192.168.1.1: bytes=32 time<1ms TTL=64
Reply from 192.168.1.1: bytes=32 time<1ms TTL=64

Ping statistics for 192.168.1.1:
Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms

Following is the output from Linux that also shows the IP address is in use. Once again I had to kill the command using control-C to stop the ping:

ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_req=1 ttl=64 time=1.34 ms
64 bytes from 192.168.1.1: icmp_req=2 ttl=64 time=0.505 ms
64 bytes from 192.168.1.1: icmp_req=3 ttl=64 time=0.488 ms
64 bytes from 192.168.1.1: icmp_req=4 ttl=64 time=0.529 ms
^C
--- 192.168.1.1 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3004ms
rtt min/avg/max/mdev = 0.488/0.717/1.346/0.363 ms

For the purposes of this project, try to find a block of contiguous addresses that are not in use. This will make the every unit step much easier.

Every Unit Setup - allocating a unique address

Once you have worked out your IP address range (and if need be a MAC address range), you will need to configure the Arduino program to correctly program the address into each individual Arduino device. To do this:

  1. Label your Arduino Ethernet ports with sequential numbers starting at 0 (i.e. 0, 1, 2, 3 etc).
  2. Locate the definition of the NW_OFFSET constant (at about line 29 of the program).
  3. Replace the number with the with the label you assigned in step 1 above.
  4. Ensure that you have selected the right Arduino (in case you have multiple connected to your computer - best just connect one at a time).
  5. Program the Arduino in the normal way.

As you can see from the previous section, the NW_OFFSET value will be added to the last digit in both the MAC and IP address to ensure that the Arduino get's a unique address.

Note: the maximum value is 254, so if you start the last octet of the IP address at 200, then the maximum value of NW_OFFSET is 54 (200 + 54 = 254). If you start the last octet of the IP Address at 160, then the maximum value of NW_OFFSET will be 94 (160 + 94 = 254). You can not use an octet of 255 (and I would recommend not using 0) for the last octet of the IP address.