© John C. Shovic 2016

John C. Shovic, Raspberry Pi IoT Projects, 10.1007/978-1-4842-1377-3_2

2. Sensing Your IOT Environment

John C. Shovic

(1)Liberty Lake, Washington, USA

Chapter Goal: Build Your First IOT Device

Topics Covered in This Chapter:

  • Building an inexpensive IOT Sensor device based on the ESP8266 and Arduino IDE

  • Setting up a simple Self-Organizing IOT Sensor Net

  • Reading I2C sensor (light and color) on the Arduino devices

  • Reading data from remote IOT sensors on the Raspberry Pi

  • Using the Raspberry Pi for Monitoring and Debugging

  • Displaying your Data on the screen and on an iPad

In this chapter, we build our first IOT device. This simple design, a light-sensor swarm, is easy to build and illustrates a number of IOT principles including the following:

  • Distributed Control

  • Self-Organization

  • Passing Information to the Internet

  • Swarm Behavior

The LightSwarm architecture is a simple and flexible scheme for understanding the idea of an IOT project using many simple small computers and sensors with shared responsibility for control and reporting. Note that in this swarm, communication with the Internet is handled by a Raspberry Pi. The swarm devices talk to each other, but not with the Internet. The Raspberry Pi is located on the same WiFi access point as the swarm, but could be located far away with some clever forwarding of packets through your WiFi router. In this case, since we have no computer security at all in this design (see Chapter 7 for information on making your IOT swarm and device more secure) and so we are sticking with the local network. The exception to this is the RasPiConnect LightSwarm control panel that can be located anywhere on the Internet and is secured by password control and could easily be sent via https, encrypting the individual XML packets.

IOT Sensor Nets

One of the major uses of the IOT will be building nets and groups of sensors. Inexpensive sensing is just as big of a driver for the IOT as the development of inexpensive computers. The ability for a computer to sense its environment is the key to gathering information for analysis, action, or transmittal to the Internet. Sensor nets have been around in academia for many years, but now the dropping prices and availability of development tools are quickly moving sensor nets out to the mainstream. Whole industrial and academic conferences are now held on sensor nets [ www.sensornets.org ]. The market is exploding for these devices, and opportunities are huge for the creative person or group that can figure out how to make the sensor net that will truly drive consumer sales.

IOT Characterization of This Project

As I discussed in Chapter 1, the first thing to do to understand an IOT project is to look at our six different aspects of IOT. LightSwarm is a pretty simple project and can be broken down into the six components listed in Table 2-1.

Table 2-1. LightSwarm Characterization (CPLPFC )

Aspect

Rating

Comments

Communications

9

WiFi connection to Internet - can do ad-hoc mesh-type communication

Processor Power

7

80MHz XTensa Harvard Architecture CPU, ∼80KB Data RAM / ∼35KB of Instruction RAM / 200K ROM

Local Storage

6

4MB Flash (or 3MB file system!)

Power Consumption

7

∼200mA transmitting, ∼60mA receiving, no WiFi ∼15mA, Standby ∼1mA

Functionality

7

Partial Arduino Support (limited GPIO/Analog Inputs)

Cost

9

< $10 and getting cheaper

Ratings are from 1–10, 1 being the least suitable for IOT and 10 being the most suitable for IOT applications .

This gives us a CPLPFC rating of 7.2. This is calculated by averaging all six values together with equal weighting. This way is great for learning and experimenting, and could be deployed for some applications.

The ESP8266 is an impressive device having a built-in WiFi connection, a powerful CPU, and quite a bit of RAM and access to the Arduino libraries. It is inexpensive and will get cheaper as time goes on. It is a powerful device for prototyping IOT applications requiring medium levels of functionality.

How Does This Device Hook Up to the IOT?

The ESP8266 provides a WiFi transmitter/receiver, a TCP/IP stack, and firmware to support direction connections to a local WiFi access point that then can connect to the Internet. In this project, the ESP8266 will only be talking to devices on the local wireless network. -This is an amazing amount of functionality for less than $10 retail. These chips can be found for as little as $1 on the open market, if you want to roll your own printed circuit board.

What Is an ESP8266?

The ESP8266 is made by a company in China called Espressif [espressif.com]. They are a fabless semiconductor company that just came out of nowhere with the first generation of this chip and shook up the whole industry. Now all the major players are working on inexpensive versions of an IOT chip with WiFi connectivity.

The ESP8266 chip was originally designed for connected light bulbs but soon got used in a variety of applications, and ESP8266 modules are currently now the most popular solutions to add WiFi to IOT projects. While the ESP8266 has huge functionality and a good price, the amount of current consumed by the chip makes battery-powered solutions problematic .

The ESP8266 is enabling a whole new set of applications and communities with their innovative and inexpensive design (Figure 2-1).

A367492_1_En_2_Fig1_HTML.jpg
Figure 2-1. The ESP8266 die. (Courtesy of Hackaday.io)

We will be using a version of the ESP8266 on a breakout board designed by Adafruit (Figure 2-2). The board provides some connections, a built-in antenna, and some power regulation, all for less than $10.

A367492_1_En_2_Fig2_HTML.jpg
Figure 2-2. The Adafruit Huzzah ESP8266 . (Courtesy of adafruit.com )

The LightSwarm Design

There are two major design aspects of the LightSwarm project . First of all, each element of the swarm is based on an ESP8266 with a light sensor attached. The software is what makes this small IOT device into a swarm. In the following block diagram you can see the major two devices. I am using the Adafruit Huzzah breakout board for the ESP8266 [ www.adafruit.com/product/2471 ]. Why use a breakout board? With a breakout board you can quickly prototype devices and then move to a less-expensive design in the future. The other electronic component (Figure 2-3) is a TCS34725 RGB light-sensor breakout board, also from Adafruit [ www.adafruit.com/products/1334 ].

A367492_1_En_2_Fig3_HTML.jpg
Figure 2-3. TCS34725 Breakout Board . (Courtesy of adafruit.com )

The addition of a sensor to a computer is what makes this project an IOT device. I am sensing the environment and then doing something with the information (determining which of the Swarm has the brightest light). Figure 2-4 shows the block diagram of a single Swarm element.

A367492_1_En_2_Fig4_HTML.jpg
Figure 2-4. LightSwarm Element Block Diagram

Each of the LightSwarm devices in the swarm is identical. There are no software differences and no hardware differences. As you will see when we discuss the software, they vote with each other and compare notes and then elect the device that has the brightest light as the “Master,” and then the “Master” turns the red LED on the device to show us who has been elected “Master.” The swarm is designed so devices can drop out of the swarm, be added to the swarm dynamically, and the swarm adjusts to the new configuration. The swarm behavior (who is the master, how long it takes information about changing lights to propagate through the swarm, etc.) is rather complex. More complex than expected from the simple swarm device code. There is a lesson here: simple machines in groups can lead to large complex systems with higher-level behaviors based on the simple machines and the way they interact.

The behavior of the LightSwarm surprised me a number of times and it was sometimes very difficult to figure out what was happening, even with the Raspberry Pi logger. Figure 2-5 shows the complete LightSwarm including the Raspberry Pi.

A367492_1_En_2_Fig5_HTML.jpg
Figure 2-5. The Light Swarm

The Raspberry Pi in this diagram is not controlling the swarm. The Raspberry Pi gathers data from the swarm (the current “Master” device sends information to the Raspberry Pi), and then the Raspberry Pi can store the data and communicate it through the Internet, in this case to an iPad-based app called RasPiConnect [ www.milocreek.com ] , which displays the current and historical state of the LightSwarm.

The LightSwarm project has an amazing amount of functionality and quirky self-organizing behavior for such a simple design.

Building Your First IOT Swarm

Table 2-2 lists the parts needed to build an IOT Swarm.

Table 2-2. Swam Parts List

Part Number

Count

Description

Approximate Cost per Board

Source

ESP8266 Huzzah Board

5

CPU / WiFi board

$10

www.adafruit.com/products/2471

TCS34725 Breakout Board

5

I2C Light Sensor

$8

www.adafruit.com/products/1334

FTDI Cable

1

Cable for programming the ESP8266 from PC/Mac

$18

www.adafruit.com/products/70

Installing Arduino Support on the PC or Mac

The key to making this project work is the software. While there are many ways of programming the ESP8266 (MicroPython) [micropython.org], NodeMCU Lua interpreter [nodemcu.com/index_en.html], and the Arduino IDE (Integrated Development Environment) [ www.arduino.cc/en/Main/Software ]), I chose the Arduino IDE for its flexibility and the large number of sensor and device libraries available.

