© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2022
C. BellBeginning MicroPython with the Raspberry Pi Picohttps://doi.org/10.1007/978-1-4842-8135-2_14

14. Using ThingSpeak

Charles Bell1  
(1)
Warsaw, VA, USA
 

Now that we’ve built a good foundation of experience working with basic electronics and Grove modules including how to write code to use the sensors, respond to inputs (e.g., buttons), and display data as well as how to create a simple web server solution, it’s time to take our IoT skills to a new level.

Thus far, we haven’t discussed how to use the data generated from our projects other than saving the data in a file on the Pico or an SD card on the Pimoroni Wireless Pack. Due to the limited size of these options, you will encounter issues you need to resolve such as how much data you want to store and for how long.

While those are things that can be solved, the bigger question is what are you going to do with the data? Would you want to see how the data changes over time, how one sensor data compares to another, how often a value changes, or more basic statistics like min, max, and average values? All of these things require processing power that the Pico doesn’t have to spare.

Furthermore, you may want to see the data presented in one or more graphs that you can use for a pictorial representation. The best way to do this is to take advantage of IoT cloud services. Not only can you store the data easily, but you can also perform analysis on the data and present it in one of several graphics.

In fact, you can store your data in the cloud using a popular, easy-to-use, cloud-based IoT data hosting service from MathWorks called ThingSpeak (www.thingspeak.com). We will see how to take several of the example projects from this book and connect them to ThingSpeak to see how we can gain more insights about the data.

But first, let’s take a brief tour of ThingSpeak and how to get started using it in our projects.

Getting Started

ThingSpeak offers a free account for noncommercial projects that generate fewer than 3 million messages (or data elements) per year or around 8200 messages per day. Free accounts are also limited to four 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.

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 Python or the Arduino platform.

However, you can also use a machine-to-machine (M2M) connectivity protocol (called MQTT1) or representational state transfer (REST2) 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 from/to 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 first thing we need to do is create an account.

Create an Account in 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/, click Get Started For Free, then click Create one! as shown in Figure 14-1.
Figure 14-1

Create a new ThingSpeak/MathLabs account

On the next page, enter your email address, location (general geographic), and first and last names, then click Continue. You may be asked to set the email address as your MatLab account. To do so, tick the Use this email for my MathWorks Account checkbox and click Continue. You will then be sent a validation email. Open that and follow the instructions to verify your email and complete your free account by choosing a password and ticking the accept the Online Services Agreement Online Services Agreement checkbox. You may be asked to complete a short questionnaire. Be sure to log in before continuing.

Next, let’s create our first channel.

Create 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 14-2.
Figure 14-2

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 14-3 shows an example of the form.
Figure 14-3

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.

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.

Now, let’s create a practice channel that we will use in the next section to see how to write data (sometimes called upload) to ThingSpeak. Use the following parameters for the fields on the New Channel form:
  • Name: practice_channel

  • Description: Testing ThingSpeak connection from Pico

  • Field 1: RandInt

Enter the values as shown and then click Save Channel to complete the process. Now we are ready to test writing some data.

How to Add ThingSpeak to Your Projects

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, etc. Figure 14-4 shows the API Keys tab for the channel created previously.
Figure 14-4

API keys for a practice 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 Pico, we will use the library for the Pimoroni Pico Wireless Pack and the HTTP protocol.

Now that you understand the basics of creating a channel in ThingSpeak, let’s take a look at how to do it in more detail for the Pico.

Using ThingSpeak with the Pico

This project is a very simple sketch to learn how to connect and write data to a ThingSpeak channel. For the data, we will be generating a random integer and send that to the channel. While this won’t necessarily give you anything meaningful, we keep things simple so we can see the mechanics of how to interact with ThingSpeak.

The hardware we will use is our Pico Omnibus host board and Wireless Pack from Chapter 13. Refer to the projects in Chapter 13 on how to set up these components for use in this example.

Configuring the Raspberry Pi Pico

To write data to the ThingSpeak channel, we need to ensure we have the Pimoroni Pico Wireless Pack library (ppwhttp.py) uploaded. That’s it! Now, let’s write the code. As you will see, it uses a function in the ppwhttp.py module for uploading data to ThingSpeak.

Write the Code

While there is no library for the Raspberry Pi Pico, we can write one and use it in later examples. The class module we will create is named thingspeak.py and will contain a class named ThingSpeak. For the public methods, we need only a constructor and a function to write (upload) data to ThingSpeak. We will use three private methods for connect, disconnect, and parsing the response operations. To make it a bit more tolerant of networking issues, we will also build a retry loop into the upload procedure. While we will see the complete code for these functions, we will only discuss the highlights of each, leaving explanation of the code details as an exercise.

Note

If you want to read data from ThingSpeak, you can add that function to this class extending its use to other projects.

