Hello! Welcome to the "making an IoT connected sensor" tutorial.
We will be using the MSP432 LaunchPad & CC3100 Wi-Fi BoosterPack from Texas Instruments. In addition, we will add various analog sensors to our system, including moisture, potentiometers, ambient light sensors & sound sensors with the help of the modular Grove sensor ecosystem from Seeed Studio.
THE HARDWARE!
Rapid Prototyping with modular, open source building blocks!
The MSP432 LaunchPad
The MSP432 LaunchPad is a low-cost, easy-to-use development kit featuring the MSP432 microcontroller. The MSP432 device is a next-generation microcontroller from the ultra-low-power MSP family of devices from Texas Instruments. These devices are known for ultra-low-power consumption & enable very long battery life. The new MSP432 class device is now based on a powerful ARM Cortex M4F MCU (up to 48Mhz, 128kB Flash, 32kB RAM), while still retaining the same low-power features of the MSP family.
The MSP432 LaunchPad features an on-board programmer, access to all I/O pins of the MSP device & features an on-board RGB LED & pushbuttons.
This will be the application processor for our IoT connected sensor.
Learn more about the MSP432 LaunchPad here!
The CC3100 Wi-Fi BoosterPack
In order to connect to the all-knowing cloud, the MSP432 microcontroller will need the help of the CC3100 Wi-Fi BoosterPack. The CC3100 is a Wi-Fi network processor that can interact with a microcontroller of a SPI interface. Fortunately, we have a nice Energia library that enables the MSP432 device to do compelling things in the cloud, like connect to a network, handle REST APIs, publish/subscribe over MQTT & more.
When paired with the MSP432, the CC3100 provides a low-cost & low power solution for creating internet-connected devices that can run off a battery.
When plugging the CC3100 to your MSP432 LaunchPad, be sure that the silkscreen/labels printed on the PCB (printed circuit board) are in the same orientation as that of the under-lying LaunchPad.
Sensors & Buzzer
We are using the Seeed Studio Grove Starter Kit for LaunchPad, which provides various sensors and actuators for rapid prototyping. In our internet connected sensor, we are using the potentiometer module, moisture sensor, ambient light sensor, microphone/sound sensor & buzzer.
The pin assignment below will be helpful for when we're writing the software:
- Potentiometer module: Pin 23 (analog in)
- Moisture sensor: Pin 24 (analog in)
- Ambient Light Sensor: Pin 25 (analog in)
- Sound sensor/microphone: Pin 26 (analog in)
- Buzzer: Pin 40 (digital out)
Plugging it all in together!
This is what you should get! I also added an optional Lithium Polymer Rechargeable Battery BoosterPack to my setup to make it completely un-tethered!
THE SOFTWARE
Introducing Energia MT!
We are going to be using a unique feature now available in the latest version of Energia, an open source fork of the Arduino/Wiring framework for TI devices. The newest version of Energia (v15 or newer) features multi-tasking. This new functionality enables developers to run multiple sketches in parallel on a single device! You can learn more about this new feature here.
Energia MT is built on top of TI RTOS, but wraps up the powerful embedded OS into a more intuitive set of APIs within a simpler IDE. Note that Energia MT is a cooperative multi-tasking implementation, which means that all tasks are at the same priority. Each task will yield to other tasks to "time-slice" your application. For example if I have a task that is blinking an LED every 500ms, this task will yield to other tasks when it calls the delay(500) function. A nice benefit of Energia MT is that when all tasks are idle, the application will jump to low power mode automatically, improving battery life!
Energia MT enables inter-task communication/interaction through global variables. In addition, a new "EVENT" library is available for sending & waiting on events triggered from other tasks.
New tasks are identified automatically by the RTOS when a new setup/loop pair is identified. All you have to do to create a new task is open up a new tab in Energia & instantiate a new & unique setup[keyword] /loop[keyword] pair. Also note that global variables that will be shared across the various tasks should be declared at the top of the first task.
Let's get started!
Houskeeping & creating a new Energia MT project
To keep our firmware nice and tidy, we will create a .ino that will handle our global variables & #defines. This .ino file needs to be your main file & should share the same name as the folder that it will reside in.
We have 2 options for doing our development. We can use Energia MT, which is a downloadable IDE. Or, we can use CCS Cloud, a browser-based cloud-hosted IDE. In this tutorial, I will use CCS Cloud. Let's create a new project!
- Go to CCS Cloud here
- Click on the CCS Cloud icon.
- To use the cloud IDE, you need a myTI account. Register or login.
- Once in CCS Cloud, click on "Project > New Energia Sketch"
- In the dialog box, give your project a name. I'll call mine "myWiFiSensor"
- In the "Device" drop down, select MSP432 & ensure the "MSP-EXP432P401R (48MHz)" appears in other drop down box.
- Select the "Basics > Empty Sketch" template, then click "Finish"
You should now have a new project called "myWiFiSensor" in your CCS Cloud Workspace as shown in the screenshot below.
Now that our project is created, we will use the myWiFiSensor.ino file that we just created to manage all of the libraries & global variables that our multi-tasked application will use.
To start, let's first import the libraries that we will be using in this application. The libraries we need are all core libraries and are included inside of Energia MT & CCS Cloud for the MSP432 LaunchPad.
To import these libraries, copy & paste the code below into the myWiFiSensor.ino file
//myWiFiSensor.ino
//Import Libraries!
#include <SPI.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <aJSON.h>
Great! These libraries will be now accessible to all of the tasks that we are about to create. We will come back to this file & modify it as-needed to add in global variables, any additional libraries, etc that the various tasks will leverage.
A closer look at the software: The tasks in our system
In this system, we will have a total of 9 tasks, all of which will be operating independent of each other. Here's an overview of the tasks that we will create:
- Housekeeping: File for libraries & global variables (myWiFiSensor.ino)
- Task 1: Potentiometer sensor (potentiometer.ino)
- Task 2: Ambient Light Sensor (light.ino)
- Task 3: Moisture sensor (moisture.ino)
- Task 4: Sound sensor/microphone (sound.ino)
- Task 5: Serial Port (Serial.ino)
- Task 6: Buzzer (buzzer.ino)
- Task 7: LED (LED.ino)
- Task 8: JSON-ifier (JSONify.ino)
- Task 9: MQTT Publish (MQTT.ino)
Let's write some code!
Now that we know the tasks, let's go ahead and build our application one task at a time!
Task 1: Potentiometer (potentiometer.ino)
First, let's create a new file in our project by right-clicking on the project & clicking "New File." This will create a generic file called "Untitled. Go ahead and re-name it to "potentiometer.ino".
Pseudo-Code:
Now that we have our new file created, take a look at the pseudo-code below to give you an overview of what this task will be doing:
setupPot(){
- Nothing! No setup required for these analog sensors!
}
loopPot(){
- Read sensor value: Use analogRead(pinNumber) function to read the sensor value & store the reading into a global variable to make the readings accessible to the other tasks
- Go to idle mode: That's pretty much it! Then, we use the delay(milliseconds) function to put this task into idle. The milliseconds parameter will determine the frequency that the global potentiometer sensor value is updated for the other tasks to use.
}
Additions to myWiFiSensor.ino:
Now that you have a better idea of what this task will be doing, let's first modify our myWiFiSensor.ino file by adding in a global variable that we will store our sensor values into.
Copy the bolded code below & add it to myWiFiSensor.ino
//Import Libraries!
#include <SPI.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <aJSON.h>// Globals for storing our latest sensor valuesint pot = 0;
Copy & paste the code below into potentiometer.ino:
void setupPot() {
// put your setup code here, to run once:
}
void loopPot() {
// put your main code here, to run repeatedly:
pot = analogRead(23); // Potentiometer is connected to pin 23
delay(100); // specifies how frequently we will read sensor
}
Task 2: Ambient Light Sensor (light.ino)
- Same as Task 1! We will save this sensor value into another global variable. In addition, we can change the delay time if we want to have different update rates for each sensor.
- Add new global variable for storing light values in myWiFiSensor.ino
//Import Libraries!
#include <SPI.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <aJSON.h>// Globals for storing our latest sensor values
int pot = 0;
int light = 0; Create a new file in your project called light.ino. You can right-click on potentiometer.ino & select "Duplicate". Re-name the file appropriately.
- Copy & Paste the code below into light.ino
void setupLight() {
// put your setup code here, to run once:
}
void loopLight() {
// put your main code here, to run repeatedly:
light = analogRead(24); // Ambient Light Sensor is connected to pin 24
delay(100); // specifies how frequently we will read sensor
}
Task 3: Moisture Sensor (moisture.ino)
- Same as Task 1! We will save this sensor value into another global variable. In addition, we can change the delay time if we want to have different update rates for each sensor.
- Add new global variable for storing moisture values in myWiFiSensor.ino
//Import Libraries!
#include <SPI.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <aJSON.h>// Globals for storing our latest sensor values
int pot = 0;
int light = 0;
int moisture = 0; - Create a new file in your project called moisture.ino. You can right-click on potentiometer.ino & select "Duplicate". Re-name the file appropriately.
- Copy & Paste the code below into moisture.ino
void setupMoisture() {
// put your setup code here, to run once:
}
void loopMoisture() {
// put your main code here, to run repeatedly:
moisture = analogRead(25); // Moisture sensor is connected to pin 25
delay(100); // specifies how frequently we will read sensor
}
Task 4: Microphone Sensor (sound.ino)
- Same as Task 1! We will save this sensor value into another global variable. In addition, we can change the delay time if we want to have different update rates for each sensor.
- Add new global variable for storing sound values in myWiFiSensor.ino
//Import Libraries!
#include <SPI.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <aJSON.h>// Globals for storing our latest sensor values
int pot = 0;
int light = 0;
int moisture = 0;
int sound = 0; - Create a new file in your project called sound.ino. You can right-click on potentiometer.ino & select "Duplicate". Re-name the file appropriately.
- Copy & Paste the code below into sound.ino
void setupSound() {
// put your setup code here, to run once:
}
void loopSound() {
// put your main code here, to run repeatedly:
sound = analogRead(26); // Mic/ Sound Sensor is connected to pin 26
delay(100); // specifies how frequently we will read sensor
}
TASK 5: Serial port! (serial.ino)
Now that we have 4 tasks running in parallel that are reading the 4 sensors & updating 4 separate global variables, let's make sure everything is working!
Let's create a new file called Serial.ino and add it to your project. In this task, we will simply print out the latest values stored in the global variables to the built-in hyper-terminal of CCS Cloud.
Copy & paste the code below into Serial.ino:
void setupSerial(){
Serial.begin(115200); // initialize serial port @115200 baud
}
void loopSerial(){
/*Comment these out when not needed. This will clutter your terminal*/
Serial.print("Pot: "); Serial.print(pot);
Serial.print(" / Light: "); Serial.print(light);
Serial.print(" / Moisture: "); Serial.print(moisture);
Serial.print(" / Sound: "); Serial.println(sound);
delay(100);
}
Build & Flash the code to your hardware!
To compile our multi-tasked application, we simply click on the "hammer" build icon in CCS Cloud. This will send our source code to a cloud server, where it will compile the code and send back to us a binary file. Once the build/compilation is complete, a "green Run" button will light up. Click on this to flash your LaunchPad with the code that we just wrote & compiled!
If you have never used CCS Cloud before, you may get a prompt to download the appropriate drivers and tools to enable your browser to flash code to your LaunchPad. Follow the prompts.
Open up the Terminal to see your data!
CCS Cloud offers an integrated terminal. You can access it by clicking on "Target > Connect COM Port". This will serve up a dialog box. Select the appropriate port (check your computers device manager if you're unsure - look for "UART"), then select the appropriate baud rate. In our code, we set the baud rate to 115200, so select that in the drop down.
Once your COM port is connected, data should start flowing into the Terminal window of CCS Cloud!
Success! Play with the sensors to verify that the values are changing as expected. This verifies that our multi-tasked application is working so far! *hi-five!*
Task 6: Buzzer/audio status update (buzzer.ino)
Let's add a new buzzer task!
Now that we know our sensors are working, let's go ahead and add a buzzer to our system. Just like before, let's create a new file in our project & call it buzzer.ino
The buzzer doesn't require any additional libraries or global variables, so no further modification to myWiFiSensor.ino is needed.
Pseudo-code:
Check out the pseudo-code below to get an understanding of what this task will be doing.
setupBuzzer(){
- Configure Buzzer! Set buzzer pin as output & initialize it so that buzzer is off.
}
loopBuzzer(){
- Check each sensor reading: This task will periodically check the global variables of each sensor against a threshold.
- Buzz buzzer! If any of these global sensor reading variables exceed their respective thresholds, we beep the buzzer!
- Go to idle mode! Use the delay(milliseconds) function to yield to other tasks.
}
The code:
Now that you've seen the pseudo-code, let's go ahead and copy & paste the real code below into buzzer.ino
#define buzzerPin 40
//Set Buzzer thresholds for each sensor here.
#define potThreshold 1000
#define lightThreshold 1000
#define moistureThreshold 1000
#define soundThreshold 1000
void setupBuzzer(){
pinMode(buzzerPin, OUTPUT);
digitalWrite(buzzerPin, LOW);
}
void loopBuzzer(){
if(pot > potThreshold || light > lightThreshold || moisture > moistureThreshold || sound > soundThreshold){
digitalWrite(buzzerPin, HIGH);
delay(50);
digitalWrite(buzzerPin, LOW);
delay(950);
}
delay(100);
}
Now that you've added the buzzer, go ahead and re-compile/flash your device & turn the potentiometer all the way to the right. You should hear the buzzer chirp!
Task 7: RGB LED/visual status update (LED.ino)
This code is almost identical to the buzzer. The only difference is that we are going to change the color of the RGB LED if a threshold is exceeded.
Go ahead and create a new file called LED.ino. Easiest thing would be to duplicate the buzzer.ino file that we just created & rename it to LED.ino.
Here's the pseudo-code:
setupRGB(){
- Configure RGB LED! Set RGB LED pins as output & initialize it so that LED is off.
}
loopRGB(){
- Check each sensor reading: This task will periodically check the global variables of each sensor against a threshold.
- Change color of the RGB LED! If any of these global sensor reading variables exceed their respective thresholds, we will change the color of the RGB LED that is on the MSP432 LaunchPad. RED = one of the sensors have exceeded their threshold / GREEN = all sensors below their threshold!
- Go to idle mode! Use the delay(milliseconds) function to yield to other tasks.
}
And now the real code! Go ahead and copy & paste this into LED.ino
// Set LED Thresholds here
#define potThreshold 1000
#define lightThreshold 1000
#define moistureThreshold 1000
#define soundThreshold 1000
void setupLED(){
pinMode(RED_LED, OUTPUT); // Set LED pins as output
pinMode(GREEN_LED, OUTPUT);
digitalWrite(RED_LED, LOW); // init LEDs to off
digitalWrite(GREEN_LED, LOW);
}
void loopLED(){
if(pot > potThreshold || light > lightThreshold || moisture > moistureThreshold || sound > soundThreshold){
// a threshold is exceeded! RED LED on
digitalWrite(RED_LED, HIGH);
digitalWrite(GREEN_LED, LOW);
}else{
// We're all good! GREEN LED on
digitalWrite(GREEN_LED, HIGH);
digitalWrite(RED_LED, LOW);
}
delay(100);
}
Now that LED.ino has been added to your project, go ahead and re-compile/flash to your device. The LED should be green or red depending on the readings of the various sensors!
Note that we have a different set of #defines for the sensor thresholds here in the LED.ino task. This allows our LED to have a different "sensitivity" level compared to the buzzer task if desired. In this case, their thresholds are the same.
Task 8: JSON-ify latest sensor readings
Let's add a JSON task!
Now that our local alerts are working (buzzer & LED), let's start to package our sensor data so that we can send it remotely to a cloud service. Let's create a new file in our project & call it JSONify.ino
The aJSON library
We will be using the aJSON library to encode our sensor values into an easy-to-digest format, called JSON. You can learn more about the aJSON library here in github.
Pseudo-code:
This will require a few modifications to our project, but first let's take a look at the pseudo-code:
setupJSON(){
- Initialize your Event! This will configure and initialize an event that we will use within the loop function below to signal to another task that the latest & greatest sensor data is encoded & ready!
}
loopJSON(){
- Encode latest global sensor readings into JSON: This task will use the aJSON library that is a core library available in Energia for the MSP432 device. This library allows simple JSON encoding & decoding. Not familiar with JSON? In short, JSON, or JavaScript Object Notation, is a minimal, readable format for structuring data. This will allows our internet connected sensor to organize our sensor readings into a format that will be easy to parse by the cloud (or other connected things)!
{
"d":
{
"myName":"TILaunchPad",
"POT":123, "MIC":254,
"MOISTURE":142,
"LIGHT":221
}
}
- Save JSON to a global variable! Convert the JSON payload to a char array and save it to a global variable that other tasks can leverage.
- Trigger an event! Once the data is encoded, we will trigger an event using Energia MT's new "Event" library to notify other pending tasks that data is encoded & ready!
- Go to idle mode: Again, we will use the delay(milliseconds) function to yield control to other tasks & define the update rate of our JSON.
}
Now that we've seen the pseudo-code, you'll see that we'll need a few things. First, let's modify the myWiFiSensor.ino file to include a few global variables that we will be needing.
Add the bolded code below to our myWiFiSensor.ino file.
The aJSON library we are using requires a serial_stream and a global char array, which will be the global variable that holds the latest JSON-encoded payload that other tasks can read.
//Import Libraries!
#include <SPI.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <aJSON.h>
#include "Event.h"
//Events
Event myEvent;
//JSON Globals
aJsonStream serial_stream(&Serial);
char* jsonPayload;>// Globals for storing our latest sensor values
int pot = 0;
int light = 0;
int moisture = 0;
int sound = 0;
Also notice that we are including a new library, called "Event".
At the time that this tutorial was written, the Event library was not yet integrated into Energia MT as a core library & will have to be included manually. To do so, please add the following files to your project:
Event.cpp
#include "Event.h"
Event::Event(){}
void Event::begin()
{
Error_Block eb;
Error_init(&eb);
eventHandle = Event_create(NULL, &eb);
}
void Event::waitFor()
{
Event_pend(eventHandle, Event_Id_NONE,
Event_Id_00, BIOS_WAIT_FOREVER);
}
void Event::send()
{
Event_post(eventHandle, Event_Id_00);
}
Event.h
#include <Energia.h>
#include <xdc/runtime/Error.h>
#include <ti/sysbios/knl/Event.h>
#include <ti/sysbios/BIOS.h>
class Event {
private:
Event_Handle eventHandle;
static xdc_UInt eventId;
public:
Event();
void begin();
void send();
void waitFor();
};
To add these files to your project, simply right-click on your project & add new file. Rename them to the file names shown above & copy and paste the code into the files.
This library will allow tasks to trigger events that other tasks can wait on.
Let's write the code for our new JSONify.ino task!
Now that we've made the appropriate modifications to the project, let's go ahead and add the JSONify.ino file to our project. Right-click your project & add file. Re-name it to JSONify.ino.
Copy & paste the code below into the JSONify.ino file
/*Create JSON with following format:
{
"d": {"myName" : "TILaunchPad",
"POT" : 123,
"LIGHT" : 221
"MOISTURE" : 142,
"MIC" : 254,
}
}
*///Setup
}
void setupJSON(){
myEvent.begin();
delay(100);
}
void loopJSON(){
aJsonObject *root = aJson.createObject();
aJsonObject *d = aJson.createObject();
aJson.addItemToObject(root, "d", d);
aJson.addStringToObject(d, "myName" , "TILaunchPad"); // add name to JSON
aJson.addNumberToObject(d, "pot" , pot); // add pot to JSON
aJson.addNumberToObject(d, "light" , light); // add light to JSON
aJson.addNumberToObject(d, "moisture" , moisture); // add moisture to JSON
aJson.addNumberToObject(d, "sound" , sound); // add mic data to JSON
jsonPayload = aJson.print(root)+'\0'; // Convert JSON object to char array
// Serial.println(jsonPayload); // Un-comment to print out JSON-encoded data
aJson.deleteItem(d); // this library uses malloc. Free mem.
aJson.deleteItem(root); // this library uses malloc. Free mem.
myEvent.send(); // Trigger event! New JSON payload ready
delay(1000); // Determines how often a new JSON payload is created
Awesome! Now, go ahead and try to compile & flash your updated project to your LaunchPad! Notice the Serial.println(jsonPayload); is commented out. Un-comment it to see your freshly JSON-encoded messages print to the terminal to verify that the library is working properly!
Task 9: Publish latest readings to the cloud via MQTT over Wi-Fi
Let's add the final task! MQTT!
Create another new file & call it MQTT.ino. This task will wait for an event to be triggered & will publish the latest readings to the cloud via MQTT. We will be using the PubSubClient library, which is already imported in our myWiFiSensor.ino file.
Pseudo-code:
Let's take a look at the pseudo code!
setupMQTT(){
- Create wifiReady event. This will notify other tasks once the LaunchPad connects to the internet.
- Connect to Wi-Fi!
- Once connected, trigger the wifiReady event!
}
loopMQTT(){
- Wait for JSON ready event! This task will be waiting for an event to be posted. In this case, we are waiting for the JSON task to send us an event for when new data is formatted & ready to be sent to the cloud!
- Publishing MQTT! We are going to use the PubSubClient library that is a core library available in Energia for the MSP432 device (when paired with the CC3100 Wi-Fi BoosterPack). This library offers APIs for publishing & subscribing to & from the cloud via MQTT. MQTT is a lightweight messaging protocol for IoT & is a good fit for low-power embedded connected devices, such as the one we're building! MQTT is a pub/sub interface & requires a messaging broker to broker/facilitate the many messages. The nice thing about MQTT is that we can have one subscriber, or thousands of subscribers, so it's very scalable that way (where subscribers can be in the cloud, or even another LaunchPad/hardware). In this case, we will publish our JSON-encoded sensor data to a cloud service.
- Go back to idle mode! Since this task is waiting on an event to be posted, it's frequency is dependent on how often new events are triggered by the JSON task. We don't have to call the delay(milliseconds) function here because the task will automatically go to idle mode when it waits for the next event.
}
Let's first modify myWiFiSensor.inoWe need to create a new event called wifiReady. This event will notify to other tasks when we are connected to WiFi. In particular, the JSONinify.ino task will need this because we don't want that task to start triggering an MQTT transaction if we're not yet connected to WiFi. Add the bolded code to myWiFiSensor.ino:
//myWiFiSensor.ino
//Import Libraries!
#include <SPI.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <aJSON.h>
#include "Event.h"
//Events
Event myEvent;
Event wifiReady;//JSON Globals
aJsonStream serial_stream(&Serial);
char* jsonPayload;
// Globals for storing our latest sensor values
int pot = 0;
int light = 0;
int moisture = 0;
int sound = 0;
Let's write some code!
Now that you understand what this task is going to do, let's copy & paste the code below into our MQTT.ino file
char ssid[] = "LaunchPad"; // your network name also called SSID
char password[] = "energia!"; // your network password
char server[] = "iot.eclipse.org"; // MQTTServer to use
WiFiClient wifiClient;
PubSubClient client(server, 1883, callback, wifiClient);int pubCount = 0; // Keep count of number of MQTT publishes we've sent
void callback(char* topic, byte* payload, unsigned int length) {
}
void setup() {
wifiReady.begin(); // initialize wifiReady event.
delay(100);
// attempt to connect to Wifi network:
Serial.print("\nAttempting to connect to Network named: ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while ( WiFi.status() != WL_CONNECTED) { // print dots while we wait to connect Serial.print(".");
delay(300);
}
Serial.println("\nYou're connected to the network");
Serial.println("Waiting for an ip address");
while (WiFi.localIP() == INADDR_NONE) { // print dots! Waiting for ip address
Serial.print(".");
delay(300);
}
Serial.print("\nIP Address obtained: ");
Serial.println(WiFi.localIP());}
wifiReady.send(); // We're connected! Let the JSONify task know!
void loop() {
myEvent.waitFor(); // Wait for next JSON payload event to be triggered
// Reconnect to MQTT Broker if not connected
if (!client.connected()) {
Serial.println("Disconnected. Reconnecting....");
client.connect("MSP432_CC3100");
}
pubCount++; // Keep count of times we publish to the cloud
client.publish("myLaunchPadData", jsonPayload); // PUBLISH!!!
free(jsonPayload); // free the dynamic space allocated for this global variable
Serial.print("MQTT published! ("); Serial.print(pubCount); Serial.println(")");
}
Small modification to JSONinfy.ino
Now, let's make a small modification to the JSONify.ino task so that it won't start to run until the wifiReady event is posted.
Make the following modifications (bolded code) to the setupJSON() code in the JSONify.ino task:
//Setup
void setupJSON(){
myEvent.begin();
delay(100);
wifiReady.waitFor();}
We should be all set!
Go ahead and compile & flash the updated code to your LaunchPad! You should now be publishing JSON-encoded sensor data to the cloud! In the next step, we'll start cloud development to start receiving/subscribing to the data your LaunchPad is publishing over MQTT.
Your LaunchPad is now sending data to the cloud via MQTT & the data is now accessible to other MQTT clients.
THE CLOUD!
Now that we're sending data the cloud, let's build our cloud application. We want our cloud application to enable:
- Visualization of our sensor data
- Trigger events when certain conditions are met (i.e. SMS, twitter, email)
- Datalog when these events happen
Step 1: Spinning up a cloud service for our application with IBM BlueMix
BlueMix is a flexible Platform-as-a-Service (PaaS), which enables developers to quickly spin up servers on the cloud for hosting your application & comes packed with many different services.
- CREATE BLUEMIX ACCOUNT: The first thing you'll need to do is go to www.bluemix.net and create an account. Once you're logged into BlueMix, you will be taken to your dashboard.
- Click on the "CATALOG" tab. There, you will see various BoilerPlates. The one we will want is Node-RED Starter
- Click on Node-RED Starter
- This will provide a simple wizard for creating a new Node-RED-based application. Simply provide your app "Name" & desired "Host" name. This will determine the URL to the cloud-hosted application that is about to be created!
- Click "CREATE" - This will create your new cloud-based application. This may take a few minutes!
- Once your app is running, navigate to the Host URL you specified for your app!
- This will take you to your Node-RED app home page
- Click on the "Go to your Node-RED Flow editor" button
- This will present to you your freshly created, cloud-hosted Node-RED Flow Editor!
Step 2: Let's build an app & start receiving our sensor data in the cloud via MQTT!!
Now that we have our own instance of Node-RED running on our personal sliver of the cloud, let's start to capture our incoming sensor data from the LaunchPad!
- Drag in the "mqtt input block" from the Node-RED palette into the empty sheet.
- Double-click the mqtt block & configure it with the same parameters that your LaunchPad is publishing to:
MQTT BROKER: iot.eclipse.org
PORT: 1883
TOPIC: myLaunchPadData - Once configured, hit OK
- Now, drag in the green "debug" node into your worksheet from the node-RED palette.
- Wire the 2 nodes together! The MQTT input node is subscribed to your LaunchPad, which is publishing its sensor data! By wiring up the debug node, all of the payloads the MQTT subscribe block receives "flows out" and hits the debug node, which causes the payload to be printed to the node-RED debug window!
- Once wired up, hit the "Deploy" button on the top-right. This will cause your new flow/application to start running! Then, switch to the debug tab in the right side-bar.
- Congratulations! Your real-world sensor data is now in the cloud!!!
Step 3: Let's trigger a tweet based on sensor data!
Now that we are receiving data into our node-RED cloud app, let's parse our incoming JSON for a specific data pair, the potentiometer.
- Let's delete our "debug" node for now. Click on it & hit "delete" on your keyboard.
- Drag in the "JSON" node from the palette into your worksheet. Wire up the output of the mqtt node to the json node. This will convert the raw string from the MQTT node into a parse-able JSON object in node-RED.
- Drag in the "switch" node. This will allow us re-route our node-RED flow based on certain parameters. Wire up the output of the "json" node to the input of the "switch" node.
- Double-click the "switch" node & let's name it "if potentiometer..."
- Then, let's parse the incoming JSON for the potentiometer value by typing in:
if msg.payload.d.pot - Let's configure the rule by selecting the desired in-equality & threshold:
> 1000 - This node will only pass on the JSON message if the rule we just configured is satisfied.
- LET'S TEST IT OUT! Drag in a debug node & wire it up to the output of your "if potentiometer" node.
- HIT DEPLOY! Now, we should only be getting messages on the debug window when the potentiometer knob is turned all the way to the right!
- But, we want to send a tweet! Let's drag in a "delay" node from the palette & wire it up to the output of "if potentiometer..."
- Double-click "delay" and let's configure it to the following:Action: Limit Rate to
Rate: 1 msg(s) per minute
[x] drop intermediate messages - This ensures we won't annoy all of our twitter followers with too many tweets!
- Now, let's built our twitter message. Drag in the "function" node from the palette & wire it up to the output of the "delay/limit" node.
- Double-click the "function" node. This will open up a full-blown javascript IDE! Let's place the following code into the code editor:
msg.payload = "ALERT! Your #IoT sensor has exceeded a threshold!";
return msg;
- Click OK. Now, whenever the threshold is exceeded, the message we crafted above will be sent to the twitter node, which we'll configure next....
- Drag in the "twitter" output node from the palette onto the worksheet & connect it to the output of your "function/twitter message" node
- Double click the twitter node & add in your twitter credentials.
- THAT'S IT! Hit Deploy & turn your potentiometer knob all the way to the right! This should trigger the tweet! Be sure to turn your sensor knob back to below the threshold so you don't spam your twitter followers once every minute :)
Step 4: Creating your own REST API for your sensor data
Now that we're receiving our sensor data in the cloud from our application, let's create our own REST API to enable other cloud services to get access to your sensor data! Fortunately, node-RED makes it very easy.
- First, we need to save our latest payload into a global variable inside of node-RED. You can learn more about global variables in node-RED here.
- Drag in a "function" node & wire it up to the output of our existing "json" node. Double-click on the function node & type in the following code into the code editor, then hit OK
context.global.latestMessage = msg;
- The code above will save the latest payload (stored in msg) into a global variable called "context.global.latestMessage". This global variable is now accessible to the rest of your node-RED application/flow!
- Now that our latest payload is continuously saved into the global variable, it's time to create our REST GET API.
- Drag in the "http" input node from the palette into your node-RED worksheet.
- Double-click on it and configure it as follows:
Method: GET
url: /api/latestMessage
Then press OK - Now, drag in a "function" node, then wire it up to the http node. Then, paste in the following code. This will add the global variable to the payload item in msg, then we return "msg" to the next node.
msg.payload = context.global.latestMessage;
return msg;
- Now, drag in the http response node. This doesn't require any configuration. Simply wire it up to the function node that we just created.
- That's it! Go ahead and deploy the application. This API is now live & public!
- TEST IT OUT! Navigate to [yourAppName].mybluemix.net/api/latestMessage in your web browser. At this point you should see the latest JSON coming from your LaunchPad! Hit F5 a few times to see your latest sensor data!
- Now that we have a public API, other apps, web services, or even other LaunchPads can easily pull your latest sensor data!
Step 5: Let's visualize our incoming sensor data!
Now that we have our sensor data available to other services through our REST API, let's build a cloud-side dashboard for visualizing our data! We will be using a free & easy-to-use service called www.freeboard.io
- Navigate to freeboard.io & create a free account.
- Once signed in, create a new dashboard by entering a name in the create field & click "Create New"
- This will take us to a WYSIWYG editor for building our dashboard!
- First, we need to add a datasource, by clicking on the "ADD" button.
- In the datasource type dropdown, select "JSON"
- Enter the following parameters in the various fields:
TYPE: JSON
NAME: myLaunchPad
URL: http://[yourAppName].mybluemix.net/api/latestMessage
TRY THINGPROXY YES [x]
REFRESH EVERY: 1 SECONDS
METHOD: GET
BODY: [blank]
HEADERS: click ADD
NAME: PAYLOAD / VALUE: [blank]
Click SAVE - Now, notice the "Last Updated" timestamp. It is updated every second implying that it is receiving data via our REST API!
- Let's add some widgets! Click on +ADD PANE. At this point, a new page will be added to your dashboard.
- Click on the "+" sign in the pane that we just added. This will allow us to select the type of widget we want. Let's select: "Gauge" & enter the following parameters:
TYPE: GAUGE
TITLE: POTENTIOMETER
VALUE: Click on "+DATASOURCE".
At this point, Freeboard will "live de-code" the JSON. Simply click through & filter down to the sensor value you care about: datasources["myLaunchPad"]["payload"]["d"]["pot"]UNITS: [blank]
MINIMUM: 0
MAXIMUM: 1023
Click SAVE - At this point, the gauge should now be updating with the latest potentiometer values once per second!
- Challenge: Now, add gauges for the other 3 sensors!
- Challenge: Try the other types of widgets (i.e. sparkline, etc)
Comments