To install the Arduino IDE you need to do the following:

  1. Download the Arduino IDE package for your computer and install the software [ www.arduino.cc/en/Guide/HomePage ] .

  2. Download the ESP libraries so you can use the Arduino IDE with the ESP breakout board. Adafruit has an excellent tutorial for installing the ESP8266 support for the Arduino IDE [ learn.adafruit.com/adafruit-huzzah-esp8266-breakout/using-arduino-ide ].

Your First Sketch for the ESP8266

A great way of testing your setup is to run a small sketch that will blink the red LED on the ESP8266 breakout board. The red LED is hooked up to GPIO 0 (General Purpose Input Output pin 0) on the Adafruit board.

Open a new sketch using the Arduino IDE and place the following code into the code window, replacing the stubs provided when opening a new sketch. The code given here will make the red LED blink.

void setup() {
  pinMode(0, OUTPUT);
}


void loop() {
  digitalWrite(0, HIGH);
  delay(500);
  digitalWrite(0, LOW);
  delay(500);
}

If your LED is happily blinking away now, you have correctly followed all the tutorials and have the ESP8266 and the Arduino IDE running on your computer.

Next I will describe the major hardware systems and then dive into the software.

The Hardware

The main pieces of hardware in the swarm device are the following:

  • ESP8266 - CPU/WiFi Interface

  • TCS34725 - Light sensor

  • 9V Battery - Power

  • FTDI Cable - Programming and power

The ESP8266 communicates with other swarm devices by using the WiFi interface . The ESP8266 uses the I2C interface to communicate with the light sensor. The WiFi is a standard that is very common (although we use protocols to communicate that are NOT common). See the description of UDP (User Datagram Protocol) later in this chapter. The I2C bus interface is much less familiar and needs some explanation.

Reviewing the I2C Bus

An I2C bus is often used to communicate with chips or sensors that are on the same board or located physically close to the CPU. It stands for standard Inter-IC device bus. The I2C was first developed by Phillips (now NXP Semiconductors). To get around licensing issues, often the bus will be called TWI (Two Wire Interface). SMBus, developed by Intel, is a subset of I2C that defines the protocols more strictly. Modern I2C systems take policies and rules from SMBus sometimes supporting both with minimal reconfiguration needed. The Raspberry Pi and the Arduino are both these kinds of devices. Even the ESP8266 used in this project can support both.

An I2C provides good support for slow, close peripheral devices that only need be addressed occasionally. For example, a temperature measuring device will generally only change very slowly and so is a good candidate for the use of I2C, where a camera will generate lots of data quickly and potentially changes often.

An I2C bus uses only two bidirectional open-drain lines (open-drain means the device can pull a level down to ground, but cannot pull the line up to Vdd. Hence the name open-drain). Thus a requirement of an I2C bus is that both lines are pulled up to Vdd. This is an important area and not properly pulling up the lines is the first and most common mistake you make when you first use an I2C bus. More on pullup resistors later in the next section. The two lines are SDA (Serial Data Line) and the SCL (Serial Clock Line). There are two types of devices you can connect to an I2C bus. They are Master devices and Slave devices. Typically, you have one Master device (the Raspberry Pi in our case) and multiple Slave devices, each with their individual 7-bit address (like 0x68 in the case of the DS1307). There are ways to have 10-bit addresses and multiple Master devices, but that is beyond the scope of this column. Figure 2-6 shows an I2C bus with devices and the master connected.

A367492_1_En_2_Fig6_HTML.jpg
Figure 2-6. An I2C bus with one Master (the ESP8266 in this case) and three Slave devices. Rps are the Pullup Resistors
SwitchDoc Note

Vcc and Vdd mean the same. Gnd and Vss generally also both mean ground. There are historical differences, but today Vcc usually is one power supply, and if there is a second, they will call it Vdd.

When used on the Raspberry Pi, the Raspberry Pi acts as the Master and all other devices are connected as Slaves.

SwitchDoc Note

If you connect an Arduino to a Raspberry Pi, you need to be careful about voltage levels because the Raspberry Pi is a 3.3V device and the Arduino is a 5.0V device. The ESP8266 is a 3.3V device so you also need to be careful connecting an Arduino to an ESP8266. Before you do this, read this excellent tutorial [ blog.retep.org/2014/02/15/connecting-an-arduino-to-a-raspberry-pi-using-i2c/ ].

The I2C protocol uses three types of messages:

  • Single message where a master writes data to a slave;

  • Single message where a master reads data from a slave;

  • Combined messages, where a master issues at least two reads and/or writes to one or more slaves.

Lucky for us, most of the complexity of dealing with the I2C bus is hidden by drivers and libraries.

Pullups on the I2C Bus

One important thing to consider on your I2C bus is a pullup resistor. The Raspberry Pi has 1.8K ohm (1k8) resistors already attached to the SDA and SCL lines, so you really shouldn't need any additional pullup resistors. However, you do need to look at your I2C boards to find out if they have pullup resistors. If you have too many devices on the I2C bus with their own pullups, your bus will stop working. The rule of thumb from Phillips is not to let the total pullup resistors in parallel be less than 1K (1k0) ohms. You can get a pretty good idea of what the total pullup resistance is by turning the power off on all devices and using an ohm meter to measure the resistance on the SCL line from the SCL line to Vdd .

Sensor Being Used

We are using the TCS34725 , which has RGB and Clear light-sensing elements. Figure 2-7 shows the TCS34725 die with the optical sensor showing in the center of the figure. An IR blocking filter, integrated on-chip and localized to the color-sensing photodiodes, minimizes the IR spectral component of the incoming light and allows color measurements to be made accurately. The IR filter means you'll get much truer color than most sensors, since humans don't see IR. The sensor does see IR and thus the IR filter is provided. The sensor also has a 3,800,000:1 dynamic range with adjustable integration time and gain so it is suited for use behind darkened glass or directly in the light.

A367492_1_En_2_Fig7_HTML.jpg
Figure 2-7. The TCS34725 Chip Die

This is an excellent inexpensive sensor ($8 retail from Adafruit on a breakout board) and forms the basis of our IOT sensor array. Of course, you could add many more sensors, but having one sensor that is easy to manipulate is perfect for our first IOT project. In Chapter 3, we add many more sensors to the Raspberry Pi computer for a complete IOT WeatherStation design.

3D Printed Case

One of the big changes in the way people build prototypes is the availability of inexpensive 3D printers. It used to be difficult to build prototype cases and stands for various electronic projects. Now it is easy to design a case in one of many types of 3D software and then print it out using your 3D printer. For the swarm, I wanted a partial case to hold the 9V battery, the ESP8266, and the light sensor. I used OpenSCAD [www.openscad.org ] to do the design. OpenSCAD is a free 3D CAD system that appeals to programmers. Rather than doing the entire design in a graphical environment, you write code (consisting of various objects, joined together or subtracted from each other) that you then compile in the environment to form a design in 3D space. OpenSCAD comes with an IDE (Integrated Development Environment) and you place the code showing in Listing 2-1 in the editor, compile the code, and then see the results in the attached IDE as shown in Figure 2-8.

A367492_1_En_2_Fig8_HTML.jpg
Figure 2-8. OpenSCAD display

As shown in Listing 2-1, the OpenSCAD programming code to build this stand is quite simple. It consists of cubes and cylinders of various sizes and types.

Listing 2-1. Mounting Base for the IOT LightSwarm
//
// IOT Light Swarm Mounting Base
//
// SwitchDoc Labs
// August 2015
//


union()
{


    cube([80,60,3]);
    translate([-1,-1,0])
    cube([82,62,2]);


    // Mount for Battery

    translate([40,2,0])
    cube([40,1.35,20]);
    translate([40,26.10+3.3,0])
    cube([40,1.5,20]);


    // lips for battery
    translate([79,2,0])
    cube([1,28,4]);


    // pylons for ESP8266

    translate([70-1.0,35,0])
    cylinder(h=10,r1=2.2, r2=1.35/2, $fn=100);
    translate([70-1.0,56,0])
    cylinder(h=10,r1=2.2, r2=1.35/2, $fn=100);
    translate([70-34,35,0])
    cylinder(h=10,r1=2.2, r2=1.35/2, $fn=100);
    translate([70-34,56,0])
    cylinder(h=10,r1=2.2, r2=1.35/2, $fn=100);


    // pylons for light sensor

    translate([10,35,0])
    cylinder(h=10,r1=2.2, r2=1.35/2, $fn=100);
    translate([10,49.5,0])
    cylinder(h=10,r1=2.2, r2=1.35/2, $fn=100);


   translate([22,37,0])
    cylinder(h=6,r1=2.2, r2=2.2, $fn=100);
    translate([22,47,0])
    cylinder(h=6,r1=2.2, r2=2.2, $fn=100) ;
}

You can see the completed stand and the FTDI cable in the upcoming Figure 2-9. Once designed, I quickly built five of them for the LightSwarm. Figure 2-10 shows a completed Swarm element.

