Cloud IoT step-by-step: Cloud to device communication
Hi friends!
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.
As I mentioned, this post builds on my previous post, so you’ll need to be sure you’ve done the Pi setup basics, the library dependency setup and preparing the Google Cloud Platform steps.
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:
Both 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 registryId
and 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 “clear”
with “red”
. Don’t worry about the serviceAccount
or which
yet.
For the function to execute box, put in updateDevice
.
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.
Click 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.
In the 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 “red”
with “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:
TESTING: clear
matching message text: clear
RESPONDING
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:
https://us-central1-gweiss-project.cloudfunctions.net/test-functin/index.js?message=red
The message variable is a direct passthrough to the parsing code. So anything I mentioned above are viable variables to pass, e.g. temp
, 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=config
, or 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.
Next step: Become a bit more efficient by using the command line for setup and testing.