This tutorial builds on the previous one, and assumes you’re comfortable connecting your device up to a Google Cloud Project. If you’re not, it’s okay! Just follow that link, and do the other one, then come back for this one.
So now you’ve got a device sending its data up into the Cloud, but what if you wanted to send messages back down to your device? Like all things IoT, there are many different ways to do this. If you really wanted to, you could even set up Apache or NGINX on your Pi and run your own web server on it! For most cases, that’s probably a bit overkill, so today I want to cover sending simple messages back down to your device, and doing something based on the message.
By the end of this post, you’ll have the Cloud sending messages down to your device.
Table of Contents:
- Types of messages from Cloud
- Device setup and code
- Sending messages from Cloud Console
- Sending messages from a Google Cloud Function
- Creating a service account
- Finalizing the function
- Running with device
Types of messages from Cloud
There are two types of messages (currently) that you can send back down to your device from Google Cloud through the IoT Core Admin SDK (also called the cloudiot REST API).
- Configuration messages. These are versioned in the Cloud, so you can send/retrieve specific versions of the message. The latest config message will be sent to the device when the device connects to the Cloud. So even if your device isn’t currently connected, this is a way to set some state on it. It has some limitations. The biggie is you can only send up to one configuration message per second per device.
- Command messages. These are fire and forget. They aren’t versioned, so if your device isn’t currently connected to the Cloud when you send one of these, it’s gone. As a result, they’re much faster and can be sent more frequently. 1000 messages per second per device registry (but can be increased on a case-by-case basis).
My take is, if it’s something you want to persist on the device? Configuration. Transient information you want to respond to if your device is “live”? Command.
Device setup and code
In the same GitHub repo as the last post, found here. The code for this post is 02_basics.py. We’re still using the Sense HAT, and I’ll walk you through the pieces of the code that are new from last time.
Done those? Great!
Same as last time, be sure you’ve edited the variable code block to match your specific project and cert locations.
You can see what values are supported in the
respondToMsg(msg) method. If you want to tinker with adding more, that’s where to do it.
Apologies for the flickering, the frequency of the LED matrix matches the camera. What you’re seeing is me sending
“red” and then
“rainbow” through IoT Core to my Pi. The wobbling is because I’m holding the camera as I’m typing “rainbow”.
One note, don’t freak out if it feels like it takes awhile to get the message to the device the first couple times. There’s a thing called “cold start”. Once you’ve done something a couple times with a Cloud Function, it gets cached and stays in-memory, so it’ll happen faster on subsequent firings.
So how do we get these messages to our device?
Sending messages from Cloud Console
This is the easy way to do it. Great for testing to be sure everything’s hooked up right and working. Also, IMO, the least useful practical method. Since it’s manual from the console, it’s fine for one-time updates, but in terms of a real use-case? Pretty niche.
Navigate to the IoT Core page on the console, either from the search bar or the hamburger menu.
Drill down into the device that you’ve created in IoT Core and on the device details page, you’ll see these options:
update config and
send command open a very similar dialog, which allows you to send either a text, or a base64 encoded message back to your device. Make sure
text is selected in that dialog. For now, don’t worry about the subfolder added option in the commands dialog. We won’t be using it.
Keep in mind, that to receive commands the device has to be running the code (connected actively). And for config, if your device is connected, it will receive them live, and in addition upon connecting, the device will receive the most recent config message. So e.g. if you’ve sent the “red” config message, and cleared the LED matrix another way, on connecting, your device will get “red” from the Cloud and will turn the LED matrix red.
Sending messages from a Google Cloud Function
There are as many ways to send messages to your device through the IoT Core API as there are ways to call a REST API. One way I wanted to highlight is using Cloud Functions. They’re serverless methods that can either be triggered by calling an endpoint (HTTPS), or in response to some Cloud event (you can see which ones in the drop-down when we create one). They’re nice and easy to setup and deploy. There are some faster options out there, which I’ll cover in future posts, but in terms of ease of setup, this one’s my favorite (personal opinion, others may disagree with me).
The function I wrote for this post is in the same repo, and can be found here.
There are two main ways to deploy Cloud Functions: Command line and the Console. I’m going to cover the console today, and I’ll cover all things command line in a later post.
In your GCP console, either search for Cloud Functions, or use the menu to navigate to Cloud Functions. In the navigation hamburger, it’s near the top, under Compute.
If you’ve never done anything with Cloud Functions, it’ll ask you to enable the API, go ahead and do that.
Click on the
Create Function link at the very top of the page. Give your function a name, leave the memory allocation alone, and leave the trigger alone. We’re going to use an HTTP endpoint to trigger our function. You can see the URL that will trigger this function on this page.
Cut/paste the code from my GitHub into the inline editor for the index.js, and copy the package.json contents from my GitHub into the package.json tab of the inline editor. Update the variables for your
deviceId in the code to the device we created in the previous section. The
projectId I have pulling from the environment, but you can also edit it explicitly. For the msg variable, replace
“red”. Don’t worry about the
For the function to execute box, put in
Next step, is down below the Function to execute box, there’s a little link for “More”. Click it.
This will expand out a few more options we need to tweak. The region should be the same as your project, and is fine. Timeout is fine.
Service account though, we need to talk about. There’s a piece in the code that asks you to
<paste contents of the service account json blob here>. There is a lot to service accounts, beyond the scope of this blog post. If you want to dive in deep, the docs are here. TL;DR is that these are bearer tokens that authorize your code to access different pieces of Google’s Cloud Platform including APIs, database access, etc. By default, you have a few of these already when you created the project, but they have broad sweeping permissions that we don’t want to just use everywhere, so we will create one just for this. You don’t need to change the one listed in the Cloud Function, I just want to bring it up because this will be the next step for us. Go ahead and click the
Create button at the very bottom to get the Cloud Function into the system and not lose any progress before we create our service account.
It’ll take a bit to deploy the function, and a note, when we end up tweaking the function later, when the spinning icon next to your function name turns green, it kind of lies. The function has deployed but likely hasn’t fully propagated its version. Give it another minute past the green checkbox and you’ll be good to go. Although this particular one is not yet ready to run successfully because we haven’t handled the auth yet. Let’s get that part done.
Creating a service account
In the console, search for
Service accounts and it’ll be the option with the IAM & admin sub-header (in my image here it’s the last one in that list)
Or use the hamburger menu to go IAM & admin -> Service accounts. Click the
Create service account link at the top of the page. Give it a name, and click
Create. In the
Select a role dropdown, scroll down until you find
Cloud IoT, and select the
Cloud IoT Admin option.
Continue. On this next page, click the
Create key button, leave the JSON radio button selected, and click the
Create button. This will download a JSON file to your computer. We’ll need that, so track where it lives, then click the
Done button to finalize the service account creation.
index.js file where it has that
<paste contents of the service account json blob here> comment, that’s where you want to paste the contents of the file you just downloaded. It’ll end up looking something like this:
Finalizing the function
Navigate back to your Cloud Function in the console, and on the details page for it, click
Edit. Paste the code from index.js into the inline editor, overwriting everything that’s there, and scroll to the bottom and click
Save. Wait that extra minute for deployment, and you should be able to click over to the
Testing tab (near top, under the version dropdown). Because we have logical defaults, you should at this point be able to hit
Test the function. If all has gone right, the
Test the function button will re-enable very quickly, because this doesn’t take long to actually run at all, but what we’re waiting for, is below, in the logs section, it’ll say “Fetching logs”. It can take a bit for this to come through, but in theory, we shouldn’t see any errors here. If you do, double-check all your variables for registry and device ID, and make sure you don’t have any extra quotes around the service JSON. Those two bit me when I was doing this, and the error messages are often not as clear as they could be.
Running with device
Alrighty, now turn your device on with the script running. You’ll see the telemetry publish output from the script. Currently it’s set to output four or five payloads to the Cloud (although if you haven’t uncommented the
client.publish call, then it doesn’t ACTUALLY send anything to the Cloud, just outputs the payload to console), and then the script will wait. Since we already ran the Cloud Function, when the device runs with the script, it should turn red, because that’s the config we set, and remember your device will be sent the latest config message upon connecting to IoT Core.
So now in your function (in the inline editor), go back and replace
“clear” for the
msg variable again since that’s our default and we want to be able to run with the default as clearing the matrix. To test to be sure it’s all still good, once it’s deployed (a minute past the green icon appears) test it again from the Function console’s testing tab while the script is still running on your device, and you should see the lights go out (don’t forget it may take a bit still at this stage of the game because of cold start).
Now that we’ve confirmed it works from a test…now for the real deal! The HTTP trigger from before, now we need it so we can test it for real. You can find it again if you didn’t copy it somewhere on the details page for your Cloud Function, check the
Trigger tab for the URL.
You can click that link and it’ll open another webpage to your trigger and it’ll fire. Because all the defaults are to clear, the LEDs won’t light up, but you’ll see my debug messages on the device from the script that look like:
matching message text: clear
Now for the REAL test. Add a GET variable to the end of your trigger URL. Taking my above example URL as a starting point, it might look like:
The message variable is a direct passthrough to the parsing code. So anything I mentioned above are viable variables to pass, e.g.
rainbow, etc. If you add any handling to the script, then those too would be viable variables to pass.
The last variable is the
which variable. This is the switch for whether the script issues a config message, vs. a command message. The two possible values are
which=command. Something to keep in mind, is that Cloud Functions on their own add latency to this pipeline. So issuing configs/commands from the console will always be faster than through the function. And as I mentioned, the first few times you fire a newly deployed function will also be slower.
If Node.js isn’t your language of choice, I’ve now written a new post that adds in the same Cloud Function that we have here, but in Python and Go. That post can be found here.
That’s it for this one! In upcoming blogs, I’ll talk about working with the Google Cloud command line tools, and what you can do around the IoT tools with it, and also some other options around Communication back to devices that solve different problems.
Let me know in the comments if you have any feedback, or things you’re dying to see me write about. You can also reach out to me on Twitter, my DMs are open.