A367492_1_En_2_Fig9_HTML.jpg
Figure 2-9. FTDI Cable Plugged into ESP8266
A367492_1_En_2_Fig10_HTML.jpg
Figure 2-10. Completed LightSwarm Stand

The Full Wiring List

Table 2-3 provides the complete wiring list for a LightSwarm device. As you wire it, check off each wire for accuracy.

Table 2-3. LightSwarm Wiring List

From

To

Description

ESP8266 / GND

TCS34725 /GND

Ground for I2C Light Sensor

ESP8266 / 3V

TCS34725 / 3V3

3.3V Power for I2C Light Sensor

ESP8266 / #4

TCS34725 /SDA

SDA for I2C Light Sensor

ESP8266 / #5

TCS34725 /SCL

SCL for I2C Light Sensor

ESP8266 / GND

9VBat / “-” terminal (minus terminal)

Ground for battery

ESP8266 / VBat

9VBat / “+” terminal (plus 9V)

9V from battery

TCS34725 / LED

TCS34725 / INT

Connecting these two pins together allow for software control of bright LED on TCS34725 board

The FTDI cable is plugged into the end of the Adafruit Huzzah ESP8266. Make sure you align the black wire with the GND pin on the ESP8266 breakout board as in Figure 2-9. Figure 2-11 shows the fully complete LightSwarm device.

A367492_1_En_2_Fig11_HTML.jpg
Figure 2-11. A Complete LightSwarm Device

The Software

There are two major modules written for the LightSwarm. The first is ESP8266 code for the LightSwarm device itself (written in the Arduino IDE - written in simplified C and C++ language), and the second is the Raspberry Pi data-gathering software (written in Python on the Raspberry Pi).

The major design specifications for the LightSwarm Device software are the following:

  • Device self discovery.

  • Device becomes master when it has the brightest light; all others become slaves.

  • Distributed voting method for determining master status.

  • Self-organizing swarm. No server.

  • Swarm must survive and recover from devices coming in and out of the network.

  • Master device sends data to Raspberry Pi for analysis and distribution to Internet.

The entire code for the LightSwarm devices is provided in Listings 2-2 though 2-11 (with the exception of the TCS74725 light-sensor driver, available here [ github.​com/​adafruit/​Adafruit_​TCS34725]). The code is also available on the APress web site [ www.apress.com ] and the SwitchDoc Labs github site [ github.com/switchdoclabs/lightswarm ].

Listing 2-2. LightSwarm Code
/*
Cooperative IOT Self Organizing Example
SwitchDoc Labs, August 2015


 */

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <Wire.h>
#include "Adafruit_TCS34725.h"


#undef DEBUG

char ssid[] = "yyyyy";         // your wireless network SSID (name)
char pass[] = "xxxxxxx";       // your wireless network password


#define VERSIONNUMBER 28

#define SWARMSIZE 5
// 30 seconds is too old - it must be dead
#define SWARMTOOOLD 30000


int mySwarmID = 0;

Next in Listing 2-3, we define the necessary constants. Following are the definitions of all the Swarm Commands available:

  • LIGHT_UPDATE_PACKET - Packet containing current light from a LightSwarm device. Used to determine who is master and who is slave;

  • RESET_SWARM_PACKET - All LightSwarm devices are told to reset their software;

  • CHANGE_TEST_PACKET - Designed to change the master / slave criteria (not implemented);

  • RESET_ME_PACKET - Just reset a particular LightSwarm device ID;

  • DEFINE_SERVER_LOGGER_PACKET - This is the new IP address of the Raspberry Pi so the LightSwarm device can send data packets;

  • LOG_TO_SERVER_PACKET - Packets send from LightSwarm devices to Raspberry Pi;

  • MASTER_CHANGE_PACKET - Packet sent from LightSwarm device when it becomes a master (not implemented);

  • BLINK_BRIGHT_LED - Command to a LightSwarm device to blink the bright LED on the TCS34725.

After the constants in Listing 2-3, I set up the system variables for the devices. I am using UDP across the WiFi interface. What is UDP? UDP stands for User Datagram Protocol. UDP uses a simple connectionless model. Connectionless means that there is no handshake between the transmitting device and the receiving device to let the transmitter know that the receiver is actually there. Unlike TCP (Transmission Control Protocol ), you have no idea or guarantee of data packets being delivered to any particular device. You can think of it as kind of a TV broadcast to your local network. Everyone gets it, but they don’t have to read the packets. There are also subtle other effects – such as you don’t have any guarantee that packets will arrive in the order they are sent. So why are we using UDP instead of TCP? I am using the broadcast mode of UDP so when a LightSwarm devices send out a message to the WiFi subnet, everybody gets it and if they are listening on the port 2910 (set above), then they can react to the message. This is how LightSwarm devices get discovered. Everybody starts sending packages (with random delays introduced) and all of the LightSwarm devices figure out who is present and who has the brightest light. Nothing in the LightSwarm system assigns IP numbers or names. They all figure it out themselves.

Listing 2-3. LightSwarm Constants
// Packet Types

#define LIGHT_UPDATE_PACKET 0
#define RESET_SWARM_PACKET 1
#define CHANGE_TEST_PACKET 2
#define RESET_ME_PACKET 3
#define DEFINE_SERVER_LOGGER_PACKET 4
#define LOG_TO_SERVER_PACKET 5
#define MASTER_CHANGE_PACKET 6
#define BLINK_BRIGHT_LED 7


unsigned int localPort = 2910;      // local port to listen for UDP packets

// master variables
boolean masterState = true;   // True if master, False if not
int swarmClear[SWARMSIZE];
int swarmVersion[SWARMSIZE];
int swarmState[SWARMSIZE];
long swarmTimeStamp[SWARMSIZE];   // for aging


IPAddress serverAddress = IPAddress(0, 0, 0, 0); // default no IP Address

int swarmAddresses[SWARMSIZE];  // Swarm addresses

// variables for light sensor

int clearColor;
int redColor;
int blueColor;
int greenColor;


const int PACKET_SIZE = 14; // Light Update Packet
const int BUFFERSIZE = 1024;


byte packetBuffer[BUFFERSIZE]; //buffer to hold incoming and outgoing packets

// A UDP instance to let us send and receive packets over UDP
WiFiUDP udp;


/* Initialize with specific int time and gain values */
Adafruit_TCS34725 tcs = Adafruit_TCS34725(TCS34725_INTEGRATIONTIME_700MS, TCS34725_GAIN_1X);


IPAddress localIP ;

The setup() routine shown in Listing 2-4 is only run once after reset of the ESP8266 and is used to set up all the devices and print logging information to the Serial port on the ESP8266, where, if you have the FTDI cable connected, you can see the logging information and debugging information on your PC or Mac.

Listing 2-4. The Setup() Function for LightSwarm
void setup()
{


  Serial.begin(115200);
  Serial.println();
  Serial.println();


  Serial.println("");
  Serial.println("--------------------------");
  Serial.println("LightSwarm");
  Serial.print("Version ");
  Serial.println(VERSIONNUMBER);
  Serial.println("--------------------------");


  Serial.println(F(" 09/03/2015"));
  Serial.print(F("Compiled at:"));
  Serial.print (F(__TIME__));
  Serial.print(F(" "));
  Serial.println(F(__DATE__));
  Serial.println();
  pinMode(0, OUTPUT);


  digitalWrite(0, LOW);
  delay(500);
  digitalWrite(0, HIGH) ;

Here we use the floating value of the analog input on the ESP8266 to set the pseudo-random number generation seed. This will vary a bit from device to device, and so it’s not a bad way of initializing the pseudo-random number generator. If you put a fixed number as the argument, it will always generate the same set of pseudo-random numbers. This can be very useful in testing. Listing 2-5 shows the setup of the random seed and the detection of the TCS34725.

What is a pseudo-random number generator? It is an algorithm for generating a sequence of numbers whose properties approximate a truly random number sequence. It is not a truly random sequence of numbers, but it is close. Good enough for our usage.

Listing 2-5. Remainder of the Setup() Function for LightSwarm
randomSeed(analogRead(A0));
  Serial.print("analogRead(A0)=");
  Serial.println(analogRead(A0));


  if (tcs.begin()) {
    Serial.println("Found sensor");
  } else {
    Serial.println("No TCS34725 found ... check your connections");


  }

  // turn off the light
  tcs.setInterrupt(true);  // true means off, false means on


  // everybody starts at 0 and changes from there
  mySwarmID = 0;


  // We start by connecting to a WiFi network
  Serial.print("LightSwarm Instance: ");
  Serial.println(mySwarmID);


  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, pass);


  // initialize Swarm Address - we start out as swarmID of 0

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");


  Serial.println("WiFi connected") ;
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());


  Serial.println("Starting UDP");

  udp.begin(localPort);
  Serial.print("Local port: ");
  Serial.println(udp.localPort());


  // initialize light sensor and arrays
  int i;
  for (i = 0; i < SWARMSIZE; i++)
  {


    swarmAddresses[i] = 0;
    swarmClear[i] = 0;
    swarmTimeStamp[i] = -1;
  }
  swarmClear[mySwarmID] = 0;
  swarmTimeStamp[mySwarmID] = 1;   // I am always in time to myself
  clearColor = swarmClear[mySwarmID];
  swarmVersion[mySwarmID] = VERSIONNUMBER;
  swarmState[mySwarmID] = masterState;
  Serial.print("clearColor =");
  Serial.println(clearColor);