Let’s begin with the imports and constants. In order to send data to a server, we must work with some lower-level methods defined in a class named picowireless (used by ppwhttp). We will also need the JSON and time libraries. We use the JSON library to convert a Python dictionary into a JSON string. It is very easy to use, and it is just one line of code. The following shows an example of how to convert a Python dictionary into a JSON string for use in uploading data:
param_str = json.dumps(param_dict)

We will use several constants, most of which are for communicating over HTTP with ThingSpeak including the hostname, port, mode, and delay values. We will also form a header packet as a constant that we will complete with values at runtime (so, it is a format string). Finally, we will also set a constant for the number of retries to attempt to connect and send data. This is necessary since communicating over HTTP can fail due to a number of reasons (primarily because packets are not guaranteed to be delivered).

Listing 14-1 shows the import and constant sections for the ThingSpeak class.
# Import libraries
import json
import sys
import time
import picowireless
import ppwhttp
# Constants
MAX_RETRIES = 10
TCP_MODE = const(0)
REQUEST_DELAY = const(30)
THINGSPEAK_PORT = 80
THINGSPEAK_HOST = "api.ThingSpeak.com"
HTTP_HEADERS = """POST /update HTTP/1.1
Host: api.ThingSpeak.com
Accept: */*
Content-Length: {0}
Content-Type: application/json
{1}
"""
...
Listing 14-1

ThingSpeak Class Import and Constant Sections

