© Charles Bell 2020
C. BellBeginning Sensor Networks with XBee, Raspberry Pi, and Arduinohttps://doi.org/10.1007/978-1-4842-5796-8_7

7. Methods for Storing Sensor Data

Charles Bell1 
(1)
Warsaw, VA, USA
 

If you have had success with the projects thus far in the book, you have at your disposal several forms of sensor and data-aggregate nodes. In essence, you have the basic building blocks for constructing a sensor network to monitor and record temperature data. It would not take much more work to add nodes for other environmental sensors such as humidity or barometric pressure. Indeed, the basic sensor node you have built can host a variety of sensors.

If you have run the example projects and experimented with the challenges, no doubt you have noticed that a lot of data is being generated. What do you do with that data? Is it meaningful only at the instant it is generated, or do you think it is more likely that you would want to store the data and examine it later? For example, if you want to know the temperature range for your workshop on a monthly basis throughout the year, logically you need data from an entire year1 to tabulate and average.

Arduino boards don’t have built-in storage devices in general (but some specialized variants do). Raspberry Pi boards come with a secure digital (SD) drive and can accept USB-based storage devices where you can store data, but what do you do with the data from your Arduino-based nodes?

This chapter examines the available storage methods and gives examples of how to store data using those methods. Sample projects are provided to illustrate the mechanisms and code, but I omit the sensor-specific code for brevity.

Storage Methods

Sensor data can come in several forms. Sensors can produce numeric data consisting of floating-point numbers or sometimes integers. Some sensors produce more complex information that is grouped together and may contain several forms of data. Knowing how to interpret the values read is often the hardest part of using a sensor. In fact, you saw this in a number of the sensor node examples. For example, the temperature sensors produced values that had to be converted to scale to be meaningful.

Although it is possible to store all the data as text, if you want to use the data in another application or consume it for use in a spreadsheet or statistical application, you may need to consider storing it either in binary form or in a text form that can be easily converted. For example, most spreadsheet applications can easily convert a text string like “123.45” to a float, but they may not be able to convert “12E236” to a float. On the other hand, if you plan to write additional code for your Arduino sketches or Raspberry Pi Python scripts to process the data, you may want to store the data in binary form to avoid having to write costly (and potentially slow) conversion routines.

But that is only part of the problem. Where you store the data is a greater concern. You want to store the data in the form you need but also in a location (on a device) that you can retrieve it from and that won’t be erased when the host is rebooted. For example, storing data in main memory on an Arduino is not a good idea. Not only does it consume valuable program space, but it is volatile and will be lost when the Arduino is powered off.

The Raspberry Pi offers better options. You can easily create a file and store the data on the root partition or in your home directory on the SD card. This is nonvolatile and does not affect the operation of the Raspberry Pi operating system. The only drawback is that it has the potential to result in too little disk space if the data grows significantly. But the data would have to grow to nearly two gigabytes (for a 2GB SD card) before it would threaten the stability of the operating system (although that can happen).

So, what are your options for storing data with Arduino? Are there any other possibilities with the Raspberry Pi? There are two types of storage to consider: local and remote. Local storage includes any method that results in the data being stored with the node, for example, storing data on the SD card on the Raspberry Pi. Remote storage includes any method where the data is stored on a device or medium that is not directly connected to the node, for example, storing data on a different node or even on a server connected to the Internet.

Storing Date And Time With Samples

Neither the Arduino nor the Raspberry Pi has a real-time clock (RTC) on board. If you want to store your sensor data locally, you have to either store the data with an approximate date and timestamp or use an RTC module to read an accurate date/time value.

Fortunately, there are RTC modules for use with an Arduino or the Raspberry Pi. If your Raspberry Pi is connected to the Internet and you have enabled the network time synchronization feature, you do not need the RTC module. However, if your Raspberry Pi is not connected to the Internet, and you want to store accurate time data, you should consider using the RTC module.

The following sections examine the various local and remote storage options available for the Arduino and Raspberry Pi.

Local Storage Options for the Arduino

Although it is true that the Arduino has no onboard storage devices, there are two ways you can store data locally for the Arduino. You can store data in a special form of nonvolatile memory or on an SD card hosted via either a special SD card shield or an Ethernet shield (most Ethernet shields have a built-in SD card drive).

If you are truly inventive (or perhaps unable to resist a challenge), you can use some of the communication protocols to send data to other devices. For example, you could use the serial interface to write data to a serial device.

The following sections discuss each option in greater detail. Later sections present small projects you can use to learn how to use these devices for storing data.

Nonvolatile Memory

The most common form of nonvolatile memory available to the Arduino is electrically erasable programmable read-only memory (EEPROM—pronounced “e-e-prom” or “double-e prom”). EEPROMs are packaged as chips (integrated circuits). As the name suggests, data can be written to the chip and is readable even after a power cycle but can be erased or overwritten.

Most Arduino boards have a small EEPROM where the sketch is stored and read during power-up. If you have ever wondered how the Arduino does that, now you know. You can write to the unused portion of this memory if you desire, but the amount of memory available is small (512KB for some boards). You can also use an EEPROM and wire it directly to the Arduino via the I2C protocol to overcome this limitation.

Writing to and reading from an EEPROM is supported via a special library that is included in the Arduino IDE. Due to the limited amount of memory available, storing data in the EEPROM memory is not ideal for most sensor nodes. You are likely to exceed the memory available if the data you are storing is large or there are many data items per sample.

You also have the issue of getting the data from the EEPROM for use in other applications. In this case, you would have to build not only a way to write the data but also a way to read the data and export it to some other medium (local or remote).

That is not to say that you should never use EEPROM to store data. Several possible reasons justify storing data in EEPROM. For example, if your sensor node is likely to be isolated, or connectivity to other nodes is limited, you may want to use an EEPROM to temporarily store data while the node is offline. In fact, you could build your sketch to detect when the node goes offline and switch to the EEPROM at that time. This way, your Arduino-based sensor node can continue to record sensor data. Once the node is back online, you can write your sketch to dump the contents of the EEPROM to another node (remote storage).

SD Card

You can also store (and retrieve) data on an SD card. The Arduino IDE has a library for interacting with an SD drive. In this case, you would use the library to access the SD drive via an SD shield or an Ethernet shield.

Storing data on an SD card is done via files. You open a file and write the data to it in whatever format is best for the next phase in your data analysis. Examples in the Arduino IDE and elsewhere demonstrate how to create a web server interface for your Arduino that displays the list of files available on the SD card.

Compared to EEPROMs, SD cards store many times more data. You can purchase high-density SD cards that exceed 128GB of storage space. That’s a lot of sensor data!

You may choose to store data to an SD card in situations where your sensor node is designed as a remote sensor with no connectivity to other nodes, or you can use it as a backup-logging device in case your sensor node is disconnected or your data-aggregator node goes down. Because the card is removable and readable in other devices, you can read it on another device when you want to use the data.

Using an SD card means you can move the data from the sensor node to a computer simply by unplugging the card from the Arduino and plugging it in to the SD card reader in your computer.

Project: Saving Data in Nonvolatile Memory

Recall that you can use the local EEPROM on an Arduino to store data. There are some excellent examples in the Arduino IDE that I encourage you to experiment with at your leisure. They are located under the Examples menu under the EEPROM submenu. You need only an Arduino and your laptop to experiment with writing to and from the EEPROM on the Arduino.

Rather than rehash the example sketch for using the built-in EEPROM, this section outlines a project to use an external EEPROM to store data. Unlike the local EEPROM, which uses a dedicated library to interact with, an external EEPROM uses the I2C communication protocol.

Hardware Setup

The hardware for this project consists of a 24LC256 or 24LC512 EEPROM chip like those from SparkFun (www.sparkfun.com/products/525), a pushbutton, jumper wires, and an Arduino. Figure 7-1 shows a typical 24LC256 pin-mount EEPROM chip.
../images/313992_2_En_7_Chapter/313992_2_En_7_Fig1_HTML.jpg
Figure 7-1

I2C EEPROM chip (courtesy of SparkFun)

The pushbutton will allow you to reset the memory on the chip. Doing so erases the data values stored, resetting the memory configuration for reuse. You will find this feature particularly handy when using the sketch for the first time, debugging problems, and reusing the chip once memory has been read and stored on another medium.

The chip communicates via an I2C bus. You can set the address for the chip by connecting ground or power to pins A0–A2, as shown in Figure 7-2. You can think of this as a binary number, where connecting ground to all three pins is the lowest address available (0x50) and power to all three pins is the highest address available (0x57). Table 7-1 shows the possible addresses and connections required. You use the lowest address (0x50) by connecting ground to all three pins.
../images/313992_2_En_7_Chapter/313992_2_En_7_Fig2_HTML.jpg
Figure 7-2

Pinout of the I2C EEPROM

Table 7-1

Setting the Address of the I2C EEPROM

Address

A0

A1

A2

0x50

Ground

Ground

Ground

0x51

Ground

Ground

+5V

0x52

Ground

+5V

Ground

0x53

Ground

+5V

+5V

0x54

+5V

Ground

Ground

0x55

+5V

Ground

+5V

0x56

+5V

+5V

Ground

0x57

+5V

+5V

+5V

Now that you understand how to address the chip, let’s connect it to your Arduino. Begin by placing the chip in a breadboard with the half circle pointing to the left. This establishes pin 1 as the upper-right pin. Connect a ground wire to all four pins on the top side of the chip. These are pins 1–4, as shown in Figure 7-2.

Next, connect pin 5 (SDA) to pin 4 on the Arduino and pin 6 (SCL) to pin 5 on the Arduino. Connect a ground wire to pin 7. Then connect positive voltage (+5V) to pin 8. We also use 4.7K Ohm resistors on the I2C lines to reduce noise. Finally, connect the pushbutton to pin 2 on one side and power on the other. Use a 10K Ohm resistor to pull the button HIGH (connect it to positive voltage) as you did in a previous project. See Figure 7-3 for a detailed wiring diagram. Be sure to double-check your connections.
../images/313992_2_En_7_Chapter/313992_2_En_7_Fig3_HTML.jpg
Figure 7-3