Now we have initialized all the data structures for describing our LightSwarm device and the states of the light sensor. Listing 2-6 sets the SwarmID based on the current device IP address. When you turn on a LightSwarm device and it connects to a WiFi access point, the access point assigns a unique IP address to the LightSwarm device. This is done through a process called DHCP (Dynamic Host Configuration Protocol ) [ en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol ]. While the number will be different for each LightSwarm device, it is not random. Typically, if you power a specific device down and power it up again, the access point will assign the same IP address. However, you can’t count on this. The access point knows your specific device because each and every WiFi interface has a specific and unique MAC (Media Access Control) number, which is usually never changed.

SwitchDoc Note

Faking MAC addresses allows you to impersonate other devices with your device in some cases, and you can use MAC addresses to track specific machines by looking at the network. This is why Apple, Inc. has started using random MAC addresses in their devices while scanning for networks. If random MAC addresses aren’t used, then researchers have confirmed that it is possible to link a specific real identity to a particular wireless MAC address [Cunche, Mathieu. "I know your MAC Address:​ Targeted tracking of individual using Wi-Fi". 2013].

Listing 2-6. Setting the SwarmID from IP Address
  // set SwarmID based on IP address

  localIP = WiFi.localIP();

  swarmAddresses[0] =  localIP[3];

  mySwarmID = 0;

  Serial.print("MySwarmID=");
  Serial.println(mySwarmID);


}

The second main section of the LightSwarm device code is the loop() . The loop() function does precisely what its name suggests and loops infinitely, allowing your program to change and respond. This is the section of the code that performs the main work of the LightSwarm code.

void loop()
{
  int secondsCount;
  int lastSecondsCount;


  lastSecondsCount = 0;
 #define LOGHOWOFTEN


  secondsCount = millis() / 100;

In this Listing 2-7, we read all the data from the TCS34725 sensor to find out how bright the ambient light currently is. This forms the substance of the data to determine who the master in the swarm is.

After the delay (300) line in Listing 2-7, I check for UDP packets being broadcast to port 2910. Remember the way the swarm is using UDP is in broadcast mode and all packets are being received by everybody all the time. Note, this sets a limit of how many swarm devices you can have (limited to the subnet size) and also by the congestion of having too many messages go through at the same time. This was pretty easy to simulate by increasing the rate that packets are sent. The swarm still functions but the behavior becomes more erratic and with sometimes large delays.

Listing 2-7. Reading the Light Color
uint16_t r, g, b, c, colorTemp, lux;

tcs.getRawData(&r, &g, &b, &c);
colorTemp = tcs.calculateColorTemperature(r, g, b);
lux = tcs.calculateLux(r, g, b);


Serial.print("Color Temp: "); Serial.print(colorTemp, DEC); Serial.print(" K - ");
Serial.print("Lux: "); Serial.print(lux, DEC); Serial.print(" - ");
Serial.print("R: "); Serial.print(r, DEC); Serial.print(" ");
Serial.print("G: "); Serial.print(g, DEC); Serial.print(" ");
Serial.print("B: "); Serial.print(b, DEC); Serial.print(" ");
Serial.print("C: "); Serial.print(c, DEC); Serial.print(" ");
Serial.println(" ");


clearColor = c;
redColor = r;
blueColor = b;
greenColor = g;


swarmClear[mySwarmID] = clearColor;

// wait to see if a reply is available
delay(300);


int cb = udp.parsePacket();

if (!cb) {
  //  Serial.println("no packet yet");
  Serial.print(".");
}
else {

In Listing 2-8, we interpret all the packets depending on the packet type .

Listing 2-8. Interpreting the Packet Type
// We've received a packet, read the data from it

   udp.read(packetBuffer, PACKET_SIZE); // read the packet into the buffer
   Serial.print("packetbuffer[1] =");
   Serial.println(packetBuffer[1]);
   if (packetBuffer[1] == LIGHT_UPDATE_PACKET)
   {
     Serial.print("LIGHT_UPDATE_PACKET received from LightSwarm #");
     Serial.println(packetBuffer[2]);
     setAndReturnMySwarmIndex(packetBuffer[2]);


     Serial.print("LS Packet Recieved from #");
     Serial.print(packetBuffer[2]);
     Serial.print(" SwarmState:");
     if (packetBuffer[3] == 0)
       Serial.print("SLAVE");
     else
       Serial.print("MASTER");
     Serial.print(" CC:");
     Serial.print(packetBuffer[5] * 256 + packetBuffer[6]);
     Serial.print(" RC:");
     Serial.print(packetBuffer[7] * 256 + packetBuffer[8]);
     Serial.print(" GC:");
     Serial.print(packetBuffer[9] * 256 + packetBuffer[10]);
     Serial.print(" BC:");
     Serial.print(packetBuffer[11] * 256 + packetBuffer[12]);
     Serial.print(" Version=");
     Serial.println(packetBuffer[4]);


     // record the incoming clear color

     swarmClear[setAndReturnMySwarmIndex(packetBuffer[2])] = packetBuffer[5] * 256 + packetBuffer[6];
     swarmVersion[setAndReturnMySwarmIndex(packetBuffer[2])] = packetBuffer[4];
     swarmState[setAndReturnMySwarmIndex(packetBuffer[2])] = packetBuffer[3];
     swarmTimeStamp[setAndReturnMySwarmIndex(packetBuffer[2])] = millis();


     // Check to see if I am master!
     checkAndSetIfMaster() ;


   }

The RESET_SWARM_PACKET command sets all of the LightSwarm devices to master (turning on the red LED on each) and then lets the LightSwarm software vote and determine who has the brightest light. As each device receives a LIGHT_UPDATE_PACKET, it compares the light from that swarm device to its own sensor and becomes a slave if their light is brighter. Eventually, the swarm figures out who has the brightest light. I have been sending this periodically from the Raspberry Pi and watching the devices work it out. It makes an interesting video. Listing 2-9 shows how LightSwarm interprets incoming packets. The very last section of Listing 2-9 shows how the Swarm element updates everybody else in the Swarm what is going on with this device and then we send a data packet to the Raspberry Pi if we are the swarm master.

Listing 2-9. LightSwarm Packet Interpretation Code
if (packetBuffer[1] == RESET_SWARM_PACKET)
{
  Serial.println(">>>>>>>>>RESET_SWARM_PACKETPacket Received");
  masterState = true;
  Serial.println("Reset Swarm:  I just BECAME Master (and everybody else!)");
  digitalWrite(0, LOW);


}

if (packetBuffer[1] == CHANGE_TEST_PACKET)
{
  Serial.println(">>>>>>>>>CHANGE_TEST_PACKET Packet Received");
  Serial.println("not implemented");
  int i;
  for (i = 0; i < PACKET_SIZE; i++)
  {
    if (i == 2)
    {
      Serial.print("LPS[");
      Serial.print(i);
      Serial.print("] = ");
      Serial.println(packetBuffer[i]);


    }
    else
    {
      Serial.print("LPS[");
      Serial.print(i);
      Serial.print("] = 0x");
      Serial.println(packetBuffer[i], HEX);
    }


  }

}

if (packetBuffer[1] == RESET_ME_PACKET)
{
  Serial.println(">>>>>>>>>RESET_ME_PACKET Packet Received");


  if (packetBuffer[2] == swarmAddresses[mySwarmID])
  {
    masterState = true;
    Serial.println("Reset Me:  I just BECAME Master");
    digitalWrite(0, LOW) ;


  }
  else
  {
    Serial.print("Wanted #");
    Serial.print(packetBuffer[2]);
    Serial.println(" Not me - reset ignored");
  }


}

}

if (packetBuffer[1] ==  DEFINE_SERVER_LOGGER_PACKET)
{
  Serial.println(">>>>>>>>>DEFINE_SERVER_LOGGER_PACKET Packet Received");
  serverAddress = IPAddress(packetBuffer[4], packetBuffer[5], packetBuffer[6], packetBuffer[7]);
  Serial.print("Server address received: ");
  Serial.println(serverAddress) ;


}
if (packetBuffer[1] ==  BLINK_BRIGHT_LED)
{
  Serial.println(">>>>>>>>>BLINK_BRIGHT_LED Packet Received");
  if (packetBuffer[2] == swarmAddresses[mySwarmID])
  {


    tcs.setInterrupt(false);  // true means off, false means on
    delay(packetBuffer[4] * 100);
    tcs.setInterrupt(true);  // true means off, false means on
  }
  else
  {
    Serial.print("Wanted #");
    Serial.print(packetBuffer[2]);
    Serial.println(" Not me - reset ignored");
  }


}

Serial.print("MasterStatus:");
if (masterState == true)
{
  digitalWrite(0, LOW);
  Serial.print("MASTER");
}
else
{
  digitalWrite(0, HIGH);
  Serial.print("SLAVE");
}
  Serial.print("/cc=");
  Serial.print(clearColor);
  Serial.print("/KS:");
  Serial.println(serverAddress);


  Serial.println("--------");

  int i;
  for (i = 0; i < SWARMSIZE; i++)
{
  Serial.print("swarmAddress[");
  Serial.print(i);
  Serial.print("] = ");
  Serial.println(swarmAddresses[i]);
}
  Serial.println("--------") ;


  broadcastARandomUpdatePacket();
  sendLogToServer();


} // end of loop()