Notice the HTTP_HEADERS constant. This is defined using a document string (a string with three "’s on either side), which is used as shown complete with newlines. This is important because this forms an HTTP packet which we will send to the server. If you notice the placeholders, you will find we have two: one for the length of the data and another for the data. Since we set the header variable Content-Type to application/json, we will need to format the data as a JSON string.

For the constructor, we will accept the API key and user-customized maximum retries with a default of MAX_RETRIES. Since this code is run once, we will create a socket connection to the server as well. Listing 14-2 shows the constructor.
def __init__(self, key, num_retries=MAX_RETRIES, with_debug=False):
    self.api_key = key
    self.max_retries = num_retries
    self.with_debug = with_debug
    self.port = THINGSPEAK_PORT
    # Connect to WiFi
    ppwhttp.start_wifi()
    # Resolve the IP address for ThingSpeak
    self.host_address = picowireless.get_host_by_name(THINGSPEAK_HOST)
    if self.with_debug:
        print("DNS resolved '{}' to {}.{}.{}.{}"
              "".format(THINGSPEAK_HOST, *self.host_address))
    # Get a client socket
    self.client_sock = picowireless.get_socket()
Listing 14-2

Constructor for the ThingSpeak Class

For the upload_data() function, we will require a Python dictionary that includes each of the keys and their values. We have to add the API key, but we can do that easily. In the function, we will create a loop that contains a try...except block for calling the network functions we will use. Specifically, we open a connection to the ThingSpeak server, issue the POST request, then wait for a status code. We then test the code to ensure the upload worked.

If we encounter a problem with any of the network functions, we sleep for five seconds and then try the commands again. We will do this up to MAX_RETRIES or until the operation succeeds.

Listing 14-3 shows the code for the upload_data() function.
    def upload_data(self, param_dict, timeout=5000):
        if self.with_debug:
            print("parameters: {0}".format(param_dict))
        # Add API key to the dictionary
        param_dict.update({'api_key': self.api_key})
        param_str = json.dumps(param_dict)
        # Attempt to connect to ThingSpeak
        retry = 0
        while retry <= self.max_retries:
            try:
                print("Connecting to ThingSpeak...", end="")
                if not self._connect():
                    print("Connection failed!")
                    return False
                print("connected.")
                break
            except Exception as err:
                print(" WARNING: ThingSpeak connection failed: {0}"
                      "".format(err))
                if retry <= self.max_retries:
                    print("Retrying in 5 seconds. [{}]".format(retry+1))
                    time.sleep(5)
                    retry = retry + 1
                else:
                    retry = self.max_retries + 1
                    print("WARNING: Cannot connect to ThingSpeak. "
                          "Exceeded retries. Abort.")
                    self._disconnect()
                    return False
        # Format the POST to send data to ThingSpeak
        post_header = HTTP_HEADERS.format(len(param_str),
                       param_str).replace(" ", " ")
        if self.with_debug:
            print("POST HEADER: ", post_header)
        # Attempt to retry if the timeout fails
        retry = 0
        while retry <= self.max_retries:
            try:
                print("Sending data...", end="")
                picowireless.send_data(self.client_sock, post_header)
                print("done.")
                break
            except Exception as err:
                print(" WARNING: ThingSpeak update failed: {0}".format(err))
                if retry <= self.max_retries:
                    print("Retrying in 5 seconds. [{}]".format(retry+1))
                    time.sleep(5)
                    retry = retry + 1
                else:
                    retry = self.max_retries + 1
                    print("WARNING: Cannot upload to ThingSpeak. "
                          "Exceeded retries. Abort.")
                    self._disconnect()
                    return False
        response = self._get_response(timeout)
        # Check header for correct status.
        if response["status"] == "200 OK":
            print("Data upload complete.")
        elif response['status'] == "ERROR":
            print("ERROR: {0}".format(response['body']))
        else:
            print("WARNING: data not acknowledged.")
            if self.with_debug:
                print("Header, body: {0} {1}"
                      "".format(response['header'], response['body']))
        self._disconnect()
        return True
Listing 14-3

The upload_data() Function

Notice we retry both the connect and send data operations. Here is where we are using the lower-level functions from the picowireless module. Specifically, once the connect is made with the server, we call the picowireless.send_data() function to send the HTTP POST packet we created to the server. We then wait for a response from the server and parse it. This is where the three helper (private) functions come into play.

There are also three private functions: _connect(), _disconnect(), and _get_response(). We use the _connect() function to connect to the server using the client_start() function from the picowireless library and wait for a response. If we don’t get a response before the timeout, we fail. In the _disconnect() function, we stop the client connection. And in the _get_response() function, we retrieve the data from the server using the avail_data() function from the picowireless library, then parse the response packet looking for a status code of 200, which indicates a successful operation. If we see any other code, we flag it as a warning and print the header. How this all works will become clear once you read the code.

Listing 14-4 shows the complete code for this class with comments removed for brevity. Take some time to read it so that you familiarize yourself with how it works.
# Import libraries
import json
import sys
import time
import picowireless
import ppwhttp
# Constants
MAX_RETRIES = 10
TCP_MODE = const(0)
REQUEST_DELAY = const(30)
THINGSPEAK_PORT = 80
THINGSPEAK_HOST = "api.ThingSpeak.com"
HTTP_HEADERS = """POST /update HTTP/1.1
Host: api.ThingSpeak.com
Accept: */*
Content-Length: {0}
Content-Type: application/json
{1}
"""
class ThingSpeak:
    def __init__(self, key, num_retries=MAX_RETRIES, with_debug=False):
        self.api_key = key
        self.max_retries = num_retries
        self.with_debug = with_debug
        self.port = THINGSPEAK_PORT
        # Connect to WiFi
        ppwhttp.start_wifi()
        # Resolve the IP address for ThingSpeak
        self.host_address = picowireless.get_host_by_name(THINGSPEAK_HOST)
        if self.with_debug:
            print("DNS resolved '{}' to {}.{}.{}.{}"
                  "".format(THINGSPEAK_HOST, *self.host_address))
        # Get a client socket
        self.client_sock = picowireless.get_socket()
    # Attempt to connect to ThingSpeak to create a client connection.
    def _connect(self, timeout=1000):
        picowireless.client_start(self.host_address, self.port,
                                  self.client_sock, TCP_MODE)
        t_start = time.time()
        timeout /= 1000.0
        while time.time() - t_start < timeout:
            state = picowireless.get_client_state(self.client_sock)
            if state == 4:
                return True
            time.sleep(1.0)
        return False
    # Stop the client connection.
    def _disconnect(self):
        # Stop the client
        picowireless.client_stop(self.client_sock)
    # Upload the data in the form of a dictionary to ThingSpeak.
    def upload_data(self, param_dict, timeout=5000):
        if self.with_debug:
            print("parameters: {0}".format(param_dict))
        # Add API key to the dictionary
        param_dict.update({'api_key': self.api_key})
        param_str = json.dumps(param_dict)
        # Attempt to connect to ThingSpeak
        retry = 0
        while retry <= self.max_retries:
            try:
                print("Connecting to ThingSpeak...", end="")
                if not self._connect():
                    print("Connection failed!")
                    return False
                print("connected.")
                break
            except Exception as err:
                print(" WARNING: ThingSpeak connection failed:"
                      " {0}".format(err))
                if retry <= self.max_retries:
                    print("Retrying in 5 seconds. [{}]".format(retry+1))
                    time.sleep(5)
                    retry = retry + 1
                else:
                    retry = self.max_retries + 1
                    print("WARNING: Cannot connect to ThingSpeak. "
                          "Exceeded retries. Abort.")
                    self._disconnect()
                    return False
        # Format the POST to send data to ThingSpeak
        post_header = HTTP_HEADERS.format(len(param_str),
                      param_str).replace(" ", " ")
        if self.with_debug:
            print("POST HEADER: ", post_header)
        # Attempt to retry if the timeout fails
        retry = 0
        while retry <= self.max_retries:
            try:
                print("Sending data...", end="")
                picowireless.send_data(self.client_sock, post_header)
                print("done.")
                break
            except Exception as err:
                print(" WARNING: ThingSpeak update failed: {0}".format(err))
                if retry <= self.max_retries:
                    print("Retrying in 5 seconds. [{}]".format(retry+1))
                    time.sleep(5)
                    retry = retry + 1
                else:
                    retry = self.max_retries + 1
                    print("WARNING: Cannot upload to ThingSpeak. "
                          "Exceeded retries. Abort.")
                    self._disconnect()
                    return False
        response = self._get_response(timeout)
        # Check header for correct status.
        if response["status"] == "200 OK":
            print("Data upload complete.")
        elif response['status'] == "ERROR":
            print("ERROR: {0}".format(response['body']))
        else:
            print("WARNING: data not acknowledged.")
            if self.with_debug:
                print("Header, body: {0} {1}"
                      "".format(response['header'], response['body']))
        self._disconnect()
        return True
    # Get the response from the server.
    def _get_response(self, timeout):
        # Wait for a response
        t_start = time.time()
        while True:
            if time.time() - t_start > timeout:
                picowireless.client_stop(self.client_sock)
                err_msg = ("Timeout waiting for response {0}:{1}"
                           "".format(self.host_address, self.port))
                return {'status':'ERROR', 'header': {}, 'body': err_msg}
            avail_length = picowireless.avail_data(self.client_sock)
            if avail_length > 0:
                break
        if self.with_debug:
            print("Got {} bytes in response.".format(avail_length))
        # Read the response from the server (in bytes)
        response = b""
        while len(response) < avail_length:
            data = picowireless.get_data_buf(self.client_sock)
            response += data
        response = response.decode("utf-8")
        # Break into the header and body
        head, body = response.split(" ", 1)
        if self.with_debug:
            print("Header: ", head)
        status = "UNKNOWN"
        # Find the status
        for line in head.split(" ")[1:]:
            key, value = line.split(": ", 1)
            if key == 'Status':
                status = value
                break
        return {'status': status, 'header': head, 'body': body}
if __name__ == '__main__':
    try:
        sample_params = {'field1': 42}
        api_key = "YOUR_API_KEY_GOES_HERE"
        ThingSpeak = ThingSpeak(api_key, with_debug=True)
        ThingSpeak.upload_data(sample_params)
    except (KeyboardInterrupt, SystemExit) as ex:
        print(" bye! ")
    sys.exit(0)
Listing 14-4

The ThingSpeak Class (Python)

Notice once again we’ve employed a simple mechanism to allow us to test the class. You can simply provide a value for your API write key (not the read key) and run the class module to send a single value to your channel. If you’re eager to get started, try it out now.

Next is the code for the main script. We will use the new class to upload the random number we generate. We will name the main script test_thingspeak.py. If you are following along, open a new file now with that name. Be sure to place it in the same folder as the thingspeak.py module.

Listing 14-5 shows the complete code for the script for this project. It follows a now familiar pattern where we create a main() function and call it from a try...except block to catch a CTRL+C key sequence. The code is very simple. All you need to do is put your API key in the constant and run it.
# Import libraries
import random
import sys
import time
from thingspeak import ThingSpeak
# API KEY
THINGSPEAK_APIKEY = 'YOUR_WRITE_API_KEY_HERE'
def main():
    """main"""
    print("Welcome to the ThingSpeak Raspberry Pi Pico demonstration!")
    print("Press CTRL+C to stop.")
    thingspeak = ThingSpeak(THINGSPEAK_APIKEY)
    while True:
        # Generate a random integer
        rand_int = random.randint(1, 20)
        print("Random number generated: {}".format(rand_int))
        thingspeak.upload_data({'field1': rand_int})
        # Sleep for 30 seconds
        time.sleep(30)
if __name__ == '__main__':
    try:
        main()
    except (KeyboardInterrupt, SystemExit) as err:
        print(" bye! ")
sys.exit(0)
Listing 14-5

Complete Code for the test_thingspeak.py Script

Notice the dictionary we used to pass the data to the upload_data() function. Here, we used field1 as the key for the channel field. As it turns out, we must use field1, field2, etc. for the field key names regardless of how we may name them in the channel. While this may be a little strange, you should get in the habit of listing the fields in the dictionary in the order they appear in the channel setup.

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

To run the script, enter the following command. Let the script run for several iterations before using Ctrl+C to break the main loop. Listing 14-6 shows an example of the output you should see without debug enabled in the ThingSpeak class.

Note

You may see retry attempts if your network drops or you lose connectivity.

Welcome to the ThingSpeak Raspberry Pi Pico demonstration!
Press CTRL+C to stop.
Connecting to Snapper1...
Connected!
Random number generated: 3
Connecting to ThingSpeak...connected.
Sending data...done.
Data upload complete.
Random number generated: 7
Connecting to ThingSpeak...connected.
Sending data...done.
Data upload complete.
Random number generated: 14
Connecting to ThingSpeak...connected.
Sending data...done.
Data upload complete.
Random number generated: 9
Connecting to ThingSpeak...connected.
Sending data...done.
...
Random number generated: 13
Connecting to ThingSpeak...connected.
Sending data...done.
bye!
Listing 14-6

Example Console Output (No Debug)

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() function 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 three minutes before you visit ThingSpeak. Once the sketch has run for some time, navigate to ThingSpeak, log in, and click your channel page and then click the Private View tab. We use the private view because channels are private by default. You should see results similar to those shown in Figure 14-5.
Figure 14-5

Example channel data (Python)

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.

If you run with the debug turned on, you will see a lot more data similar to what is shown in Listing 14-7. You may want to turn on debug if you encounter problems uploading your data. Notice it prints out your API key, so use this with caution in public areas.
Welcome to the ThingSpeak Raspberry Pi Pico demonstration!
Press CTRL+C to stop.
Connecting to Snapper1...
Connected!
DNS resolved 'api.ThingSpeak.com' to 3.224.210.136
Random number generated: 4
parameters: {'field1': 4}
Connecting to ThingSpeak...connected.
POST HEADER:
 POST /update HTTP/1.1
Host: api.ThingSpeak.com
Accept: */*
Content-Length: 44
Content-Type: application/json
{"field1": 4, "api_key": "XXXXXXXXXXXXXXXXXXXX"}
Sending data...done.
Got 646 bytes in response.
Header:
 HTTP/1.1 200 OK
Date: Mon, 27 Dec 2021 21:24:17 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 2
Connection: keep-alive
Status: 200 OK
X-Frame-Options: SAMEORIGIN
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, OPTIONS, DELETE, PATCH
Access-Control-Allow-Headers: origin, content-type, X-Requested-With
Access-Control-Max-Age: 1800
ETag: W/"b17ef6d19c7a5b1ee83b907c595526dc"
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 3a8821ed-352e-450c-a541-be36d57d25a6
X-Runtime: 0.033155
X-Powered-By: Phusion Passenger 4.0.57
Server: nginx/1.9.3 + Phusion Passenger 4.0.57
Data upload complete.
bye!
Listing 14-7

Example Console Output (with Debug)

Tip

Most issues you will encounter uploading data to your channels can be solved by ensuring you have the correct write key for the channel you want to update. Be sure to double-check the key first if you have problems.

If you encounter problems or want to run the test_thingspeak.py script again, you can remove all data in your channel by clicking the Channel Settings tab and then scrolling to the bottom and clicking the Clear Channel button as shown in Figure 14-6. Once you acknowledge the clear, all data from your channel will be deleted.
Figure 14-6

Delete all data in the channel

Now, let’s turn our attention to how we can modify our example projects retooling them to upload their data to ThingSpeak.

Note

ThingSpeak free accounts are limited to four channels. If you plan to implement many projects, you may need to delete one or more channels or upgrade your account to a paid subscription.

Example Project: IoT Environment Monitor

This section includes one of the projects from previous chapters that we will update to send data to ThingSpeak for visualization. We will use the environment monitor project from Chapter 12. If you have not implemented this project, you may want to do so before attempting the following examples.

The following sections present the details at a high level, and much of the detail for the original project is omitted for brevity. Rather, we will see details on the channel to create, how to prepare and modify the source files, and then a demonstration of executing the project. Let’s begin with the hardware.

Required Components

The hardware for this project is the same as Chapter 12 with the exception we won’t be using the Grove Shield for the Pi Pico. Rather, we will be using the Pico Omnibus so that we can add the RTC and Pico Wireless Pack.

Unfortunately, this means we will not have any Grove connectors to connect our array of Grove modules. However, there are two ways we can overcome this limitation, using Grove cables and connecting the Pico Omnibus to the Pico Grove Shield using jumper wires.

For the Grove cable option, we will use two different sets of Grove cables and a breadboard. To connect Grove modules directly to the Pico, we use the Grove – 4 pin Female Jumper to Grove 4 pin Conversion Cable (www.seeedstudio.com/Grove-4-pin-Female-Jumper-to-Grove-4-pin-Conversion-Cable-5-PCs-per-PAck.html) as shown in Figure 14-7.
Figure 14-7

Grove breakout cable with female header (courtesy of seeedstudio.com)

We may also need to use the male version named Grove – 4 pin Male Jumper to Grove 4 pin Conversion Cable (www.seeedstudio.com/Grove-4-pin-Male-Jumper-to-Grove-4-pin-Conversion-Cable-5-PCs-per-Pack.html) to use a breadboard to gang the ground and power cables together since there are so few of those pins on the Pico GPIO header. Figure 14-8 shows the male version of the breakout cable.
Figure 14-8

Grove breakout cable with male header (courtesy of seeedstudio.com)

For the jumper wire option, we will use M/F jumper wires to connect the GPIO header on the Pico Omnibus to the Grove Shield. The concept is we will “jump” all pins used by the Grove Shield from the Omnibus to the Grove Shield. This will mimic using a Pico connected to the Grove Shield (we will keep the Pico installed on the Pico Omnibus).

Since using the Grove breakout cables can be cumbersome and require the use of a breadboard to connect the ground and power cables together, we will instead jumper the GPIO header of the Pico Omnibus to the Grove Shield. While this will require a number of jumper wires, it is the easiest option to implement. However, you are welcome to use the Grove breakout cable option if that works best for you.

Set Up the Hardware

You should acquire and set up all of the modules as listed in Chapter 12. If you decide to use the 3D printed mounting plate or if you already have that done, you can leave all of the modules mounted and connected to the mounting plate and plugged into the Grove Shield. You simply remove the Pico from the Grove Shield before you start connecting it to the Pico Omnibus. Figure 14-9 shows the starting point for the hardware assuming you implemented the project in Chapter 12.
Figure 14-9

Hardware from Chapter 12 with Pico removed

The easiest way to connect the Grove Shield to the Pico Omnibus is to use 40 M/F jumper wires. Connecting them is very easy. Just connect the male pin of each jumper to the Pico Omnibus noting the pin number and plug it into the corresponding pin on the Grove Shield using the female pin. If you want to avoid the inevitable “bird’s nest” of wires once you’re done, you can purchase M/F jumper wires that are connected together. Several vendors such as SparkFun and Adafruit carry these, and if you get a set that has 20 connected together, it makes connecting the Pico Omnibus and Grove Shield easier and less messy.

For example, SparkFun has the Jumper Wires – Connected 6” (M/F, 20 pack). Since there are 20 connected together, we will need 2 of these to complete the project. Each set costs $1.95. Figure 14-10 shows the connected jumper wires from SparkFun.
Figure 14-10

M/F Jumper Wires Connected (courtesy of sparkfun.com)

The best way to use these jumpers is to break them into sets of ten. Connect each set of ten first to the Pico Omnibus and then the Grove Shield. When you connect across the GPIO header side, interleave the sets so that you remove most of the strain. Be sure to push the pins all the way in as it is easy to have one or more pins partially inserted.

Using a set of ten has another benefit. There are ten colors in each set, so you can visually inspect the colors to ensure you have the pins connected in order. This helps remove the need to check each pin on each GPIO header – a time-consuming and challenging task! Using groups of ten connected together will make the connections easier.

Caution

Be sure to double- and triple-check all of your connections to ensure you have all of the wires connected to the correct pins. Do not power on your Pico until you have verified all of your connections.

When you are done, you should see an arrangement similar to Figure 14-11.
Figure 14-11

Grove Shield for Pico connected to the Pico Omnibus

The best way to make sure you have all cables connected is to return to Chapter 12 and upload the project files to your Pico and run the main.py script for Chapter 12. Figure 14-12 shows the files you should have at a minimum in the project6 folder indicated thus far in the chapter. We will create a project9 folder in a later step. You can run main.py from Thonny on your PC.
Figure 14-12

Environment monitor files on the Pico

Now that we have our hardware setup, let’s create the ThingSpeak channel.

Create the ThingSpeak Channel

The data for this project contains numerical data as well as categorized data (in the form of a string). We will capture the raw data rather than the label (category). The data generated includes the temperature, barometric pressure, dust concentration, and air quality. So, we will need one channel with one field for each sensor or four fields in all.

Log in to your ThingSpeak account and click New Channel. We will name the channel IoT Environment Monitor. Use the information shown in Figure 14-13 to complete the form and then click Save Channel at the bottom of the form. Or, you can press Enter, which will save the channel for you. Note that you will need to tick the checkbox for Fields 2 and 3 to get them to accept input.
Figure 14-13

IoT Environment Monitor channel settings

Recall, we need to remember the order of the fields. Here, we have defined Temperature, Pressure, Dust Concentration, and Air Quality where they will be referenced as field 1, field 2, field 3, and field 4 in our code.

Now that we have the channel created, go to the API Keys tab, and record the API key. You will need this information in the next step. Figure 14-14 shows which key you will need.
Figure 14-14

Weather IoT channel API keys

Prepare the Project Files

For this project, create a new folder named project9 on your Pico and upload the project files from Chapter 12 (air_monitor.py, bmx280x.py, dust_sensor.py, mcp9808.py, and ssd1306.py). See Chapter 12 for the details on the source for some of the files.

Most of these files do not need to be modified. However, we will need to change all occurrences of project6 to project9 in the air_monitor.py file as shown in the following:
# Imports
from machine import ADC, I2C, Pin
import time
from project9.bmx280x import BMX280
from project9.mcp9808 import MCP9808
from project9.dust_sensor import DustSensor
We also need to copy our new class module, thingspeak.py, to the project9 folder as well as the ppwhttp.py file from Chapter 13. However, we need to make one slight change to the thingspeak.py module. We need to change the import for the ppwhttp library as follows:
from project9 import ppwhttp

Finally, copy the modified secrets.py file to the root of the Pico filesystem.

Figure 14-15 shows the complete list of files needed on the Pico. Note that we will modify the main.py in the next section.
Figure 14-15

Files needed on the Pico for the IoT Environment Monitor

With that administrative work done, we can add the preliminary code.

Update the Main Code

In this section, we will modify the main.py to add the ThingSpeak code. Recall, we need only add the import statement and API key. Listing 14-8 shows an excerpt of the code with the new lines added. The rest of the code from Chapter 12 remains the same. We will update the main() function to add the ThingSpeak code in the next section. Notice we also need to change project6 to project9 in the imports for the air_monitor and ssd1306 libraries.
from machine import Pin, I2C
import time
from project9.air_monitor import (AirMonitor,
    AIR_POOR, AIR_FAIR, AIR_GOOD, AIR_ERR)
from project9.ssd1306 import SSD1306_I2C
from project9.thingspeak import ThingSpeak
# API KEY
THINGSPEAK_APIKEY = 'YOUR_WRITE_API_KEY_HERE'
# Constants
SAMPLING_RATE = 5 # 5 second wait to start next read
BUZZER_PIN = 26
WARNING_BEEPS = 5
HIGH = 1
LOW = 0
...
Listing 14-8

Updates to the IoT Environment Monitor Main Script (Python)

Next, we need to declare an instance of our ThingSpeak class from the thingspeak.py library module and then, after reading the data, use the existing Python dictionary we created in the class (env_data) and pass that to our thingspeak.upload_data() function call. Listing 14-9 shows the function with changes in bold. The rest of the code for this version remains the same as we had in Chapter 12.
...
def main():
    """Main"""
    print("Welcome to the Environment Monitor!")
    # Setup buzzer
    buzzer = Pin(BUZZER_PIN, Pin.OUT)
    i2c = I2C(0,scl=Pin(9), sda=Pin(8), freq=100000)
    print("Hello. I2C devices found: {}".format(i2c.scan()))
    oled = setup_oled(i2c)
    # Start the AirMonitor
    air_quality = AirMonitor(i2c)
    time.sleep(3)
    oled_write(oled, 11, 4, "done")
    beep(buzzer)
    oled.fill(0)
    oled.show()
    thingspeak = ThingSpeak(THINGSPEAK_APIKEY)
    while True:
        if air_quality.read_data():
            # Retrieve the data
            env_data = air_quality.get_data()
            oled_write(oled, 0, 0, "ENVIRONMENT DATA")
            oled_write(oled, 0, 2, "Temp: ")
            oled_write(oled, 5, 2,
                       "{:3.2f}C".format(env_data["temperature"]))
            oled_write(oled, 0, 3, "Pres: ")
            oled_write(oled, 5, 3,
                       "{:05.2f}hPa".format(env_data["pressure"]))
            oled_write(oled, 0, 4, "Dust: ")
            if env_data["dust_concentration"] == 0.0:
                oled_write(oled, 5, 4, "--         ")
            else:
                oled_write(oled, 5, 4,
                          "{:06.2f}%".format(env_data["dust_concentration"]))
            oled_write(oled, 0, 5, "airQ: ")
            if env_data["air_quality"] in {AIR_ERR, AIR_POOR}:
                oled_write(oled, 5, 5, "POOR")
            elif env_data["air_quality"] == AIR_FAIR:
                oled_write(oled, 5, 5, "FAIR")
            elif env_data["air_quality"] == AIR_GOOD:
                oled_write(oled, 5, 5, "GOOD")
            else:
                oled_write(oled, 5, 5, "--       ")
            # Check for environmental quality
            if ((env_data["dust_concentration"] > MAX_DUST) or
                    (env_data["temperature"] > MAX_TEMP) or
                    (env_data["air_quality"] == AIR_POOR) or
                    (env_data["air_quality"] == AIR_ERR)):
                #pylint: disable=unused-variable
                for i in range(0, WARNING_BEEPS):
                    oled_write(oled, 3, 7, "ENV NOT OK")
                    beep(0.250)
                    time.sleep(0.250)
                    oled_write(oled, 3, 7, "          ")
                    time.sleep(0.250)
            # Send data to ThingSpeak channel
            data = {
                'field1': env_data['temperature'],
                'field2': env_data['pressure'],
                'field3': env_data['dust_concentration'],
                'field4': env_data['air_quality']
            }
            thingspeak.upload_data(data)
        else:
            oled.fill(0)
            oled.show()
            oled_write(oled, 0, 2, "ERROR! CANNOT")
            oled_write(oled, 0, 3, "READ DATA")
        time.sleep(SAMPLING_RATE)
...
Listing 14-9

Updates to the IoT Environment Monitor Main Function (Python)

That’s it, we’re ready to execute the project. We will need to let it run for a few minutes so we can get some data. If you’re running the project in a controlled environment where the values do not change, you may not notice much variation. As an exercise, consider altering the environment to stimulate changes in the data. Don’t use flame or touch the electronics in any way while they are running.

Execute and Visualize the Data

At this point, you can set up the hardware and run the project. Let it run for about 20 minutes and then visit your ThingSpeak channel page. You should see your data in the channel private view similar to Figure 14-16.
Figure 14-16

Example results (IoT Environment Monitor example)

Once again, you may not see a lot of variances in the data if you run it in a controlled environment. For better results in a controlled environment, you should consider changing the sample rate from 30 seconds to every 4–6 hours. This should help show how the data changes over the course of a day. Listing 14-10 shows an example execution. You should see something similar.
Welcome to the Environment Monitor!
Hello. I2C devices found: [24, 60, 119]
Connecting to Snapper1...
Connected!
>> Reading Data <<
> Reading temperature = 18.5625
> Reading pressure = 1006.613
> Reading dust concentration
> PM concentration: 0.6303307 pcs/0.01cf
> Dust concentration = 0.6303307
> Reading air quality = 2
Connecting to ThingSpeak...connected.
Sending data...done.
Data upload complete.
...
>> Reading Data <<
> Reading temperature = 20.125
> Reading pressure = 1006.402
> Reading dust concentration
> PM concentration: 0.6453933 pcs/0.01cf
> Dust concentration = 0.6453933
> Reading air quality = 2
Connecting to ThingSpeak...connected.
Sending data...done.
Data upload complete.
>> Reading Data <<
> Reading temperature = 19.9375
> Reading pressure = 1006.338
> Reading dust concentration
> PM concentration: 0.62572 pcs/0.01cf
> Dust concentration = 0.62572
> Reading air quality = 2
Connecting to ThingSpeak...connected.
Sending data...done.
...
Listing 14-10

IoT Environment Monitor Execution

However, notice the air quality line graph. That’s not telling us anything, is it? What if we created an indicator widget for that data that changes color when the air quality gets poor?

In fact, you can create a gauge, numeric display, or an indicator (like an LED or light) for your data that triggers on some value or threshold. See the ThingSpeak documentation for more details about these options. For this project, an indicator is an excellent choice for the air quality to provide an at-a-glance readout.

Let’s do that. Go ahead and click Add Widgets, then select the indicator and fill in the settings as shown in Figure 14-17, and then click Create. Notice I set the indicator to turn on only if the air quality (field 4) reaches three or more.
Figure 14-17

Creating an indicator (IoT Environment Monitor example)

When air quality (field 4) is less than or equal to three, the indicator is dim as shown in Figure 14-18.
Figure 14-18

Indicator off (IoT Environment Monitor example)

Should the data reach a value of three to indicate poor air quality, the indicator will turn on as shown in Figure 14-19. This shows us how we can use the data to show thresholds reached. It can be used for high thresholds or low thresholds in which case you may want to choose a less alarming color such as green and so on.
Figure 14-19

Indicator on (IoT Environment Monitor example)

Now, we can take this a step further and create an array of indicators for the air quality. For example, we can create one for good air quality (green indicator, field 4 = 2), another for fair (yellow indicator, field 4 = 1), and another for bad (red indicator, field 4 = 0). Figure 14-20 shows an example of the indicators. Note that you can drag and drop the widgets on the view to rearrange them. Nice! Note: The indicator colors are green for good, yellow for poor, and red for bad quality.
Figure 14-20

Air quality indicators (IoT Environment Monitor example)

Now I can see at a glance what the air quality is at the moment of last data read. Very nice!

There is just one more step you may want to consider – making the data public.

Public View

By default, your data in your channel is private. Only you can see it when you log in. However, you can share the data views to more people. You can choose one of the following options:
  • Keep channel view private: Only you can see the data.

  • Share channel view with everyone: Anyone can view the data via its URL on the Public View tab. Thus, it only shows the public widgets and such that you create.

  • Share channel view only with the following users: Only those users that you specify can see the data on your Private View tab. To add a new user, enter their email address and click the Add User button. Each user is then emailed an invitation with the URL for the private data view. If they do not have a ThingSpeak account, they must create one to access the data. Once logged in, users can click the ChannelsChannels Shared with Me menu to see the shared channels.

To choose one of the public options, click the Sharing tab of your channel and make your selection. If you choose to share the data, you won’t be given a specific URL to use. However, you can click the Public View tab and then copy the URL in your browser and share that. For example, it will resemble the following:3
https://thingspeak.com/channels/16A94A7

Similarly, you can do the same for those users you identified in the last option.

Summary

If you have implemented all of the projects in this book, congratulations! You are now ready to tackle your own IoT projects. If you’re still working on the examples, keep at it until you’ve learned everything you need to know to build your own IoT projects.

Our journey in learning how to build projects for the Raspberry Pi Pico using MicroPython has concluded with a dive into how to use ThingSpeak to satisfy the needs of your IoT project for storing and displaying your data. In this chapter, we learned how to get started with ThingSpeak from creating our account to creating channels to storing our data and even some insights into how to modify the visualizations. Together with the knowledge you gained in this chapter and the previous chapters, you now have the skills to complete your own IoT projects.

In fact, you can now put down this book in triumph and start thinking of some really cool ways you can implement what you have learned. You want to monitor events and data in your house, workshop, or garage. Or you want to design a more complex project that monitors sound, movement, and ambient temperature changes (like a home security system). Or you want to revisit the example project chapters and implement the suggestions at the end of each chapter. All that and more is possible with what you have learned in this book. Good luck, and happy MicroPython programming!

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

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