Wiring the EEPROM to the Arduino

Tip

If you are using the Leonardo board, you need to use the SDC and SCL pins located near the USB port. For the Uno board, they are located at A4 and A5 and on the Mega 2560, they are on pins 20 and 21. Check the hardware pinout for your board to ensure you use the correct I2C interface connections.

Software Setup

With the wiring in place, you are ready to start writing a sketch to read and write data. Rather than write a script to simply store data, in this example, you write a sketch to let you write data to and read it from the chip. You also include a reset operation to allow you to overwrite any memory.

You add the read methods so that you can create additional sketches to read data, should you wish to review the data, move the chip (data) to another Arduino, or use another sketch to process the data.

Let’s get started. You use the I2C library (called Wire) to interact with the EEPROM. Open a new sketch, and enter the following:
#include <Wire.h>
#define FIRST_SAMPLE 0x02  // First position of first sample
#define MEM_ADDR 0x50      // EEPROM address
#define BUTTON_PIN 0x02    // Button pin
#define EEPROM_SIZE 32768  // Size of 24LC256
#define SAMPLE_BYTES 2     // Size of sample in bytes
int next_index = 0;        // Address of first sample

These statements include the Wire library and define a number of constants you use in the sketch. Notice that you have an address for the first sample (the position in memory on the chip), the address for the chip, a pin for the pushbutton, the maximum size (for the 256 chip), and the number of bytes per sample.

You need a number of methods. You need the ability to write a single byte to memory, store a sample, read a byte, and read a sample. Let’s look at the simplest forms of these methods—the read byte method. In the following code, address refers to the address of the EEPROM chip, and index is the location in memory that you want to access:
byte read_byte(int address, unsigned int index)  {
  byte data = 0xFF;
  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Wire.endTransmission();
  Wire.requestFrom(address,1);
  if (Wire.available()) {
    data = Wire.read();
  }
  return data;
}

Notice the process for communicating with the chip. First, you start a transmission with the chip, send the address that you intend to read, and then end the transmission. The address is a two-byte value, and the statements show you how to manipulate the bytes to form a word (two bytes). The next method, requestFrom(), tells the chip you want to read a single byte. If the chip is ready, you read the data. Finally, you return the value to the caller.

You use the same format for every operation you wish to use with the chip. Let’s look at the write method to write a single byte to the chip:
void write_byte(int address, unsigned int index, byte data) {
  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Wire.write(data);
  Wire.endTransmission();
  delay(5);
}

Notice that you have the same setup—you begin the transmission and set the value at the index specified. What differs is that you send the data (write it) before you end the transmission.

But how do you know what is written to which address (or index)? Rather than just write data willy-nilly or in some illogical order, let’s use the first byte at index 0 to store the number of data samples (or rows) and the second byte to store how many bytes each sample consumes (or columns). In this way, you make the data easier to read because it is uniform and easier to manage on a reboot.

In fact, let’s add a new method named sample_data() to write some data and display the contents of the data in the EEPROM on startup. Recall for the Arduino that if you want to execute a method once at startup, you place it in the setup() method. The following shows how you can use the existing read method to read data from the EEPROM and display the information in the serial monitor:
void sample_data(void) {
  int bytes_per_sample = SAMPLE_BYTES;
  byte buffer[SAMPLE_BYTES];
  next_index = read_byte(MEM_ADDR, 0);
  bytes_per_sample = read_byte(MEM_ADDR, 1);
  Serial.print("Byte pointer: ");
  Serial.println(next_index, DEC);
  Serial.print("Bytes per sample: ");
  Serial.println(bytes_per_sample, DEC);
  Serial.print("Number of samples:");
  Serial.println((next_index/bytes_per_sample)-1, DEC);
  // Add some sample data
  record_sample(MEM_ADDR, 6011);
  record_sample(MEM_ADDR, 8088);
  // Example of how to read sample data - read last 2 values
  read_sample(MEM_ADDR, next_index-(SAMPLE_BYTES * 2), buffer);
  Serial.print("First value: ");
  Serial.println((int)(buffer[0] << 8) + (int)buffer[1]);
  read_sample(MEM_ADDR, next_index-SAMPLE_BYTES, buffer);
  Serial.print("Second value: ");
  Serial.println((int)(buffer[0] << 8) + (int)buffer[1]);
}
This technique makes it easy to verify that the code is working by running the dump method on startup as shown as follows. In essence, you create a crude self-diagnostic mechanism that you can use to check the state of the data. If you see anything other than valid data at startup, you know something has gone wrong:
void setup(void) {
  Serial.begin(115200);
  while (!Serial);
  Wire.begin();
  Serial.println("Welcome to the Arduino external EEPROM project.");
  initialize(MEM_ADDR);
  sample_data();
}
But wait! What does this code do if you encounter an uninitialized EEPROM? In that case, you can create a special method to initialize the EEPROM. The following code shows the initialize() method:
void initialize(int address) {
  // Clear memory
  // NOTE: replace '10' with EEPROM_SIZE to erase all data
  for (int i = 0; i < 10; i++) {
    write_byte(address, i, 0xFF);
  }
  write_byte(address, 0, FIRST_SAMPLE);
  write_byte(address, 1, SAMPLE_BYTES);
  Serial.print("EEPROM at address 0x");
  Serial.print(address, HEX);
  Serial.println(" has been initialized.");
}

You use the write_byte() method to write 0 for the number of bytes and the constant defined earlier for the number of bytes per sample. The method begins by writing 0xff to the first 10 bytes to ensure that you have no data stored; then the number of bytes is written to index 0 and the number of bytes per sample to index 1. You add some print statements for feedback.

But how does this method get called? One way would be to put it in your setup() method as the first call after the call to initialize the Wire library, but that would mean you would have to comment out the other methods, load the sketch, execute it, remove the method, and reload. That seems like a lot of extra work. A better way is to trigger this method with a pushbutton. Code to do this is placed in the loop() method, as shown here:
if (digitalRead(BUTTON_PIN) == LOW) {
  initialize(MEM_ADDR);
  delay(500); // debounce
}
Now that you can read and write a byte and initialize the chip, you also need to be able to read a sample in case you want to use the chip in another sketch to process the data. The following code shows a method to read a sample:
void read_sample(int address, unsigned int index, byte *buffer) {
  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Wire.endTransmission();
  Wire.requestFrom(address, SAMPLE_BYTES);
  for (int i = 0; i < SAMPLE_BYTES; i++) {
    if (Wire.available()) {
      buffer[i] = Wire.read();
    }
  }
}
Notice that you form a sequence of events similar to read_byte(). But rather than read a single byte, you use a loop to read the number of bytes for a sample. You also need a method to store (write) a sample to the chip:
void write_sample(int address, unsigned int index, byte *data) {
  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Serial.print("START: ");
  Serial.println(index);
  for (int i = 0; i < SAMPLE_BYTES; i++) {
    Wire.write(data[i]);
  }
  Wire.endTransmission();
  delay(5); // wait for chip to write data
}

Once again, the method is similar to the write_byte() method, but you use a loop to write the bytes for a sample. Notice that you include a debug statement to show the starting index used. You do this so that you can see the value increase if you run the sketch multiple times.

Note

You may have noticed that I duplicated the code among the *_byte() and *_sample() methods. I did so for clarity of the code, but it isn’t strictly necessary. For example, you could consolidate the code if you changed the *_sample() methods to use an additional parameter indicating how many bytes to read/write. I leave this optimization to you as an exercise.

There is one more method to consider. Recall that you use a counter stored in index 0 to record the number of samples written. The write_sample() method simply writes a sample at a specific index. What you need is a method that manages the sample counter and stores the sample. Thus, you create a record_sample() method to handle the higher-level operation:
void record_sample(int address, int data) {
  byte sample[SAMPLE_BYTES];
  sample[0] = data >> 8;
  sample[1] = (byte)data;
  write_sample(address, next_index, sample);
  next_index += SAMPLE_BYTES;
  write_byte(address, 0, next_index);
}

Notice how you keep track of the number of samples and the next index for the next sample. You use the variable you created earlier and increment it by the number of bytes in the sample. This way, you always know what the next address is without reading the number of samples first and calculating the index. The last method updates the number of samples value.