Listing 2-10 is used to send out light packets to a swarm address. Although a specific address is allowed by this function, we set the last octet of the IP address (201 in the IP address 192.168.1.201) in the calling function to 255, which is the UDP broadcast address.

Listing 2-10. Broadcasting to the Swarm
// send an LIGHT Packet request to the swarms at the given address
unsigned long sendLightUpdatePacket(IPAddress & address)
{


  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, PACKET_SIZE);
  // Initialize values needed to form Light Packet
  // (see URL above for details on the packets)
  packetBuffer[0] = 0xF0;   // StartByte
  packetBuffer[1] = LIGHT_UPDATE_PACKET;     // Packet Type
  packetBuffer[2] = localIP[3];     // Sending Swarm Number
  packetBuffer[3] = masterState;  // 0 = slave, 1 = master
  packetBuffer[4] = VERSIONNUMBER;  // Software Version
  packetBuffer[5] = (clearColor & 0xFF00) >> 8; // Clear High Byte
  packetBuffer[6] = (clearColor & 0x00FF); // Clear Low Byte
  packetBuffer[7] = (redColor & 0xFF00) >> 8; // Red High Byte
  packetBuffer[8] = (redColor & 0x00FF); // Red Low Byte
  packetBuffer[9] = (greenColor & 0xFF00) >> 8; // green High Byte
  packetBuffer[10] = (greenColor & 0x00FF); // green Low Byte
  packetBuffer[11] = (blueColor & 0xFF00) >> 8; // blue High Byte
  packetBuffer[12] = (blueColor & 0x00FF); // blue Low Byte
  packetBuffer[13] = 0x0F;  //End Byte


  // all Light Packet fields have been given values, now
  // you can send a packet requesting coordination
  udp.beginPacketMulticast(address,  localPort, WiFi.localIP()); //
  udp.write(packetBuffer, PACKET_SIZE);
  udp.endPacket();
}


// delay 0-MAXDELAY seconds
#define MAXDELAY 500


void broadcastARandomUpdatePacket()
{


  int sendToLightSwarm = 255;
  Serial.print("Broadcast ToSwarm = ");
  Serial.print(sendToLightSwarm);
  Serial.print(" ");


  // delay 0-MAXDELAY seconds
  int randomDelay;
  randomDelay = random(0, MAXDELAY);
  Serial.print("Delay = ");
  Serial.print(randomDelay);
  Serial.print("ms : ");


  delay(randomDelay);

  IPAddress sendSwarmAddress(192, 168, 1, sendToLightSwarm); // my Swarm Address
  sendLightUpdatePacket(sendSwarmAddress) ;


}

In the function in Listing 2-11, I check if we just became master and also update the status of all the LightSwarm devices. This is where the timeout function is implemented that will remove stale or dead devices from the swarm.

Listing 2-11. Master Check and Update
void checkAndSetIfMaster()
{


  int i;
  for (i = 0; i < SWARMSIZE; i++)
  {


#ifdef DEBUG

    Serial.print("swarmClear[");
    Serial.print(i);
    Serial.print("] = ");
    Serial.print(swarmClear[i]);
    Serial.print("  swarmTimeStamp[");
    Serial.print(i);
    Serial.print("] = ");
    Serial.println(swarmTimeStamp[i]);
#endif


    Serial.print("#");
    Serial.print(i);
    Serial.print("/");
    Serial.print(swarmState[i]);
    Serial.print("/");
    Serial.print(swarmVersion[i]);
    Serial.print(":");
    // age data
    int howLongAgo = millis() - swarmTimeStamp[i] ;


    if (swarmTimeStamp[i] == 0)
    {
      Serial.print("TO ");
    }
    else if (swarmTimeStamp[i] == -1)
    {
      Serial.print("NP ");
    }
    else if (swarmTimeStamp[i] == 1)
    {
      Serial.print("ME ");
    }
    else if (howLongAgo > SWARMTOOOLD)
    {
      Serial.print("TO ");
      swarmTimeStamp[i] = 0;
      swarmClear[i] = 0;


    }
    else
    {
      Serial.print("PR ") ;


    }
  }


  Serial.println();
  boolean setMaster = true;


  for (i = 0; i < SWARMSIZE; i++)
  {


    if (swarmClear[mySwarmID] >= swarmClear[i])
    {
      // I might be master!


    }
    else
    {
      // nope, not master
      setMaster = false;
      break;
    }


  }
  if (setMaster == true)
  {
    if (masterState == false)
    {
      Serial.println("I just BECAME Master");
      digitalWrite(0, LOW);
    }


    masterState = true;
  }
  else
  {
    if (masterState == true)
    {
      Serial.println("I just LOST Master");
      digitalWrite(0, HIGH);
    }


    masterState = false;
  }


  swarmState[mySwarmID] = masterState;

}

int setAndReturnMySwarmIndex(int incomingID)
{


  int i;
  for (i = 0; i< SWARMSIZE; i++)
  {
    if (swarmAddresses[i] == incomingID)
    {
       return i ;
    }
    else
    if (swarmAddresses[i] == 0)  // not in the system, so put it in
    {


      swarmAddresses[i] = incomingID;
      Serial.print("incomingID ");
      Serial.print(incomingID);
      Serial.print("  assigned #");
      Serial.println(i);
      return i;
    }


  }  

  // if we get here, then we have a new swarm member.  
  // Delete the oldest swarm member and add the new one in
  // (this will probably be the one that dropped out)


  int oldSwarmID;
  long oldTime;
  oldTime = millis();
  for (i = 0;  i < SWARMSIZE; i++)
 {
  if (oldTime > swarmTimeStamp[i])
  {
    oldTime = swarmTimeStamp[i];
    oldSwarmID = i;
  }


 }

 // remove the old one and put this one in....
 swarmAddresses[oldSwarmID] = incomingID;
 // the rest will be filled in by Light Packet Receive


}

// send log packet to Server if master and server address defined

void sendLogToServer()
{


  // build the string

  char myBuildString[1000];
  myBuildString[0] = '';


  if (masterState == true)
  {
    // now check for server address defined
    if ((serverAddress[0] == 0) && (serverAddress[1] == 0))
    {
      return;  // we are done.  not defined
    }
    else
    {
      // now send the packet as a string with the following format:
      // swarmID, MasterSlave, SoftwareVersion, clearColor, Status | ....next Swarm ID
      // 0,1,15,3883, PR | 1,0,14,399, PR | ....


      int i;
      char swarmString[20];
      swarmString[0] = '';


      for (i = 0; i < SWARMSIZE; i++)
      {


        char stateString[5];
        stateString[0] = '';
        if (swarmTimeStamp[i] == 0)
        {
          strcat(stateString, "TO");
        }
        else if (swarmTimeStamp[i] == -1)
        {
          strcat(stateString, "NP");
        }
        else if (swarmTimeStamp[i] == 1)
        {
          strcat(stateString, "PR");
        }
        else
        {
          strcat(stateString, "PR");
        }


        sprintf(swarmString, " %i,%i,%i,%i,%s,%i ", i, swarmState[i], swarmVersion[i], swarmClear[i], stateString, swarmAddresses[i]);

        strcat(myBuildString, swarmString);
        if (i < SWARMSIZE - 1)
        {


          strcat(myBuildString, "|");

        }
      }


    }

    // set all bytes in the buffer to 0
    memset(packetBuffer, 0, BUFFERSIZE);
    // Initialize values needed to form Light Packet
    // (see URL above for details on the packets)
    packetBuffer[0] = 0xF0;   // StartByte
    packetBuffer[1] = LOG_TO_SERVER_PACKET;     // Packet Type
    packetBuffer[2] = localIP[3];     // Sending Swarm Number
    packetBuffer[3] = strlen(myBuildString); // length of string in bytes
    packetBuffer[4] = VERSIONNUMBER;  // Software Version
    int i;
    for (i = 0; i < strlen(myBuildString); i++)
    {
      packetBuffer[i + 5] = myBuildString[i];// first string byte
    }


    packetBuffer[i + 5] = 0x0F; //End Byte
    Serial.print("Sending Log to Sever:");
    Serial.println(myBuildString);
    int packetLength;
    packetLength = i + 5 + 1;


    udp.beginPacket(serverAddress,  localPort); //

    udp.write(packetBuffer, packetLength);
    udp.endPacket();


  }

}

That is the entire LightSwarm device code. When compiling this code on the Arduino IDE targeting the Adafruit ESP8266, we get the following:

Sketch uses 308,736 bytes (29%) of program storage space. Maximum is 1,044,464 bytes.

Global variables use 50,572 bytes (61%) of dynamic memory, leaving 31,348 bytes for local variables. Maximum is 81,920 bytes.

Still a lot of space left for more code. Most of the compiled codes space above are used by the system libraries for WiFi and running the ESP8266.

Self-Organizing Behavior

Why do we say that the LightSwarm code is self-organizing? It is because there is no central control of who is the master and who is the slave. This makes the system more reliable and able to function even in a bad environment. Self-organization is defined as a process where some sort of order arises out of the local interactions between smaller items in an initially disordered system.

Typically these kinds of systems are robust and able to survive in a chaotic environment. Self-organizing systems occur in a variety of physical, biological, and social systems.

One reason to build these kinds of systems is that the individual devices can be small and not very smart, and yet the overall task or picture of the data being collected and processed can be amazingly interesting and informative.

Monitoring and Debugging the System with the Raspberry Pi (the Smart Guy on the Block)

The Raspberry Pi is used in LightSwarm primarily as a data storage device for examining the LightSwarm data and telling what is going on in the swarm. You can send a few commands to reset the swarm, turn lights on, etc., but the swarm runs itself with or without the Raspberry Pi running. However, debugging self-organizing systems like this are difficult without some way of watching what is going on with the swarm, preferably from another computer. And that is what we have done with the LightSwarm Logger software on the Raspberry Pi. The primary design criteria for this software follows:

  • Read and log information on the swarm behavior.

  • Reproduce archival swarm behavior.

  • Provide methods for testing swarm behavior (such as resetting the swarm).

  • Provide real-time information to the Internet on swarm behavior and status.

Remember that the Raspberry Pi is a full, complex, and powerful computer system that goes way beyond what you can do with an ESP8266. First we will look at the LightSwarm logging software and then the software that supports the RasPiConnect LightSwarm panel. Note that we are not storing the information coming from the swarm devices in this software, but we could easily add logging software that would populate a MySQL database that would allow us to store and analyze the information coming in from the swarm.

LightSwarm Logging Software Written in Python

The entire code base of the LightSwarm Logging software is available off the APress site [APress code site] and on the SwitchDoc Labs github site [github.com/switchdoclabs/lightswarm_Pi]. I am picking out the most interesting code in the Logging software to comment on and explain.

First of all, this program is written in Python. Python is a widely used programming language, especially with Raspberry Pi coders. There are a number of device libraries available for building your own IOT devices and there is even a small version that runs on the ESP8266. Python’s design philosophy emphasizes code readability. Indenting is important in Python, so keep that in mind as you look at the code below.

SwitchDoc Note

Python is “weakly typed” meaning you define a variable and the type by the first time you use it. Some programmers like this, but I don’t. Misspelling a variable name makes a whole new variable and can cause great confusion. My prejudice is toward “strongly typed” languages as it tends to reduce the number of coding errors, at the cost of having to think about and declare variables explicitly.

The first section of this program defines all the needed libraries (import statements) and defines necessary “constants.” Python does not have a way to define constants, so you declare variables for constant values, which by my convention are all in uppercase. There are other ways of defining constants by using classes and functions, but they are more complex than just defining another variable. Listing 2-13 shows how the variables and constants are initialized.

Listing 2-13. Import and Constant Value Declaration
'''
    LightSwarm Raspberry Pi Logger
    SwitchDoc Labs
    September 2015
'''


import sys  
import time
import random


from netifaces import interfaces, ifaddresses, AF_INET

from socket import *

VERSIONNUMBER = 6
# packet type definitions
LIGHT_UPDATE_PACKET = 0
RESET_SWARM_PACKET = 1
CHANGE_TEST_PACKET = 2   # Not Implemented
RESET_ME_PACKET = 3
DEFINE_SERVER_LOGGER_PACKET = 4
LOG_TO_SERVER_PACKET = 5
MASTER_CHANGE_PACKET = 6
BLINK_BRIGHT_LED = 7


MYPORT = 2910

SWARMSIZE = 5

Listing 2-14 defines the interface between the LightSwarm Logging software and the RasPiConnect control panel software [ www.milocreek.com ]. The author wrote a tutorial about this command-passing structure in MagPi magazine reprinted on the SwitchDoc Labs web site [ www.switchdoc.com/2014/07/build-control-panels-tutorial-raspiconnect/ ]. These are the three important functions:

  • processCommand(s) - When a command is received from the RasPiConnect server software running on the same computer, this function defines all the actions to be completed when a specific command is received from RasPiConnect.

  • completeCommandWithValue(value) - call function and return a value to RasPiConnect when you have completed a command.

  • completeCommand() - call function when you have completed a command to tell RasPiConnect you are done with the command.

Basically, the idea is that when you ask for a data refresh or push a button on the RasPiConnect control panel, the RasPiConnect server software sends a command to the LightSwarm logging software that is running on a different thread in the same system. Remember that the Raspberry Pi Linux-based system is multitasking and you can run many different programs at once.

Listing 2-14. RaspiConnect Code
logString = ""
# command from RasPiConnect Execution Code


def completeCommand():

        f = open("/home/pi/LightSwarm/state/LSCommand.txt", "w")
        f.write("DONE")
        f.close()


def completeCommandWithValue(value):

        f = open("/home/pi/LightSwarm/state/LSResponse.txt", "w")
        f.write(value)
        print "in completeCommandWithValue=", value
        f.close()


        completeCommand()

def processCommand(s):
        f = open("//home/pi/LightSwarm/state/LSCommand.txt", "r")
        command = f.read()
        f.close()


        if (command == "") or (command == "DONE"):
                # Nothing to do
                return False


        # Check for our commands

        print "Processing Command: ", command
        if (command == "STATUS"):


                completeCommandWithValue(logString)

                return True

        if (command == "RESETSWARM"):

                SendRESET_SWARM_PACKET(s)

                completeCommand()

                return True

        # check for , commands

        print "command=%s" % command
        myCommandList = command.split(',')
        print "myCommandList=", myCommandList


        if (myCommandList.count > 1):  
                # we have a list command


                if (myCommandList[0]== "BLINKLIGHT"):
                        SendBLINK_BRIGHT_LED(s, int(myCommandList[1]), 1)


                if (myCommandList[0]== "RESETSELECTED"):
                        SendRESET_ME_PACKET(s, int(myCommandList[1]))


                if (myCommandList[0]== "SENDSERVER"):
                        SendDEFINE_SERVER_LOGGER_PACKET(s)


                completeCommand()

                return True

        completeCommand()

        return False

In Listing 2-15, I have the actual LightSwarm command implementations for sending packets. Listing 2-15 just shows the first packet type to illustrate the concepts.

Listing 2-15. Light Swam Command Packet Definitions
# UDP Commands and packets

def SendDEFINE_SERVER_LOGGER_PACKET(s):
        print "DEFINE_SERVER_LOGGER_PACKET Sent"
        s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)


        # get IP address
        for ifaceName in interfaces():
                    addresses = [i['addr'] for i in ifaddresses(ifaceName).setdefault(AF_INET, [{'addr':'No IP addr'}] )]
                    print '%s: %s' % (ifaceName, ', '.join(addresses))


        # last interface (wlan0) grabbed
        print addresses
        myIP = addresses[0].split('.')
        print myIP
        data= ["" for i in range(14)]


        data[0] = chr(0xF0)
        data[1] = chr(DEFINE_SERVER_LOGGER_PACKET)
        data[2] = chr(0xFF) # swarm id (FF means not part of swarm)
        data[3] = chr(VERSIONNUMBER)
        data[4] = chr(int(myIP[0])) # first octet of ip
        data[5] = chr(int(myIP[1])) # second octet of ip
        data[6] = chr(int(myIP[2])) # third octet of ip
        data[7] = chr(int(myIP[3])) # fourth octet of ip
        data[8] = chr(0x00)
        data[9] = chr(0x00)
        data[10] = chr(0x00)
        data[11] = chr(0x00)
        data[12] = chr(0x00)
        data[13] = chr(0x0F)


            s.sendto(''.join(data), ('<broadcast>', MYPORT))