Now that you have all the building blocks, Listing 7-1 shows the completed code for this sketch. Save the sketch as external_eeprom.ino. Notice that in the sketch you do not include any code to read from sensors. I left this out for brevity and included some debug statements (shown in bold) in the setup() method instead to show how you record samples. Be sure to remove these statements when you modify the sketch for use with a sensor.
/**
  Beginning Sensor Networks Second Edition
  Sensor Networks Example Arduino External EEPROM data store
  This project demonstrates how to save and retrieve sensor data
  to/from an external EEPROM chip.
*/
#include <Wire.h>
#define FIRST_SAMPLE 0x02 // First position of fist sample
#define MEM_ADDR 0x50     // EEPROM address
#define BUTTON_PIN 0x02   // Button pin
#define EEPROM_SIZE 32768 // Size of 24LC256
#define SAMPLE_BYTES 2    // Size of sample in bytes
int next_index = 0;       // Address of first sample
/* Initialize the chip erasing data */
void initialize(int address) {
  // Clear memory
  // NOTE: replace '100' with EEPROM_SIZE to erase all data
  for (int i = 0; i < 100; i++) {
    write_byte(address, i, 0x00);
  }
  write_byte(address, 0, FIRST_SAMPLE);
  write_byte(address, 1, SAMPLE_BYTES);
  Serial.print("EEPROM at address 0x");
  Serial.print(address, HEX);
  Serial.println(" has been initialized.");
}
/* Write a sample to the chip. */
void write_sample(int address, unsigned int index, byte *data) {
  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Serial.print("START: ");
  Serial.println(index);
  for (int i = 0; i < SAMPLE_BYTES; i++) {
    Wire.write(data[i]);
  }
  Wire.endTransmission();
  delay(5); // wait for chip to write data
}
/* Write a byte to the chip at specific index (offset). */
void write_byte(int address, unsigned int index, byte data) {
  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Wire.write(data);
  Wire.endTransmission();
  delay(5);
}
/* Read a sample from an index (offset). */
void read_sample(int address, unsigned int index, byte *buffer) {
  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Wire.endTransmission();
  Wire.requestFrom(address, SAMPLE_BYTES);
  for (int i = 0; i < SAMPLE_BYTES; i++) {
    if (Wire.available()) {
      buffer[i] = Wire.read();
    }
  }
}
/* Read a byte from an index (offset). */
byte read_byte(int address, unsigned int index)  {
  byte data = 0xFF;
  Wire.beginTransmission(address);
  Wire.write((int)(index >> 8));   // MSB
  Wire.write((int)(index & 0xFF)); // LSB
  Wire.endTransmission();
  Wire.requestFrom(address,1);
  if (Wire.available()) {
    data = Wire.read();
  }
  return data;
}
/* Save a sample to the data chip and increment next address counter. */
void record_sample(int address, int data) {
  byte sample[SAMPLE_BYTES];
  sample[0] = data >> 8;
  sample[1] = (byte)data;
  write_sample(address, next_index, sample);
  next_index += SAMPLE_BYTES;
  write_byte(address, 0, next_index);
}
/* Example write data sample */
void sample_data(void) {
  int bytes_per_sample = SAMPLE_BYTES;
  byte buffer[SAMPLE_BYTES];
  next_index = read_byte(MEM_ADDR, 0);
  bytes_per_sample = read_byte(MEM_ADDR, 1);
  Serial.print("Byte pointer: ");
  Serial.println(next_index, DEC);
  Serial.print("Bytes per sample: ");
  Serial.println(bytes_per_sample, DEC);
  Serial.print("Number of samples:");
  Serial.println((next_index/bytes_per_sample)-1, DEC);
  // Add some sample data
  record_sample(MEM_ADDR, 6011);
  record_sample(MEM_ADDR, 8088);
  // Example of how to read sample data - read last 2 values
  read_sample(MEM_ADDR, next_index-(SAMPLE_BYTES * 2), buffer);
  Serial.print("First value: ");
  Serial.println((int)(buffer[0] << 8) + (int)buffer[1]);
  read_sample(MEM_ADDR, next_index-SAMPLE_BYTES, buffer);
  Serial.print("Second value: ");
  Serial.println((int)(buffer[0] << 8) + (int)buffer[1]);
}
void setup(void) {
  Serial.begin(115200);
  while (!Serial);
  Wire.begin();
  Serial.println("Welcome to the Arduino external EEPROM project.");
  initialize(MEM_ADDR);
  sample_data();
}
void loop() {
  delay(2000);
  if (digitalRead(BUTTON_PIN) == LOW) {
    initialize(MEM_ADDR);
    delay(500); // debounce
  }
  //
  // Read sensor data and record sample here
  //
  sample_data();
}
Listing 7-1

Storing and Retrieving Data on an External EEPROM

Notice that you include some additional statements for communicating the progress of the sketch via the serial monitor. Take some time to examine these so that you are familiar with what to expect when the sketch runs.

Tip

If you want to write-protect the chip, disconnect the WP pin. Doing so makes the chip read-only.

Testing the Sketch

To test the sketch, be sure the code compiles and you have your hardware set up correctly. When you have a sketch that compiles, upload it to your Arduino and launch a serial monitor.

When the sketch is loaded for the first time, you need to press the button to initialize the EEPROM. This is because the values on the chip are uninitialized for a new chip. You only have to do this the first time you run the sketch. Once you’ve done that, you should see output similar to that in Listing 7-2.
Welcome to the Arduino external EEPROM project.
EEPROM at address 0x50 has been initialized.
Byte pointer: 2
Bytes per sample: 2
Number of samples: 0
START: 2
START: 4
First value: 6011
Second value: 8088
Byte pointer: 6
Bytes per sample: 2
Number of samples: 2
START: 6
START: 8
First value: 6011
Second value: 8088
Byte pointer: 10
Bytes per sample: 2
Number of samples: 4
START: 10
START: 12
First value: 6011
Second value: 8088
EEPROM at address 0x50 has been initialized.
Byte pointer: 2
Bytes per sample: 2
Number of samples: 0
START: 2
START: 4
First value: 6011
Second value: 8088
Byte pointer: 6
Bytes per sample: 2
Number of samples: 2
START: 6
START: 8
First value: 6011
Second value: 8088
Listing 7-2

Serial Monitor Output for EEPROM Example

Did you see something similar? If you run the sketch again (e.g., by pressing the Reset button), you should see the value for the start index (from the write_sample() method) increase. Go ahead and give it a try.

Once you’ve done it a few times Saving Data in Nonvolatile Memory, press the pushbutton and notice what happens. As you can see in Listing 7-2, the start index is reset, and the next samples are stored at the beginning of memory.

For More Fun

The sketch for this project has a lot of promise. No doubt you can think of a number of things you could do with this code. The following are some suggestions for improving the code and experimenting with using an external EEPROM:
  • Add some visual aids for use in embedded projects (cases with no serial monitor capability). You can add an LED that illuminates when there is data on the chip. You can also add a set of seven-segment LEDs to display the number of data samples stored.

  • Improve the code for reuse. Begin by removing the redundancy described earlier in the read and write methods, and then move the code to a class to make it easier to use the EEPROM in other sketches.

  • Add a second EEPROM chip to expand the amount of storage available. Hint: You need to set each chip to a different address, but the methods used are the same.

  • Perhaps a bit easier and more inline with the hardware-hacking element of Arduino is moving the EEPROM to another Arduino and reading all the values stored. This demonstrates the nonvolatile nature of EEPROM chips.

Caution

Use appropriate grounding to avoid electrostatic discharge (ESD) damage to the chip.

Project: Writing Data to an SD Card

Aside from an EEPROM chip, you can also store data locally on an Arduino by writing the data to an SD drive. The SD drive is a good choice for storing data because the data is stored in files, which other devices can read (and write to).

For example, although writing data to an EEPROM chip is not difficult, reading that chip on a personal computer requires writing a sketch for the Arduino to transfer the data. However, the SD card can be removed from the Arduino (once files are closed) and inserted in an SD drive connected to a personal computer, allowing you to read the files directly. Thus, the SD card makes a better choice for sensor networks where your sensor nodes are not connected via a network or other wireless connections.

There are several choices for adding an SD card reader to an Arduino. Two of the most popular are the Arduino Ethernet shield and the microSD shield from SparkFun (www.sparkfun.com/categories/240). If you use the Arduino Ethernet shield, you can use the networking capabilities and the SD card together. A number of similar devices are available from a variety of vendors.

Adafruit also has a Data Logging shield for Arduino with an onboard SD drive (www.adafruit.com/product/1141). The Data Logging shield also includes an RTC, making it possible to store date and time along with the sample. I discuss using an RTC in the next project.

Tip

Both the microSD shield and the Data Logging shield offer a prototyping area that you can use to mount your sensor components or even an XBee module.

An SD drive allows you to create a hybrid node where you store data locally as well as transmit it to another node in the network. This redundancy is one of the ways you can build durability in to your sensor network. For example, if a node loses its connection to another node via the network, it can still record its data locally. Although it is a manual process to recover the data (you must go get the SD card), the fact that the data is recoverable at all means the network can survive network failures without losing data.

It is possible to use an EEPROM as a local storage backup option, but an EEPROM is harder to use, is not as durable as an SD card, does not have the same storage capacity, and is not as easy to use in other devices.

There is one other very important thing to consider concerning building a durable sensor node. Having a local backup of the data may not be helpful if you do not know when the data was stored. The Arduino does not have any time-keeping capability beyond a limited accuracy cycle time. Thus, if you store data locally without a timestamp of any kind that you can relate to other data, the samples taken may not be meaningful beyond the sequence itself (the order of the values).

To mitigate this, you can add an RTC module to the Arduino. The RTC allows you to store the date and time a sample was taken. This information may be critical if you are trying to plot values over time or want to know when a spurious or otherwise interesting event took place.

Hardware Setup