The next section of the code to be discussed is the web map that is used by the RasPiConnect web control to display HTML code. The code in Listing 2-16 produces Figure 2-12.

A367492_1_En_2_Fig12_HTML.jpg
Figure 2-12. HTML Web Control in RasPiConnect Produced by Web Map Code in LightSwarm Logging Software
Listing 2-16. Web page Building Code
# build Webmap

def buildWebMapToFile(logString, swarmSize ):

                f = open("/home/pi/RasPiConnectServer/Templates/W-1a.txt", "w")

                webresponse = ""

                swarmList = logString.split("|")
                for i in range(0,swarmSize):
                        swarmElement = swarmList[i].split(",")
                        print "swarmElement=", swarmElement
                        webresponse += "<figure>"
                        webresponse += "<figcaption"
                        webresponse += " style='position: absolute; top: "
                        webresponse +=  str(100-20)
                        webresponse +=  "px; left: " +str(20+120*i)+  "px;'/> "
                        if (int(swarmElement[5]) == 0):
                                webresponse += "&nbsp;&nbsp;&nbsp&nbsp;&nbsp;---"
                        else:
                                webresponse += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;%s" % swarmElement[5]


                        webresponse += "</figcaption>"
                        webresponse += "<img src='" + "192.168.1.40:9750"


                        if (swarmElement[4] == "PR"):
                                if (swarmElement[1] == "1"):
                                        webresponse += "/static/On-Master.png' style='position: absolute; top: "
                                else:
                                        webresponse += "/static/On-Slave.png' style='position: absolute; top: "
                        else:
                                if (swarmElement[4] == "TO"):
                                        webresponse += "/static/Off-TimeOut.png' style='position: absolute; top: "
                                else:
                                        webresponse += "/static/Off-NotPresent.png' style='position: absolute; top: "


                        webresponse +=  str(100)
                        webresponse +=  "px; left: " +str(20+120*i)+  "px;'/> "


                        webresponse += "<figcaption"
                        webresponse += " style='position: absolute; top: "
                        webresponse +=  str(100+100)
                        webresponse +=  "px; left: " +str(20+120*i)+  "px;'/> "
                        if (swarmElement[4] == "PR"):
                                if (swarmElement[1] == "1"):
                                        webresponse += "&nbsp;&nbsp;&nbsp;&nbsp;Master"
                                        else:
                                        webresponse += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Slave"
                        else:
                                if (swarmElement[4] == "TO"):
                                               webresponse += "TimeOut"
                                else:
                                               webresponse += "Not Present"


                        webresponse += "</figcaption>"

                        webresponse += "</figure>"

                #print webresponse
                f.write(webresponse)


                f.close()

Listing 2-17 looks at incoming swarm IDs and builds a current table of matching IDs, removing old ones when adding new ones. The maximum number of swarm devices you can have is five, but can be easily increased.

Listing 2-17. Incoming Swarm Analysis Code
def setAndReturnSwarmID(incomingID):

        for i in range(0,SWARMSIZE):
                if (swarmStatus[i][5] == incomingID):
                               return i
                    else:
                        if (swarmStatus[i][5] == 0):  # not in the system, so put it in


                                      swarmStatus[i][5] = incomingID;
                                      print "incomingID %d " % incomingID
                                      print "assigned #%d" % i
                                      return i


          # if we get here, then we have a new swarm member.  
          # Delete the oldest swarm member and add the new one in
          # (this will probably be the one that dropped out)


          oldTime = time.time();
        oldSwarmID = 0
        for i in range(0,SWARMSIZE):
                  if (oldTime > swarmStatus[i][1]):
                            oldTime = swarmStatus[i][1]
                            oldSwarmID = i


        # remove the old one and put this one in....
         swarmStatus[oldSwarmID][5] = incomingID;
         # the rest will be filled in by Light Packet Receive
        print "oldSwarmID %i" % oldSwarmID


        return oldSwarmID

Finally, Listing 2-18 is the main code for the Python program. It is very similar in function to the setup() code for the ESP8266 in the Arduino IDE. We use this to define variables, send out one-time commands, and set up the UDP interface.

Listing 2-18. LightSwarm Logger Startup Code
# set up sockets for UDP

s=socket(AF_INET, SOCK_DGRAM)
host = 'localhost';
s.bind(('',MYPORT))


print "--------------"
print "LightSwarm Logger"
print "Version ", VERSIONNUMBER
print "--------------"


# first send out DEFINE_SERVER_LOGGER_PACKET to tell swarm where to send logging information

SendDEFINE_SERVER_LOGGER_PACKET(s)
time.sleep(3)
SendDEFINE_SERVER_LOGGER_PACKET(s)


# swarmStatus
swarmStatus = [[0 for x  in range(6)] for x in range(SWARMSIZE)]


# 6 items per swarm item

# 0 - NP  Not present, P = present, TO = time out
# 1 - timestamp of last LIGHT_UPDATE_PACKET received
# 2 - Master or slave status   M S
# 3 - Current Test Item - 0 - CC 1 - Lux 2 - Red 3 - Green  4 - Blue
# 4 - Current Test Direction  0 >=   1 <=
# 5 - IP Address of Swarm


for i in range(0,SWARMSIZE):
        swarmStatus[i][0] = "NP"
        swarmStatus[i][5] = 0


#300 seconds round
seconds_300_round = time.time() + 300.0


#120 seconds round
seconds_120_round = time.time() + 120.0


completeCommand() # ie RasPiConnect System - clear out old commands

Listing 2-19 provides the main program loop. Note this is very similar to the loop() function in the ESP8266. In this code, we check for incoming UDP packets, process RasPiConnect commands, update status information, and perform periodic commands. It is in this loop that we would be storing the Swarm status, packets, and information if we wanted to reproduce the swarm behavior archivally.

Listing 2-19. Raspberry Pi Logger Main Loop
while(1) :

        # receive datclient (data, addr)
        d = s.recvfrom(1024)


        message = d[0]
        addr = d[1]
        if (len(message) == 14):
                if (ord(message[1]) == LIGHT_UPDATE_PACKET):
                     incomingSwarmID = setAndReturnSwarmID(ord(message[2]))
                     swarmStatus[incomingSwarmID][0] = "P"
                     swarmStatus[incomingSwarmID][1] = time.time()  


                if (ord(message[1]) == RESET_SWARM_PACKET):
                     print "Swarm RESET_SWARM_PACKET Received"
                     print "received from addr:",addr


                if (ord(message[1]) == CHANGE_TEST_PACKET):
                     print "Swarm CHANGE_TEST_PACKET Received"
                     print "received from addr:",addr


                if (ord(message[1]) == RESET_ME_PACKET):
                     print "Swarm RESET_ME_PACKET Received"
                     print "received from addr:",addr


                if (ord(message[1]) == DEFINE_SERVER_LOGGER_PACKET):
                     print "Swarm DEFINE_SERVER_LOGGER_PACKET Received"
                     print "received from addr:",addr


                if (ord(message[1]) == MASTER_CHANGE_PACKET):
                     print "Swarm MASTER_CHANGE_PACKET Received"
                     print "received from addr:",addr


                     for i in range(0,14):  
                                print "ls["+str(i)+"]="+format(ord(message[i]), "#04x")
        else:
                if (ord(message[1]) == LOG_TO_SERVER_PACKET):
                        print "Swarm LOG_TO_SERVER_PACKET Received"


                        # process the Log Packet
                        logString = parseLogPacket(message)
                        buildWebMapToFile(logString, SWARMSIZE )


                else:
                        print "error message length = ",len(message)


        if (time.time() >  seconds_120_round):
                # do our 2 minute round
                print ">>>>doing 120 second task"
                sendTo = random.randint(0,SWARMSIZE-1)
                SendBLINK_BRIGHT_LED(s, sendTo, 1)
                seconds_120_round = time.time() + 120.0


        if (time.time() >  seconds_300_round):
                # do our 2 minute round
                print ">>>>doing 300 second task"
                SendDEFINE_SERVER_LOGGER_PACKET(s)
                seconds_300_round = time.time() + 300.0


        processCommand(s)

        #print swarmStatus

Next, I will look at parts of the RasPiConnect Server code.

The RasPiConnect Control Panel in Real Time

RasPiConnect (and the Arduino version, ArduinoConnect) is software designed for the iPad and iPhone for building Internet-enabled control panels connecting to small computers. It is designed to be light in memory and processor usage. It has prebuilt servers in Python for the Raspberry Pi (actually any type of computer running Python) and in C/C++ for use in the Arduino IDE. We could easily implement a version running on the EPS8266 and plan to do that in a future project. You can do complex interface designs using RasPiConnect. Consider the screen in Figure 2-13 showing one of six screens used in ProjectCuracao, a massive environmental sensing solar-powered project running remotely on the Caribbean island of Curacao [ www.switchdoc.com/project-curacao-introduction-part-1/ ].

A367492_1_En_2_Fig13_HTML.jpg
Figure 2-13. Project Curacao RasPiConnect Control Panel - main page

Figure 2-14 shows the LightSwarm RasPiConnect control panel running. The control panel shows that there are currently 3 active LightSwarm devices with 125 being the master (with the brightest lights) while the others are slaves. You can send a variety of commands to the swarm, such as resetting a specific swarm device, blinking lights on a swarm device, or resetting the entire swarm.

A367492_1_En_2_Fig14_HTML.jpg
Figure 2-14. LightSwarm RasPiConnect Control Panel

All the software and configuration files for the RasPiConnect server and Apple app are up on the APress download site [The Apress site] and on the github SwitchDoc Labs site [github.com/switchdoclabs/lightswarm_RasPiConnect]

The RasPiConnect server software is pretty straightforward. There is an excellent tutorial for customizing the server software on the MiloCreek web site[ www.milocreek.com/wiki ].

As an example, Listing 2-20 is the code (located in Local.py) for the Reset Swarm button (as seen on Figure 2-14) and also the code for setting the second from the right meter display in on the LightSwarm control panel.

Listing 2-20. Local.py File for RasPiConnect Code for Reset Swarm Button
#  Reset Swarm

 if (objectServerID == "B-4"):

         #check for validate request
         # validate allows RasPiConnect to verify this object is here
         if (validate == "YES"):
                 outgoingXMLData += Validate.buildValidateResponse("YES")
                 outgoingXMLData += BuildResponse.buildFooter()
                 return outgoingXMLData


         # normal response requested
         answ = "OK"
         #answ = ""
         if (Config.debug()):
                 print "In local B-4"
                 print("answ = %s" % answ)


         sendCommandToLightSwarmAndWait("RESETSWARM")

         responseData = "OK"

         outgoingXMLData += BuildResponse.buildResponse(responseData)
         outgoingXMLData += BuildResponse.buildFooter()
         return outgoingXMLData


if (objectServerID == "M-1"):

         #check for validate request
         if (validate == "YES"):
                 outgoingXMLData += Validate.buildValidateResponse("YES")
                 outgoingXMLData += BuildResponse.buildFooter()


                 return outgoingXMLData

         try:  
                 f = open("/home/pi/LightSwarm/state/LSStatus.txt", "r")
                 logString = f.read()
                 f.close()
         except:
                 logString = ""


         responseData = "%3.2f" % logString.count("PR")
         print "%s = %s" % (objectServerID, responseData)


         outgoingXMLData += BuildResponse.buildResponse(responseData)
         outgoingXMLData += BuildResponse.buildFooter()
         return outgoingXMLData

Results

I constructed a LightSwarm consisting of five individual swarm devices. The swarm can be seen in Figure 2-15.

A367492_1_En_2_Fig15_HTML.jpg
Figure 2-15. The Light Swarm

Finally, some results from the devices are shown in Listing 2-21. First of all is the serial debugging output from a LightSwarm device. First the device is initialized, receives an IP address from the WiFi access point (named gracie in this case), and then starts listening and sending packets. The device is swarm device #125 and receives packets from #123 and #122 but remained the master as the CC (Clear Color) of #125 is 2610 while the incoming packets from #123 and #122 had CC values of 310 and 499, respectively, and were both slaves.

Listing 2-21. Results from LightSwarm IOT Device Run on ESP8266
--------------------------
LightSwarm
Version 27
--------------------------
 09/03/2015
Compiled at:21:30:47 Sep 16 2015


analogRead(A0)=98
44
Found sensor
LightSwarm Instance: 0
Connecting to gracie
..........................
WiFi connected
IP address:
192.168.1.125
Starting UDP
Local port: 2910
clearColor =0
MySwarmID=0
Color Temp: 2390 K - Lux: 630 - R: 1188 G: 848 B: 440 C: 2610  
packetbuffer[1] =0
LIGHT_UPDATE_PACKET received from LightSwarm #123
incomingID 123  assigned #1
LS Packet Recieved from #123 SwarmState:SLAVE CC:310 RC:119 GC:116 BC:69 Version=27
#0/1/27:ME #1/0/27:PR #2/0/0:NP #3/0/0:NP #4/0/0:NP
MasterStatus:MASTER/cc=2610/KS:0.0.0.0
--------
swarmAddress[0] = 125
swarmAddress[1] = 123
swarmAddress[2] = 0
swarmAddress[3] = 0
swarmAddress[4] = 0
--------
Broadcast ToSwarm = 255 Delay = 121ms : Color Temp: 2390 K - Lux: 630 - R: 1188 G: 848 B: 440 C: 2610  
packetbuffer[1] =0
LIGHT_UPDATE_PACKET received from LightSwarm #122
incomingID 122  assigned #2
LS Packet Recieved from #122 SwarmState:SLAVE CC:499 RC:231 GC:182 BC:98 Version=27
#0/1/27:ME #1/0/27:PR #2/0/27:PR #3/0/0:NP #4/0/0:NP
MasterStatus:MASTER/cc=2610/KS:0.0.0.0
--------
swarmAddress[0] = 125
swarmAddress[1] = 123
swarmAddress[2] = 122
swarmAddress[3] = 0
swarmAddress[4] = 0
--------

Listing 2-22 is the output from the Raspberry Pi LightSwarm logging software. The first thing the logging software does is send out a “DEFINE_SERVER_LOGGING_PACKET” to tell the swarm devices the IP address (1.168.1.40) of the server so the swarm master can send logging packets directly to the Raspberry Pi, rather than use the already crowded UDP broadcast ports. Finally, we see a packet coming in from SwarmID #111. Number 111 picked up the server address, and since it was the master of the swarm, it started sending in log packets to the Raspberry Pi. Note from the log results, it looks like #111 is a lonely swarm device with no nearby friends.

Listing 2-22. Output from Raspberry Pi LightSwarm Logger
--------------
LightSwarm Logger
Version  6
--------------
DEFINE_SERVER_LOGGER_PACKET Sent
lo: 127.0.0.1
eth0: No IP addr
wlan0: 192.168
.1.40
['192.168.1.40']
['192', '168', '1', '40']
DEFINE_SERVER_LOGGER_PACKET Sent
lo: 127.0.0.1
eth0: No IP addr
wlan0: 192.168.1.40
['192.168.1.40']
['192', '168', '1', '40']
Swarm DEFINE_SERVER_LOGGER_PACKET Received
received from addr: ('192.168.1.40', 2910)
incomingID 111
assigned #0
Swarm LOG_TO_SERVER_PACKET Received
Log From SwarmID: 111
Swarm Software Version: 28
StringLength: 80
logString:  0,1,28,1672,PR,111 | 1,0,0,0,NP,0 | 2,0,0,0,NP,0 | 3,0,0,0,NP,0 | 4,0,0,0,NP,0
swarmElement= [' 0', '1', '28', '1672', 'PR', '111 ']
swarmElement= [' 1', '0', '0', '0', 'NP', '0 ']
swarmElement= [' 2', '0', '0', '0', 'NP', '0 ']
swarmElement= [' 3', '0', '0', '0', 'NP', '0 ']
swarmElement= [' 4', '0', '0', '0', 'NP', '0 ']
swarmElement= [' 3', '0', '0', '0', 'NP', '0 ']
swarmElement= [' 4', '0', '0', '0', 'NP', '0 ']

What Else Can You Do with This Architecture?

The LightSwarm architecture is flexible. You can change the sensor, add more sensors, and put in more sophisticated algorithms for swarm behavior. In Chapter 5, we extend this architecture to more complex swarm behavior, actually changing some of the physical environment of the swarm devices.

Conclusion

A good part of the IOT will be the gathering of simple, small amounts of data; some analysis on the data; and the communication of that data to servers for action and further analysis on the Internet. The projects in Chapters 3 and 4 are of more complex IOT devices gathering lots of data, processing it, acting on the data, and communicating summaries to the Internet. The LightSwarm does it differently in that the swarm elements are simple and cooperate without a central controller to determine who has the brightest light and then acting on that information (turning the red LED on).

Swarms of IOT devices can be made inexpensively, can exhibit unexpected complex behavior, and be devilishly difficult to debug.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.103.15