The hardware for this project uses the Arduino Ethernet shield, the microSD shield from SparkFun (with an SD card installed), or the Data Logging shield from Adafruit. For simplicity, I used the Arduino Ethernet shield and show the code changes necessary to use the microSD shield or the Data Logging shield (via #define statements).

You also need the RTC module. There is an excellent product from Adafruit that performs very well and includes an onboard battery that powers the clock even when the Arduino is powered down. Adafruit’s DS1307 Real-Time Clock breakout board kit (www.adafruit.com/product/3296) is an outstanding module to add to your project. Figure 7-4 shows the Adafruit RTC module.
../images/313992_2_En_7_Chapter/313992_2_En_7_Fig4_HTML.jpg
Figure 7-4

DS1307 Real-Time Clock breakout board (courtesy of Adafruit)

SparkFun also has a product named Real-Time Clock module (www.sparkfun.com/products/99) that uses the same DS1307 chip and interface as the Adafruit offering. You can use either in this project.

Note

The Adafruit RTC module requires assembly. The RTC module from SparkFun does not.

The RTC module uses an I2C interface that is easy to connect to the Arduino. Simply connect 5V power to the 5V pin, ground to the GND pin, the SDA pin to pin 4 on the Arduino, and the SCL pin to pin 5 on the Arduino. Figure 7-5 shows the wiring diagram for connecting the RTC module.
../images/313992_2_En_7_Chapter/313992_2_En_7_Fig5_HTML.jpg
Figure 7-5

Arduino with an Ethernet shield and RTC module

Notice that the Ethernet shield is installed on the Arduino. Wiring connections would be the same if you were using the SparkFun microSD shield.

For this project, we will use the Adafruit Data Logging shield with an Arduino Uno Wi-Fi to keep the wiring to a minimum. In fact, all you need to do is plug the shield into your Uno and off you go! Figure 7-6 shows what the Data Logging shield looks like.
../images/313992_2_En_7_Chapter/313992_2_En_7_Fig6_HTML.jpg
Figure 7-6

Adafruit Data Logging shield (courtesy of Adafruit)

Note

If you do not have a Data Logging shield, you can use the RTC module as described earlier.

Software Setup

With the Data Logging shield in place, you are ready to start writing a sketch to write data to the SD card. Be sure to insert a formatted SD card before you power on the board. But first, you must download and install the RTC library from Adafruit (https://github.com/adafruit/RTClib).

Recall, to install a library, we open the SketchInclude LibraryLibrary Manager and, once it has loaded all of the headers, type in RTCLib and select the library from Adafruit and click Install to install it. Figure 7-7 shows the library used for this project.
../images/313992_2_En_7_Chapter/313992_2_En_7_Fig7_HTML.jpg
Figure 7-7

Installing the Adafruit RTCLib in Library Manager

Once the library is downloaded and installed (and you’ve restarted the Arduino IDE), you can begin a new sketch named sd_file_example.ino. Enter the following code to specify the modules you need to use in the sketch. You need the Wire, RTC, SD, and String libraries:
#include <Wire.h>
#include "RTClib.h"
#include <SD.h>
#include <String.h>
Next, you need to define the pin to use to communicate with the SD drive. The following are the definitions for all three SD drive options described earlier. I use the Ethernet shield in this example; but if you are not using the Ethernet shield, you can comment out that line and uncomment out the line that corresponds with the shield you are using. You also include a definition for the name of the file you use to store samples. Note that we must use the 8.3 file name format when using the microSD formatted as FAT.2
// Pin assignment for Arduino Ethernet shield
// #define SD_PIN 4
// Pin assignment for SparkFun microSD shield
//#define SD_PIN 8
// Pin assignment for Adafruit Data Logging shield
#define SD_PIN 10
// Sensor data file - require 8.3 file name
#define SENSOR_DATA "sensdata.txt"
Now you declare some variables. You need one for the RTC module and one for the file you use on the SD drive. Notice I used the RTC_PCF8523 module since the Data Logging shield has that RTC module. Be sure to use the RTC module that matches your RTC chip.
RTC_PCF8523 rtc;
File sensor_data;

With the preliminaries complete, you need a method to save a sensor sample to the SD card. The method must read the date and time from the RTC module, accept the sample as a parameter, and store the data. In this example, you place the date and time first, followed by the sample value. Name this method record_sample().

Reading from the RTC module is easy with the RTC library. You simply use the library to get the current date and time with the now() method. From there, you can call methods to get the month, day, year, hour, and so on. Forming the string to write to the file can be done in a variety of ways. I used the string class to construct the string. Feel free to use any other method you favor instead:
// Capture the date and time
DateTime now = rtc.now();
Writing to the file is very easy. You simply open the file in write mode (FILE_WRITE) that automatically permits any writes to be written to the end of the file (append). This is nice because you don’t have to worry about seeking or finding out where a file pointer is in the file. Opening the file returns a file object instance, which you can use to write data. Writing to the file (once it is opened) requires only a single method call. The following code shows a simplified set of calls to open a file using the SD library and write data. I leave the details of the record_sample() method for you to explore in Listing 7-2:
// Open the file
sensor_data = SD.open(SENSOR_DATA, FILE_WRITE);
// Save the data
sensor_data.write(1234);
sensor_data.write(" ");
// Close the file
sensor_data.close();
Of course, you need a few things to properly set up the components and libraries. The setup() method should contain, at a minimum, initialization for the Serial, Wire, and RTC libraries (by calling their begin() methods) and a call to the SD library to start communication with the SD drive. The following is an excerpt of the code needed for these steps. Notice that you also initialize the date and time for the RTC based on the last compiled date and time of the sketch (effectively, the date and time it was uploaded):
void setup () {
  Serial.begin(115200);
  while(!Serial);
  Wire.begin();
  rtc.begin();
  if (!rtc.initialized()) {
    Serial.println("RTC is NOT running!");
    // Set time to date and time of compilation
    rtc.adjust(DateTime(__DATE__, __TIME__));
  }
  // disable w5100 SPI (if needed)
  // pinMode(10,OUTPUT);
  // digitalWrite(10,HIGH);
  // Initialize the SD card.
  Serial.print("Initializing SD card...");
  if (!SD.begin(SD_PIN)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");
...
}

Notice that also you have code to turn off the Ethernet W5100 SPI interface. This is only necessary for the Ethernet shield and then only if you do not plan to use the networking capabilities.

There is one other thing you might want to add. You may want to check to see if you can read the file on the SD card. It is not enough to simply initialize the SD library. It is possible the SD drive will communicate properly, but you cannot open or create files on the card itself. Add the following code to the setup() method as an extra check. In this case, you check to see whether the file exists and, if it does not, attempt to create the file. You print a message if you get an error on the open call:
// Check for file. Create if not present
if (!SD.exists(SENSOR_DATA)) {
  Serial.print("Sensor data file does not exit. Creating file...");
  sensor_data = SD.open(SENSOR_DATA, FILE_WRITE);
  if (!sensor_data) {
    Serial.println("ERROR: Cannot create file.");
  }
  else {
    sensor_data.close();
    Serial.println("done.");
  }
}

The loop() method is where you place calls to the record_sample() method. In this case, leave the loop() method empty for brevity. Feel free to add your own code to read sensors here and call the record_sample() method for each.

Listing 7-3 shows the complete code for this project. Although the explanation thus far has been about the key parts of the sketch, notice that the listing adds additional error-handing code to make sure the SD drive is initialized properly and the file exists and can be written.
/**
  Beginning Sensor Networks Second Edition
  Sensor Networks Example Arduino SD card data store
  This project demonstrates how to save sensor data to a
  microSD card.
*/
#include <Wire.h>
#include "RTClib.h"
#include <SD.h>
#include <String.h>
// Pin assignment for Arduino Ethernet shield
//#define SD_PIN 4
// Pin assignment for SparkFun microSD shield
//#define SD_PIN 8
// Pin assignment for Adafruit Data Logging shield
#define SD_PIN 10
// Sensor data file - require 8.3 file name
#define SENSOR_DATA "sensdata.txt"
RTC_PCF8523 rtc;
File sensor_data;
void record_sample(int data) {
  // Open the file
  sensor_data = SD.open(SENSOR_DATA, FILE_WRITE);
  if (!sensor_data) {
    Serial.println("ERROR: Cannot open file. Data not saved!");
    return;
  }
  // Capture the date and time
  DateTime now = rtc.now();
  String timestamp(now.month(), DEC);
  timestamp += ("/");
  timestamp += now.day();
  timestamp += ("/");
  timestamp += now.year();
  timestamp += (" ");
  timestamp += now.hour();
  timestamp += (":");
  timestamp += now.minute();
  timestamp += (":");
  timestamp += now.second();
  timestamp += (" ");
  // Save the sensor data
  sensor_data.write(&timestamp[0]);
  String sample(data, DEC);
  sensor_data.write(&sample[0]);
  sensor_data.write(" ");
  // Echo the data
  Serial.print("Sample: ");
  Serial.print(timestamp);
  Serial.print(data, DEC);
  Serial.println();
  // Close the file
  sensor_data.close();
}
void setup () {
  Serial.begin(115200);
  while(!Serial);
  Wire.begin();
  rtc.begin();
  if (!rtc.initialized()) {
    Serial.println("RTC is NOT running!");
    // Set time to date and time of compilation
    rtc.adjust(DateTime(__DATE__, __TIME__));
  }
  // disable w5100 SPI
  // pinMode(10,OUTPUT);
  // digitalWrite(10,HIGH);
  // Initialize the SD card.
  Serial.print("Initializing SD card...");
  if (!SD.begin(SD_PIN)) {
    Serial.println("initialization failed!");
    return;
  }
  Serial.println("initialization done.");
  // Check for file. Create if not present
  if (!SD.exists(SENSOR_DATA)) {
    Serial.print("Sensor data file does not exit. Creating file...");
    sensor_data = SD.open(SENSOR_DATA, FILE_WRITE);
    if (!sensor_data) {
      Serial.println("ERROR: Cannot create file.");
    }
    else {
      sensor_data.close();
      Serial.println("done.");
    }
  }
  // Record some test samples .
  record_sample(1);
  record_sample(2);
  record_sample(3);
}
void loop () {
  // Read sensor data here and record with record_sample()
}
Listing 7-3

Storing Data on an SD Card

I added debug statements to the setup() method for illustration purposes and to make sure the sketch works. Placing these calls in the setup() method permits you to load the sketch (or reboot the Arduino) and check the contents of the SD card to see if the code worked. If you place the statements in the loop() method, then depending on when you turn off your Arduino (unplug it), you may not know how many lines were added or even if the file were closed properly. Placing the record_sample() statements in the setup() method means you have expected output to check.

Tip

If you get SD drive initialization errors, check the pin assignment used in the definition section to make sure you are using the correct pin for your SD drive/shield.

If you encounter file write or open errors, make sure the SD card is formatted as a FAT partition, the SD card is not write protected, and you can create and read files on the drive using your personal computer.

Testing the Sketch

To test the sketch, be sure the code compiles and you have your hardware set up correctly. Once you have a sketch that compiles, upload it to your Arduino and launch a serial monitor. The following code shows the expected output in the serial monitor:
Initializing SD card...initialization done.
Sample: 2/29/2020 16:34:27 1
Sample: 2/29/2020 16:34:27 2
Sample: 2/29/2020 16:34:28 3
Note

The first time you run the sketch, you may see a message about initializing the SD card and creating the file. This is normal. Subsequent runs (restarts of the Arduino) may not show the messages.

If you run the sketch a number of times as it is written, it will insert three rows at the end of the file each time the sketch is initialized. This is because you placed sample calls to record_sample() in the setup() method for debugging purposes. These calls would naturally be placed in the loop() method after you read your sensors. The following code shows an example of the file contents after running the sketch (starting the Arduino) four times:
2/29/2020 16:31:8 1
2/29/2020 16:31:8 2
2/29/2020 16:31:8 3
2/29/2020 16:34:27 1
2/29/2020 16:34:27 2
2/29/2020 16:34:28 3

If you examine the file and find more sets of entries than you expect, try deleting the data from the file, starting your Arduino, and then pressing the Reset button twice. When you look at the contents, you should see exactly three sets of entries (one for the initial start because the sketch was in memory to begin with and one for each time you restarted the Arduino).

If you see only partial sets (fewer than three rows for each set), check to ensure that you are allowing the Arduino to start before powering it off. It is best to use the serial monitor and wait until all three statements are echoed to the monitor before shutting down the Arduino.

Should the case arise that your sketch compiles and no errors are shown in the serial monitor but the data file is empty, check to make sure the card is usable and not corrupt. Try reformatting the card with the FAT file format.

Handle With Care

MicroSD cards are very fragile. They can be damaged easily if handled improperly or subjected to ESD or magnetic fields. If your card does not work properly and you cannot reformat it, it is possible that it is damaged beyond use. You can try using a formatting program from sdcard.org, but if it fails, your card is no longer viable. So far, this has happened to me only once.

Now that you have examined two primary methods for storing data locally on an Arduino, let’s look at the options available for the Raspberry Pi.

Local Storage Options for the Raspberry Pi

Because the Raspberry Pi is a personal computer, it has the capability to create, read, and write files. Although it may be possible to use an EEPROM connected via the GPIO header, why would you do that? Given the ease of programming and the convenience of using files, there is very little need for another form of storage.

You also know the Raspberry Pi can be programmed in a number of ways and with one of the most popular languages, Python.3 Working with files in Python is very easy and is native to the default libraries. This means there is nothing that you need to add to use files.

The following project demonstrates the ease of working with files in Python. The online Python documentation explains reading and writing files in detail (https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files).

One thing you will notice is that it doesn’t matter where the file is located—on the SD card or an attached USB drive. You only need to know the path to the location (folder) where you want to store data and pass that to the open() method.

Savvy Python programmers4 know that the Python library contains additional libraries and classes for manipulating folders, navigating paths, and much more. For more information, examine the Python documentation for the OS and Sys libraries. For example, look for normpath() and the Path5 class.

Project: Writing Data to Files

This project demonstrates how easy it is to use files on the Raspberry Pi with Python. Because no additional hardware or software libraries are needed, I can skip those sections and jump directly into the code.

Start your Raspberry Pi, and log in. Open a new file with the following command (or similar):
nano file_io_example.py
You name the file with a .py extension to indicate that it is a Python script. Enter the following code in the file:
import datetime
with open("/home/pi/sample_data.txt", "a+") as my_file:
    my_file.write("{0} {1} ".format(datetime.datetime.now(), 101))

In this example, you first import the datetime library . You use the datetime to capture the current date and time. Next, you open the file (notice that you are using the Pi users’ home directory) using the newer with clause and write a row to the file (you do not need to close the file—that is done for you when execute leaves the scope of the with clause). If you feel better using the explicit open and close, feel free to do so.

Notice the open() method. It takes two parameters—the file path and name and a mode to open the file. You use "a+" to append to the file (a) and create the file if it does not exist (+). Other values include r for reading and w for writing. Some of these can be combined: for example, "rw+" creates the file if it does not exist and allows for both reading and writing data.

Note

Using write mode truncates the file. For most cases in which you want to store sensor samples, you use append mode.

For each execution, you should see one row with a slightly different time value corresponding to when the script was run. To execute the file, use the following command:
python ./file_io_example.py
Go ahead and try to run the script. If you get errors, check the code and correct any syntax errors. If you encounter problems opening the file (you see I/O errors when you run the script), try checking the permissions for the folder you are using. Try running the script a number of times, and then display the contents of the file. The following code shows the complete sequence of commands for this project:
$ nano file_io_example.py
$ python ./file_io_example.py
$ python ./file_io_example.py
$ python ./file_io_example.py
$ python ./file_io_example.py
$ python ./file_io_example.py
$ python ./file_io_example.py
$ python ./file_io_example.py
$ more sample_data.txt
2020-02-29 16:50:34.076657 101
2020-02-29 16:53:23.252384 101
2020-02-29 16:53:24.078429 101
2020-02-29 16:53:24.680599 101
2020-02-29 16:53:25.676225 101
2020-02-29 16:53:26.324482 101

Did you get similar results? If not, correct any errors and try again until you do. As you can see from this simple example, it is very easy to write data to files using Python on the Raspberry Pi.

Remote Storage Options

Remote storage means the data is sent to another node or system for recording. This normally requires some form of communication or network connectivity to the remote system. Sensor networks by nature are connected and thus can take advantage of remote storage.

To give you an idea of what I am discussing, consider an Arduino sensor node with an XBee module connected to a Raspberry Pi–based node. Suppose also that you want to write your sample data to files. Rather than using an SD card on the Arduino node to store data, you could send that data to the Raspberry Pi–based node and store the data in a file there. The main motivation is that it is much easier to use files via Python on the Raspberry Pi. If you also factor in the possibility of having multiple Arduino sensor nodes with XBee modules, you can use the Raspberry Pi–based node as a data aggregate, storing all the data in a single file.

Single File or Multiple Files?

I sometimes get this question when discussing storing aggregate data. If your data is similar (e.g., temperature), you can consider storing data from like sensors to the same file. However, if the data differs (such as temperature from one node and humidity from another), you should consider using different files. This makes reading the files easier because you don’t have to write code (or use tools) to separate the data.

But are you really talking about only storing data in files? The answer is no. There are a number of mechanisms for storing data remotely. Although storing data in files is the easiest form, you can also store data in the cloud or even on a remote database server.

If you are experienced with using databases for storing and retrieving data, this method will appeal to you—especially if you plan to use other tools to process the data later. For example, you may want to perform statistical analyses or create charts that track the samples over time. Because working with databases is a complex topic, I examine this form of remote storage in the next couple of chapters.

You have already seen how easy it is to use files, but what about storing data in the cloud? What is that about? Simply stated, storing data in the cloud involves using a cloud-based data storage service to receive your data and host it in some way. The most popular form presents the data for others on the Internet to view or consume for their own use.

The following section discusses storing sample data in the cloud using a popular, easy to use, cloud-based IoT data-hosting service from MathWorks called ThingSpeak (www.thingspeak.com). You will see example projects for using ThingSpeak on both the Arduino and the Raspberry Pi.

Storing Data in the Cloud

Unless you live in a very isolated location, you have likely been bombarded with talk about the cloud and IoT. Perhaps you’ve seen advertisements in magazines and on television, or read about it in other books, or attended a seminar or conference. Unless you’ve spent time learning what cloud means, you are probably wondering what all the fuss is about.

Simply stated,6 the cloud is a name tagged to services available via the Internet. These can be servers you can access (running as a virtual machine on a larger server), systems that provide access to a specific software or environment, or resources such as disks or IP addresses that you can attach to other resources. The technologies behind the cloud include grid computing (distributed processing), virtualization, and networking. The correct scientific term is cloud computing. Although a deep dive into cloud computing is beyond the scope of this book, it is enough to understand that you can use cloud computing services to store your sensor data.

There are a number of IoT cloud vendors that offer all manner of products, capacities, and features to match just about anything you can conjure for an IoT project. With so many vendors offering IoT solutions, it can be difficult to choose one. The following is a short list of the more popular IoT offerings from the top vendors in the cloud industry:

Most of the vendors offer commercial products, but a few like Google, Azure, Arduino, and ThingSpeak offer limited free accounts. As you may surmise, some of the offerings are complex solutions with steep learning curve, but the Arduino and ThingSpeak offerings are simple and easy to use. Since we want a solution that supports Arduino and Raspberry Pi (and other platforms), we will use ThingSpeak in this chapter as an example of what is possible when storing data in the cloud.

Tip

If you want or need to use one of the other vendors, be sure to read all of the tutorials thoroughly before jumping into your code.

ThingSpeak offers a free account for non-commercial projects that generate fewer than 3 million messages (or data elements) per year or around 8000 messages per day. Free accounts are also limited to seven channels (a channel is equivalent to a project and can save up to eight data items). If you need to store or process more data than that, you can purchase a commercial license in one of four categories, each with specific products, features, and limitations: Standard, Academic, Student, and Home. See https://thingspeak.com/prices and click each of the license options to learn more about the features and pricing.

Note

Unless you use a work or school account, you may need to pay to use some of the products such as MatLab.

ThingSpeak works by receiving messages from devices that contain the data you want to save or plot. There are libraries available that you can use for certain platforms or programming languages such as Arduino or Python. That is by far the easiest way to connect to ThingSpeak and transmit data.

However, you can also use a machine-to-machine (M2M) connectivity protocol (called MQTT7) or representational state transfer (REST8) API designed as a request-response model that communicates over HTTP to send data to or read data from ThingSpeak. Yes, you can even read your data from other devices.

Tip

See www.mathworks.com/help/thingspeak/channels-and-charts-api.html for more details about the ThingSpeak MQTT and REST API.

When you want to read or write a ThingSpeak channel, you can either publish MQTT messages, send requests via HTTP to the REST API, or use one of the platform-specific libraries that encapsulate these mechanisms for you. A channel can have up to eight data fields represented as a string or numeric data. You can also process the numeric data using several sophisticated procedures such as summing, average, rounding, and more.

We won’t get too far into the details of these protocols; rather, we will see how to use ThingSpeak as a quick-start guide. MathWorks provides a complete set of tutorials, documentation, and examples. So, if you need more information about how ThingSpeak works, check out the documentation at www.mathworks.com/help/thingspeak/.

The Cloud: Isn’t That Just Marketing Hype?

Don’t believe all the hype or sales talk about any product that includes “cloud” in its name. Cloud computing services and resources should be accessible via the Internet from anywhere, available to you via subscription (fee or for free), and permit you to consume or produce and share the data involved. Also, consider the fact that you must have access to the cloud to get to your data. Thus, you have no alternative if the service is unreachable (or down).

Getting Started with ThingSpeak

To use ThingSpeak, you must first sign up for an account. Fortunately, they provide the option for a free account. In fact, you get a free account to start with and add (purchase) a license later. To create a free account, visit https://thingspeak.com/users/sign_up and fill in your email address, location (general geographic), and first and last name and then click Continue. You will then be sent a validation email. Open that and follow instructions to verify your email and complete your free account by choosing a password and completing a short questionnaire.

Creating a Channel

Once you log in to ThingSpeak, you can create a channel to hold your data. Recall, each channel can have up to eight data items (fields). From your login home page, click New Channel as shown in Figure 7-8.
../images/313992_2_En_7_Chapter/313992_2_En_7_Fig8_HTML.jpg
Figure 7-8

Creating a channel in ThingSpeak

You will be presented with a really long form that has a lot of fields that you can fill out. Figure 7-9 shows an example of the form.
../images/313992_2_En_7_Chapter/313992_2_En_7_Fig9_HTML.jpg
Figure 7-9

New Channel form

At a minimum, you need only name the channel, enter a description (not strictly required but recommended), and then select (tick) one or more fields naming each. That’s it. Click Save Channel to complete the process.

So, what are all those channel settings? The following gives a brief overview of each. As you work with ThingSpeak, you may want to start using some of these fields:
  • Percentage complete: A calculated field based on the completion of the name, description, location, URL, video, and tags in your channel.

  • Channel Name: Unique name for the channel.

  • Description: Description of the channel.

  • Field#: Tick each box to enable the field.

  • Metadata: Additional data for the channel in JSON, XML, or CSV format.

  • Tags: A comma-separated list of keywords for searching.

  • Link to External Site: If you have a website about your project, you can provide the URL here to publish on the channel.

  • Show Channel Location: Tick this box to include the following fields:
    • Latitude: Latitude of the sensor(s) for the project or source of the data

    • Longitude: Longitude of the sensor(s) for the project or source of the data

    • Elevation: Elevation in meters for use with projects affected by elevation

  • Video URL: If you have a video associated with your project, you can provide the URL here to be published on the channel.

  • Link to GitHub: If your project is hosted in GitHub, you can provide the URL to be published on the channel.

Wow, that’s a lot of stuff for free! As you will see, this isn’t a simple toy or severely limited product. You can accomplish quite a lot with these settings. Notice there are places to put links to video, website, and GitHub. This is because channels can be either private (only your login or API KEY as we will see can access) or public. Making a channel public allows you to share the data with anyone and thus those URL fields may be handy to document your project. Cool.

Once you create your channel, it is time to write some data. There are two pieces of information you will need for most projects: the API Key for the channel and for some libraries the channel number (the integer value shown on the channel page). There are libraries available for many platforms, and on some platforms, there may be several ways (libraries or techniques) to write data to a ThingSpeak channel.

You can find the API Key on the channel page by clicking the API Keys tab. When you create a new channel, you will have one write and one read API Key. You can add more keys if you need them so that you can use one key per device, location, customer, and so on. Figure 7-10 shows the API Keys tab for the channel created previously in Figure 7-9.
../images/313992_2_En_7_Chapter/313992_2_En_7_Fig10_HTML.jpg
Figure 7-10

API Keys for a ThingSpeak channel

Notice I masked out the keys. If you make your channel public, do not share the write key with anyone you don’t want to allow to write to your channel. You can create new keys by clicking the Generate New Write API Key or Add New Read API Key buttons. You can delete read keys by clicking the Delete API Key button.

We use the key in our code to allow the device to connect to and write data to the channel. So, we typically copy this string from the channel page and paste it into our code as a string. Recall, we may use a library that encapsulates the HTTP or MQTT mechanism or, in the case of the Raspberry Pi Python library, we use a Python library and the HTTP protocol. We will see both in the upcoming sample projects for Arduino and Raspberry Pi.

Now that you understand the basics of writing data to ThingSpeak, let’s take a look at how to do it in more detail for the Arduino. This is followed by an example for the Raspberry Pi.

Project: Writing Data to ThingSpeak with an Arduino

This project demonstrates how to write sensor data to a ThingSpeak channel. Unlike the previous projects in this chapter, you use a sensor and generate some sample data. In this case, you monitor temperature on an Arduino MKR1000 and save the Celsius and Fahrenheit values to your ThingSpeak. If you have not yet created a ThingSpeak channel for the Arduino, do that now and record the channel ID and API key generated. Use the following data for the channel and name it MKR1000_TMP36 as shown in Figure 7-11.
../images/313992_2_En_7_Chapter/313992_2_En_7_Fig11_HTML.jpg
Figure 7-11

Set up a channel for the MKR1000 and TMP36 sensor

Click the Save Channel button to create the channel. Then, on the API Key tab, copy the write key and paste it a new file for later use.

Now that we have a channel created, let’s set up the hardware.

Hardware Setup

The hardware for this project is an Arduino MKR1000, a breadboard, breadboard wires, a TMP36 temperature sensor, and a 0.10uF capacitor. Wire the sensor and the capacitor as shown in Figure 7-12. Attach pin 1 of the sensor to the 5V pin on the Arduino, pin 2 of the sensor to the A1 pin on the MKR1000, and pin 3 to ground on the MKR1000. The capacitor is also attached to pins 1 and 3 of the sensor (orientation does not matter).

Tip

You can use the newer MKR models, provided they are of the Wi-Fi variety.

../images/313992_2_En_7_Chapter/313992_2_En_7_Fig12_HTML.jpg
Figure 7-12

Wiring setup for the ThingSpeak temperature feed for the Arduino

To use connect to the Internet, you will also need a Wi-Fi access point or router to connect. You will need the SSID and password for the connection.

Now, let’s see how we set up the software and the sketch for the project.

Configuring the Arduino IDE

We will need to set up several things in our Arduino IDE in order to create the sketch. We will need to ensure the MKR boards are supported and the ThingSpeak and the Wi-Fi 101 libraries are installed. Let’s begin with the MKR board support.

In the Arduino IDE, open a new sketch and click ToolsBoard XXXXBoards Manager… (the XXXX represents the board you used last). When the form loads, type Arduino&MKR1000 into the search box and install the Arduino SAMD support as shown in Figure 7-13.
../images/313992_2_En_7_Chapter/313992_2_En_7_Fig13_HTML.jpg
Figure 7-13

Installing the SAMD Boards support

Click the Install button to install the board support module. On some PC platforms, the Arduino IDE may prompt you to install the board support when you start the IDE for the first time with the board connected to your PC.

Now, let’s install the two libraries we need. You can do them in either order. In the Arduino IDE, choose SketchInclude Library…Manage Libraries. Enter ThingSpeak in the search box and then click the Install button as shown in Figure 7-14. Once again, click Install to install the library.
../images/313992_2_En_7_Chapter/313992_2_En_7_Fig14_HTML.jpg
Figure 7-14

Installing the ThingSpeak library

We also need the Wi-Fi 101 library to permit use to communicate with the Internet. In the Arduino IDE, choose SketchInclude Library…Manage Libraries. Enter Wi-Fi 101 in the search box and then click the Install button as shown in Figure 7-15. Once again, click Install to install the library.
../images/313992_2_En_7_Chapter/313992_2_En_7_Fig15_HTML.jpg
Figure 7-15

Installing the WiFi 101 library

Write the Sketch

Now that you have the necessary libraries and board module installed, open a new Arduino project and name it arduino_thingspeak.ino. Recall, we will use a TMP36 and read the values from pin A1 on the MKR1000. We have seen code to read the TMP36 in Chapter 6, so we will skip the explanation and dive into how to interact with ThingSpeak.

In this example, we will see how to store our API Key and other critical data in a separate header (.h) file, which will be part of the sketch and saved in the same folder. To add a header file, click the small down arrow button to the right of the sketch and select New Tab as shown in Figure 7-16. In the prompt, enter secrets.h and press Enter. This will open a new tab. Click that tab to open the file.
../images/313992_2_En_7_Chapter/313992_2_En_7_Fig16_HTML.jpg
Figure 7-16

Add New Tab

../images/313992_2_En_7_Chapter/313992_2_En_7_Fig17_HTML.jpg
Figure 7-17

Example channel data (MKR1000_TMP36)

../images/313992_2_En_7_Chapter/313992_2_En_7_Fig18_HTML.jpg
Figure 7-18

Set up a channel for the Raspberry Pi and TMP36 sensor

../images/313992_2_En_7_Chapter/313992_2_En_7_Fig19_HTML.jpg
Figure 7-19

12-bit ADC module (courtesy of Adafruit)

../images/313992_2_En_7_Chapter/313992_2_En_7_Fig20_HTML.jpg
Figure 7-20

Wiring the TMP36 and ADC to the Raspberry Pi

../images/313992_2_En_7_Chapter/313992_2_En_7_Fig21_HTML.jpg
Figure 7-21

Verifying the ADC module

../images/313992_2_En_7_Chapter/313992_2_En_7_Fig22_HTML.jpg
Figure 7-22

Sample ThingSpeak feed for the Raspberry Pi

We will place the Wi-Fi and our ThingSpeak channel data in this file. Use the #define directive to create new strings that we will use in the main sketch. The following code shows the lines and data you need for the file. Type these in and save the file.
#define SECRET_SSID "YOUR_SSID"                 // SSID
#define SECRET_PASS "SSID_PASS"                 // WiFi Password
#define SECRET_CH_ID 0000000000                 // Channel number
#define SECRET_WRITE_APIKEY "ABCDEFGHIJKLMNOP"  // Write API Key
Now, return to the main sketch tab. Begin the sketch with the following includes. You need the ThingSpeak.h, WiFi101.h, and the secrets.h file we just created.
#include "ThingSpeak.h"
#include <WiFi101.h>
#include "secrets.h"
Next, we need to declare several variables as well as instantiate the Wi-Fi client as shown in the following code. We also add a variable to store the pin number for the sensor. Notice we use those #defines we stored in the secrets.h file.
char ssid[] = SECRET_SSID;   // your network SSID (name)
char pass[] = SECRET_PASS;   // your network password
WiFiClient  client;
unsigned long myChannelNumber = SECRET_CH_ID;
const char * myWriteAPIKey = SECRET_WRITE_APIKEY;
int SENSOR_PIN = 1;
To use the Ethernet shield, you must also declare a MAC address. The IP address for the Ethernet shield is requested via DHCP. You define the MAC address as an array. This can be a random set of values, provided they are in the range 0x00–0xff. You can use what is shown here:
byte mac_addr[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
Next, you define your ThingSpeak API key and feed ID. You also define the pin number for your TMP36 sensor. This example uses pin 0. You can choose whatever pin you want to use—just change this define, and the rest of the code will point to the correct pin:
char ThingSpeakKey[] = "<YOUR_KEY_HERE>";
#define FEED_NUMBER <YOUR_FEED_HERE>
#define SENSOR_PIN 0
Now we are ready to write the setup() method. You must initialize the serial class (so you can use the serial monitor) and initialize the Wi-Fi and ThingSpeak client. You use the begin() method for each of these operations. For the Wi-Fi class, you pass in the SSID and password defined earlier. The complete setup() method is as follows:
void setup() {
  Serial.begin(115200);      // Initialize serial
  while (!Serial);
  // Connect to WiFi
  Serial.println("Welcome to the MKR1000 + TMP36 ThingSpeak Example!");
  while(WiFi.status() != WL_CONNECTED){
    WiFi.begin(ssid, pass);
    delay(5000);
  }
  Serial.println(" Connected.");
  ThingSpeak.begin(client);  // Initialize ThingSpeak
}
Finally, the loop() method contains the code to read the sensor, calculate the temperature in Celsius and Fahrenheit, and send those data to our ThingSpeak channel. To do so, we first call the setField() method for the ThingSpeak library to set each field we want to update (field numbers start at 1). We then use the writeFields() method to send the data to ThingSpeak. We can check the result of that call to ensure the code returned is 200, which means success (OK). A simplified version of the loop() method is shown here:
void loop() {
  // Read TMP36 here
  // Set the fields with the values
  ThingSpeak.setField(1, temperatureC);
  ThingSpeak.setField(2, temperatureF);
  // Write to the ThingSpeak channel
  int res = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
  if (res == 200) {
    Serial.println("Channel update successful.");
  } else {
    Serial.print("Problem updating channel. HTTP error code ");
    Serial.println(res);
  }
  Serial.println("sleeping...");
  delay(20000); // Wait 20 seconds to update the channel again
}

Notice we display the actual result if it does not return a code of 200. Notice also we add a sleep (delay()) at the end to sleep for 20 seconds. We do this because the ThingSpeak free account is limited to updates once every 15 seconds.

Now that you understand the flow and contents of the sketch, you can complete the missing pieces and start testing. Listing 7-4 shows the complete sketch for this project.
/**
  Beginning Sensor Networks Second Edition
  Sensor Networks Arduino ThingSpeak Write Example
  This project demonstrates how to write data to a ThingSpeak channel.
*/
#include "ThingSpeak.h"
#include <WiFi101.h>
#include "secrets.h"
char ssid[] = SECRET_SSID;   // your network SSID (name)
char pass[] = SECRET_PASS;   // your network password
WiFiClient  client;
unsigned long myChannelNumber = SECRET_CH_ID;
const char * myWriteAPIKey = SECRET_WRITE_APIKEY;
int SENSOR_PIN = 1;
void setup() {
  Serial.begin(115200);      // Initialize serial
  while (!Serial);
  // Connect to WiFi
  Serial.println("Welcome to the MKR1000 + TMP36 ThingSpeak Example!");
  Serial.print("Attempting to connect to SSID: ");
  Serial.print(SECRET_SSID);
  Serial.print(" ");
  while(WiFi.status() != WL_CONNECTED){
    WiFi.begin(ssid, pass);
    Serial.print(".");
    delay(5000);
  }
  Serial.println(" Connected.");
  ThingSpeak.begin(client);  // Initialize ThingSpeak
}
void loop() {
  // Read TMP36
  float adc_data = analogRead(A1);
  float voltage = adc_data *  (3.3 / 1024.0);
  Serial.print("Temperature is ");
  float temperatureC = (voltage - 0.525) / 0.01;
  Serial.print(temperatureC);
  Serial.print("C, ");
  float temperatureF = ((temperatureC * 9.0)/5.0) + 32.0;
  Serial.print(temperatureF);
  Serial.println("F");
  // Set the fields with the values
  ThingSpeak.setField(1, temperatureC);
  ThingSpeak.setField(2, temperatureF);
  // Write to the ThingSpeak channel
  int res = ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
  if (res == 200) {
    Serial.println("Channel update successful.");
  } else {
    Serial.print("Problem updating channel. HTTP error code ");
    Serial.println(res);
  }
  Serial.println("sleeping...");
  delay(20000); // Wait 20 seconds to update the channel again
}
Listing 7-4

Arduino-Based ThingSpeak Channel Write

Note

Be sure to substitute your API key and channel number in the secrets.h file. Failure to do so will result in compilation errors.

Take some time to make sure you have all the code entered correctly and that the sketch compiles without errors. Once you reach this stage, you can upload the sketch and try it out.

Testing the Sketch

To test the sketch, be sure the code compiles and you have your hardware set up correctly. Once you have a sketch that compiles, upload it to your Arduino MKR1000 and launch a serial monitor. The following code shows an example of the output you should see:
Attempting to connect to SSID: ATT-WiFi-0059 . Connected.
Temperature is 15.50C, 59.90F
Channel update successful.
sleeping...
Temperature is 16.14C, 61.06F
Channel update successful.
sleeping...
Temperature is 15.82C, 60.48F
Channel update successful.
sleeping...
Temperature is 16.14C, 61.06F
Channel update successful.
sleeping...
Temperature is 16.46C, 61.64F
Channel update successful.
sleeping...

Did you see similar output? If you did not, check the return code as displayed in the serial monitor. You should be seeing a return code of 200 (meaning success). If the return code was a single digit (1, 2, 3, and so on), you are likely encountering issues connecting to ThingSpeak. If this occurs, connect your laptop to the same network cable, and try to access ThingSpeak.

If the connection is very slow, you could encounter a situation in which you get an error code other than 200 every other or every N attempts. If this is the case, you can increase the timeout in the loop() method to delay processing further. This may help for some very slow connections, but it is not a cure for a bad or intermittent connection.

Let the sketch run for about 3 minutes before you visit ThingSpeak. Once the sketch has run for some time, navigate to ThingSpeak, log in, and click your channel page. You should see results similar to those shown in Figure 7-17.

Notice the peaks near the beginning and end of the graph. I simulated a spike in the data by pressing a warm object device on the TMP36 (my finger). If you try this, be careful not to touch any of the wires!

For More Fun

You can have a lot of fun with this script. Try connecting other sensors and creating other channels in ThingSpeak. You can also experiment with reading the data you saved in ThingSpeak.

Now that you know how to save data to ThingSpeak on the Arduino, let’s explore how to do the same on the Raspberry Pi.

Project: Writing Data to ThingSpeak with a Raspberry Pi

This project demonstrates the ease of using the ThingSpeak REST API via HTTP on the Raspberry Pi to write sensor data to a ThingSpeak channel. Recall, to read the analog temperature sensor (TMP36), you use an I2C module that provides 12-bit precision for reading values.

Tip

This example demonstrates how to use the HTTP interface to write data to ThingSpeak. However, this is also a ThingSpeak Python library you can use if you want. You can install it with the pip3 install thingspeak command. Documentation for the ThingSpeak Python library can be found at https://thingspeak.readthedocs.io/en/latest/.

If you have not yet created a ThingSpeak channel for the Raspberry Pi, do that now and record the channel ID and API key generated. Use the following data for the channel and name it RASPI_TMP36 as shown in Figure 7-18.

Click the Save Channel button to create the channel. Then, on the API Key tab, copy the write key and paste it a new file for later use.

Now that we have a channel created, let’s set up the hardware.

Hardware Setup

The hardware for this project consists of a Raspberry Pi, a Raspberry Pi Cobbler+ (optional), a breadboard, the TMP36 sensor, jumper wires, and an ADC module.

I mentioned the Raspberry Pi does not include any ADCs, so you cannot use an analog sensor. In this project, you explore how to use a multichannel ADC with the Raspberry Pi to enable the use of the TMP36 analog temperature sensor. Figure 7-19 shows the 12-bit ADC from Adafruit (www.adafruit.com/products/1083). This module supports up to four sensors (channels). In the figure, you can see pins A0–A3; these are the pins used for each of the channels supported.

Tip

You are exploring the use of the ADC module with a Raspberry Pi, because it supports the I2C protocol, but you can use the module with the Arduino too. See http://learn.adafruit.com/adafruit-4-channel-adc-breakouts for more details.

You also require connectivity to the Internet via a network connection on the Raspberry Pi. The Internet connection can be via a wired Ethernet connection or via a wireless connection. There are no specific requirements for connectivity as there are with an Arduino.

Figure 7-20 shows the connections you need to make. Most of these should be familiar to you if you have completed the projects in previous chapters. For the TMP36, connect pin 1 to the same 5V connection as the ADC module and pin 3 to the GND connection on the ADC module. Pin 2 on the sensor connects to the A0 pin on the ADC module.

Connect the TMP36 sensor as follows (again, see Figure 7-20).

Caution

Be sure to double-check your connections and compare them to Figure 7-20. Failure to connect things properly on the Raspberry Pi can lead to a damaged board.

Once you have made these connections, power on your Raspberry Pi and issue the following command:
$ sudo i2cdetect –y 1

You should see the ADC module appear as address 0x48 in the output, as shown in Figure 7-21.

Write the Code

Now that you have the libraries you need, it is time to write a script to read samples from a TMP36 sensor (via the ADC module) and save the data to your ThingSpeak channel. Since we have already written code to read the TMP36 sensor, we will concentrate on the code for writing data to ThingSpeak.

Begin by opening a new file on your Raspberry Pi named raspi_tmp36.py. You can use the Thonny IDE or a text editor or nano in a terminal to create the file.

Let’s begin with the imports. We need to import the http.client, time, urllib, board, busio, and the Adafruit libraries for the ADC module as shown here:
import http.client
import time
import urllib
# import the Raspberry Pi libraries
import board
import busio
# Import the ADC Adafruit libraries
import adafruit_ads1x15.ads1115 as ADS
from adafruit_ads1x15.analog_in import AnalogIn
Next, we need to declare a variable for our API Key and instantiate the I2C interface as shown here:
# API KEY
THINGSPEAK_APIKEY = 'YOUR_API_KEY'
# Instantiate (start/configure the I2C protocol)
i2c = busio.I2C(board.SCL, board.SDA)
# Instantiate the ADS1115 ADC board
ads = ADS.ADS1115(i2c)
# Setup the channel from Pin 0 on the ADS1115
channel0 = AnalogIn(ads, ADS.P0)

Next is the core code for the script. We will use a try…except block to capture the keyboard interrupt (Ctrl+C). Inside that, we prepare a special URL to encode the field data for our channel and then open the URL.

More specifically, we encode the data in the form of a dictionary containing the field data using the urllib.parse class urlencode() method . This ensures the strings created are valid for use in a URL. Next, we create a header dictionary and pass that to the http.client class HttpConnection() method to open a connection to ThingSpeak. Finally, we send the data to ThingSpeak in the form of a POST command to the update REST API endpoint. Wow! The following code shows the steps. Take a moment to read through them. They should be easy to comprehend. Remember, you can.
params = urllib.parse.urlencode(
    {
        'field1': temp_c,
        'field2': temp_f,
        'key': THINGSPEAK_APIKEY,
    }
)
# Create the header
headers = {
    "Content-type": "application/x-www-form-urlencoded",
    'Accept': "text/plain"
}
# Create a connection over HTTP
conn = http.client.HTTPConnection("api.thingspeak.com:80")
# Execute the post (or update) request to upload the data
conn.request("POST", "/update", params, headers)
Listing 7-5 shows the complete code for the script for this project. You will notice we skipped the print() statements and error handling code, but it is all things we have seen in previous projects. Be sure to read through the code before you run it so you can see how it all works. Also, rather than type all of this code in, you can download it from the book website.
#
# Beginning Sensor Networks Second Edition
#
# IoT Example - Publish temperature data from a Raspberry Pi
# with TMP36 and ADC.
#
# Dr. Charles A. Bell
# March 2020
#
from __future__ import print_function
# Python imports
import http.client
import time
import urllib
# import the Raspberry Pi libraries
import board
import busio
# Import the ADC Adafruit libraries
import adafruit_ads1x15.ads1115 as ADS
from adafruit_ads1x15.analog_in import AnalogIn
# API KEY
THINGSPEAK_APIKEY = 'YOUR_API_KEY'
# Instantiate (start/configure the I2C protocol)
i2c = busio.I2C(board.SCL, board.SDA)
# Instantiate the ADS1115 ADC board
ads = ADS.ADS1115(i2c)
# Setup the channel from Pin 0 on the ADS1115
channel0 = AnalogIn(ads, ADS.P0)
# Run the program to upload temperature data to ThingSpeak
print("Welcome to the ThingSpeak Raspberry Pi temperature sensor! Press CTRL+C to stop.")
try:
    while 1:
        # Get temperature in Celsius
        temp_c = ((channel0.voltage * 3.30) - 0.5) * 10
        # Calculate temperature in Fahrenheit
        temp_f = (temp_c * 9.0 / 5.0) + 32.0
        # Display the results for diagnostics
        print("Uploading {0:.2f} C, {1:.2f} F"
              "".format(temp_c, temp_f), end=' ... ')
        # Setup the data to send in a JSON (dictionary)
        params = urllib.parse.urlencode(
            {
                'field1': temp_c,
                'field2': temp_f,
                'key': THINGSPEAK_APIKEY,
            }
        )
        # Create the header
        headers = {
            "Content-type": "application/x-www-form-urlencoded",
            'Accept': "text/plain"
        }
        # Create a connection over HTTP
        conn = http.client.HTTPConnection("api.thingspeak.com:80")
        try:
            # Execute the post (or update) request to upload the data
            conn.request("POST", "/update", params, headers)
            # Check response from server (200 is success)
            response = conn.getresponse()
            # Display response (should be 200)
            print("Response: {0} {1}".format(response.status,
                                             response.reason))
            # Read the data for diagnostics
            data = response.read()
            conn.close()
        except Exception as err:
            print("WARNING: ThingSpeak connection failed: {0}, "
                  "data: {1}".format(err, data))
        # Sleep for 20 seconds
        time.sleep(20)
except KeyboardInterrupt:
    print("Thanks, bye!")
exit(0)
Listing 7-5

Complete Code for the raspi_thingspeak.py Script

Note

Be sure to substitute your API key in the location marked. Failure to do so will result in runtime errors.

Now that you have all the code entered, let’s test the script and see if it works.

Testing the Script

Python scripts are interpreted programs. Although there is a fair amount of syntax checking at the start of a script, logic errors are not discovered until the statement is executed. Thus, you may encounter errors or exceptions if the script was not entered correctly (e.g., if you misspelled a method or variable name). This may also happen if you failed to replace the placeholder for the API key and feed number.

To run the script, enter the following command. Let the script run for several iterations before using Ctrl+C to break the main loop.
$ python3 ./raspi_thingspeak.py
The following code shows an example of the output you should see:
Welcome to the ThingSpeak Raspberry Pi temperature sensor! Press CTRL+C to stop.
Uploading 18.46 C, 65.23 F ... Response: 200 OK
Uploading 18.49 C, 65.28 F ... Response: 200 OK
Uploading 19.20 C, 66.56 F ... Response: 200 OK
Uploading 18.41 C, 65.13 F ... Response: 200 OK
Uploading 18.24 C, 64.83 F ... Response: 200 OK
Uploading 18.25 C, 64.85 F ... Response: 200 OK
Uploading 18.31 C, 64.96 F ... Response: 200 OK
Uploading 18.32 C, 64.97 F ... Response: 200 OK
Uploading 18.29 C, 64.93 F ... Response: 200 OK
Uploading 18.35 C, 65.03 F ... Response: 200 OK
Uploading 18.24 C, 64.83 F ... Response: 200 OK
Uploading 18.39 C, 65.09 F ... Response: 200 OK
Uploading 18.25 C, 64.84 F ... Response: 200 OK
Thanks, bye!

Let the script run for 3 minutes or so, and then navigate to your Raspberry Pi channel on ThingSpeak. You should see your sensor data displayed, similar to that shown in Figure 7-22.

If you do not see similar data, go back and check the return codes as discussed in the last project. You should see return codes of 200 (success). Check and correct any errors in network connectivity or syntax or logic errors in your script until it runs successfully for several iterations (all samples stored return code 200).

If you see similar data, congratulations! You now know how to generate data and save it to the cloud using two different platforms.

For More Fun

You can have a lot of fun with this script. Try connecting other sensors and creating other channels for them in ThingSpeak. You can also experiment with reading the data you saved in ThingSpeak.

Storing Sensor Data in a Database

As you may have surmised, it is possible to store sensor data to a database on a Raspberry Pi. You can use MySQL as your database server and the Connector/Python library to write Python scripts that read sensor data and store the data in tables for later processing. Because there is a lot more involved than a few dozen lines of code (like setting up MySQL on the Raspberry Pi), you explore this topic in greater detail in Chapters 8 and 9.

Component Shopping List

A number of components are needed to complete the projects in this chapter, as listed in Table 7-2. Some of them, like the XBee modules and supporting hardware, are also included in the shopping list from other chapters. These are shown in Table 7-3.
Table 7-2

Components Needed

Item

Vendors

Est. Cost USD

Qty Needed

I2C EEPROM

www.sparkfun.com/products/525

$1.95

1

Arduino Ethernet Shield

www.sparkfun.com/products/9026

$24.95

1*

microSD Shield

www.sparkfun.com/products/9802

$14.95

*

Data Logging shield for Arduino

www.adafruit.com/products/1141

$19.95

*

DS1307 Real-Time Clock breakout board

www.adafruit.com/product/3296

$7.50

1**

Real-Time Clock module

www.sparkfun.com/products/99

$14.95

**

12-bit ADC module

www.adafruit.com/products/1083

$9.95

1

*You need only one of these options.

**Either of these will work.

Table 7-3

Components Reused from Previous Chapters

Item

Vendors

Est. Cost USD

Qty Needed

Pushbutton

www.sparkfun.com/products/97

$0.35

1

Breadboard (not mini)

www.sparkfun.com/products/9567

$4.95

1

Breadboard jumper wires

www.sparkfun.com/products/8431

$3.95

1

TMP36 sensor

www.sparkfun.com/products/10988

$1.50

1

www.adafruit.com/products/165

0.10uF capacitor

www.sparkfun.com/products/8375

$0.25

1

Raspberry Pi 3B, 3B+, or 4B

Most online and retail stores

$35 and up

1

HDMI or HDMI to DVI cable

Most online and retail stores

Varies

1

HDMI or DVI monitor

Most online and retail stores

Varies

1

USB keyboard

Most online and retail stores

Varies

1

USB power supply

Most online and retail stores

Varies

1

USB Type A to USB micro male

Most online and retail stores

Varies

1

SD card, 2GB or more

Most online and retail stores

Varies

1

Cobbler+

www.adafruit.com/products/914

$7.95

1

10K Ohm Resistor

Most online and retail stores

Varies

1

4.7K Ohm Resistor

Most online and retail stores

Varies

2

Summary

This chapter explored the local storage options for the Arduino and Raspberry Pi. You completed a number of small projects demonstrating each of the possible storage options. I also discussed storing sensor data in the cloud using the ThingSpeak IoT site from MathWorks. There, we learned how to create channels and send data to the channel.

In the next two chapters, I take a break from this exploration of sensor projects and begin discussing another form of remote storage: a database server. Chapter 8 focuses on setting up a MySQL server, and Chapter 9 focuses on using the MySQL server with the Arduino via a special database connector (library) written for the Arduino.

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

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