Filter results by

Taking Action with ARTIK Cloud

In this article, we'll focus on how the Samsung ARTIK Cloud can analyze data sent by the Samsung ARTIK module over MQTT, and send back appropriate Actions. And when we say "actions", we really mean it! You'll watch ARTIK Cloud trigger significant events.


Prerequisites

In preparation for this tutorial, you must:

The time for completing this installment of the tutorial series is approximately 1 hour.

Choosing Delivery Method

Your application will have several options for communicating with ARTIK Cloud. Here we talk about the trade-offs of using each, and why we chose MQTT. You can skip to Defining Actions if you want to get right to building the demonstration platform.

Overview of Tradeoffs

The table below compares using MQTT protocol to Web REST API protocol, specifically in reference to ARTIK Cloud. We highlight the advantages of each. Note that using Web Sockets, if practical for your application, avoids all of these disadvantages.

Topic MQTT Web REST API
Overhead Minimal data structure size Large nested data structure
Speed Quick Slower than MQTT due to SSL/TLS handshaking over HTTPS
Code complexity Concise, simple code blocks Usual bulky code for Web headers
Archived data retrieval Not possible – must fall back on Web protocol to retrieve from ARTIK Cloud Unified solution for sending messages and retrieving archived ones
Action Notify vs Subscribe Subscribe model delivers Action immediately Notification model requires subsequent follow-up Web call to retrieve Action list
Sending Actions to other devices Cannot be done directly by MQTT clients (can be done indirectly through ARTIK Cloud) Can be done directly by Web REST clients

For our Economy A/C Database demonstration scenario, MQTT is the clear winner – despite its drawbacks. For your own application, you may decide to choose Web protocols, but also consider a hybrid model: Use MQTT for most transactions and Web API REST protocol for data retrieval tasks.

Tradeoffs by Protocol

MQTT

ARTIK Cloud MQTT handling is designed expressly for rapid, responsive service with low overhead costs. Over MQTT, ARTIK Cloud clients may:

  • Send Data messages
  • Subscribe to and receive Action messages directed to their Device ID

but

  • Are not able to subscribe to or receive Data messages.

This is not as great a limitation as it seems. All messages (both Data and Action) are archived by ARTIK Cloud and can be retrieved by the client on demand using Web protocols. Sign in and check the data logs page, and you will see the messages that have accumulated from your testing so far.

Web REST API

Through the ARTIK Cloud API, Web REST clients do have a way to "subscribe", but it differs from MQTT in that only a notification, not the Action message, is delivered. Once notified, the client can request messages (including Actions) that are associated with that notification. Refer to the API Specification and to Subscribe and Notify for details.

Web REST clients can at any time retrieve any messages (including Actions) that have been archived on ARTIK Cloud. That is, subscription is not required if polling is the preferred option, and not subscribing does not prevent received messages from being archived.

WebSockets

ARTIK Cloud clients can use the WebSocket interface for all the same things as the Web REST API. In addition, this method offers a live and always-available feed of activity and data, so there are no limitations on notifications or response times. WebSockets are typically associated with applications needing greater throughput than required for ours, but you may want to consider them for yours. They are not addressed in this tutorial.

Tradeoffs by Data Direction

Our strategy is to use MQTT as our primary method of communication with ARTIK Cloud. By doing so, we are saying that it is sufficient to have only two transaction types:

  • ARTIK Module –> ARTIK Cloud : Data only
  • ARTIK Cloud –> ARTIK Module : Actions for module code to carry out

We describe below how outgoing and incoming payloads are handled in general, and within that context, how our strategy of choosing MQTT will play out.


Outgoing Payloads

Regardless of protocol, payloads that ARTIK module code might want to send to ARTIK Cloud can contain Data or an Action.

  • Actions sent are executed by ARTIK Cloud as specified in the payload. MQTT clients are not allowed to deliver Action messages to ARTIK Cloud; only Web clients may.
  • Data sent is archived by ARTIK Cloud, which also analyzes the data using Rules you have set up and generates Actions called for.

Because ARTIK Cloud can analyze and act on data content, the inability of MQTT clients to send Actions is not a limitation in most cases. The ARTIK Cloud mechanism for defining and enabling Actions it takes based on data received is found at the Rules page of My ARTIK Cloud. Go there now and try it out – you'll find it easy and quite intuitive.

In our scenario, we do not need to send Actions. We are counting on ARTIK Cloud Rules to generate Actions, both to the ARTIK module and to other devices, based on:

  • Home control and sensor data that the ARTIK module sends to ARTIK Cloud
  • Data and Actions that the power company sends to ARTIK Cloud.

In other words, we are depending on the intelligence of ARTIK Cloud to interpret data and send Actions on our behalf. This approach enables us to solely employ MQTT for our outgoing payload needs.


Incoming Payloads

Regardless of protocol, payloads the ARTIK module code might want to receive from ARTIK Cloud may contain Data or an Action.

  • Actions, possibly originating from other devices, explicitly direct the ARTIK module to do something. Only MQTT clients receive these directly. Web clients receive them only when they retrieve them as part of archived messages.

  • Data can only arrive by way of Web protocols. ARTIK Cloud clients cannot retrieve or subscribe to data messages via MQTT.

As an ARTIK Cloud MQTT client, the ARTIK module subscribes to messages intended for its Device ID. In our scenario, we need to receive Actions – directly from ARTIK Cloud, and indirectly from others by way of ARTIK Cloud Rules. Subscribing to them with MQTT is the fastest and most efficient way to get Actions, which the ARTIK module can then execute rapidly.

Our application is not retrieving data from ARTIK Cloud, so MQTT is sufficient for our incoming payload needs. You will have to decide whether you would need data retrieval, and therefore Web protocols, based on your own application requirements.

Defining Actions in ARTIK Cloud

Let's review our demonstration scenario, using the Economy A/C Database manifest.

This scenario is ideally suited to the MQTT-only method described above.

  • Data and controls are all managed locally by the ARTIK module.
  • Relevant data is sent to the power company for archiving and analysis.
    ARTIK Module –> ARTIK Cloud : Data only

  • The power company sends back actions to influence electric grid loading if needed.
    ARTIK Cloud –> ARTIK Module : Actions only

Specifically, the power company wants to establish a small set of Actions it can send on very hot days.

Action ID Description Priority
Pre-cool house actionPrecoolHouse Request to cool down house early in anticipation of a warm afternoon Low
Pre-cool refrigerator actionPrecoolRefrig Request to run food cooling appliances ahead of expected heat peak Low
Set thermostat actionSetLowTemp Request to limit coolest setting on thermostat Medium
Control A/C compressor actionStopAC Urgent need to immediately cycle off compressor to avoid imminent blackout High

In our scenario, we envision most of these requests to be under homeowner control. "Priority", from the power company perspective, means the following.

  • Low: Customer can pre-configure their local system to prevent the request from being acted on (if, say, pre-cooling the refrigerator would ruin food for a person with special dietary needs).
  • Medium: Customer can override the requests on a per-instance basis (for example, if the thermostat is blocked from being set below 75 degrees, the homeowner can hold the setting buttons for 10s to override).
  • High: Customer is not allowed to override the Action.

While MQTT protocols offer Quality of Service levels to check for payload arrival, these alone would never be enough to guarantee that a High Priority Action was acted on. Therefore, it is up to your application to verify that such commands are carried out.

ARTIK Cloud delivers Actions with QoS 0 only. But even QoS 1 or 2 would not guarantee that a MQTT payload was not lost due to connectivity issues. While delivery loss is inconvenient, the harm is not irreparable. Source data and Actions can always be retrieved by polling ARTIK Cloud.

Decide Data and Actions of Interest

For our demonstration tests, our outgoing data of interest will be insTemp and outTemp. Our incoming Actions of interest will be actionSetLowTemp and actionStopAC. Here are the Rules we want to put in place.

  • When outTemp exceeds 40 C (104 F), the power company wants ARTIK Cloud to send a setTemperature message with a parameter of actionSetLowTemp and a value of 24 C (75 F).

  • When insTemp drops below 15.5 C (60 F), the power company wants ARTIK Cloud to stop the air conditioning by sending a setOff message with a parameter of actionStopAC.

  • In a pending emergency blackout situation, the power company wants to directly command an Action via ARTIK Cloud to immediately shut off the A/C compressor unit using the same message as above.

Modify Manifest

To give your manifest new capabilities, use the same approach you did when setting it up originally, but this time select Actions.

  1. Go to the ARTIK Cloud Developer Dashboard and log in.

  2. Click on

     
    ^ then select Economy A/C Database
  3. Click on

    + New Version
    ^
  4. Click on

    + Device Actions
    ^
  5. Click through steps a–d below for setTemperature with a parameter of 'actionSetLowTemp' and type 'Double'.

    + New Action
    a. ^
    Browse Standard Actions   Save
    b. ^ then select setTemperature ^
    + New Parameter  
    c. ^ then select actionSetLowTemp
    Data Type   Save
    d. ^ then select Double ^
  6. Repeat steps a–d, this time for setOffwith a parameter of actionStopAC and type Boolean.

  7. Click Next: Activate Manifest.

  8. Click the list item that looks like this to get back to where you left off.

You have now added Actions to your project. All you have left to do on ARTIK Cloud is to define and activate the Rules needed to trigger these Actions, which we'll do in the Taking Action section after we have written code on the ARTIK Module side to receive and process Actions.

Assembling the Edge Devices

Since our demonstration scenario is for home air conditioning, the test platform we have in mind represents the edge device, temp sensor, and A/C compressor unit. We will base this on a second ARTIK development board, which we will call the remote board.

We will also use the Photon device from the previous tutorial to control our shades, so that they open and close automatically depending on outside temperature.

Components

To have a little fun with our demonstration device, we modeled it on an outdoor air conditioner compressor unit and gave it sound effects. You can duplicate this, or can simply use an LED and earphones.

GEARHEAD speaker + Little Bits 5V Fan = air conditioner

The sound effects serve a practical purpose, demonstrating how to write Arduino code to trigger Linux® command line operations (in this case, playing an audio file). In a future tutorial, we will also use this setup to demonstrate Bluetooth® audio.

Connections

The following instructions refer to connecting the components to the remote ARTIK board. Power off while connecting.

  • Speaker: Plug into ARTIK board audio output jack (charge with USB before using, then leave USB unconnected and put switch to AUX position).
  • Photoresistor and resistor: Connect as on the original ARTIK board (like this).
  • Fan: Attach GND to ARTIK GND, Vcc to ARTIK +5V, SIG to GPIO pin 2.

You may also want to connect a resistor (we chose 220 ohm) between SIG and GND. Otherwise, the signal floats and causes the fan to "flutter" while idle.

Coding / How It Works

As in previous tutorials, we supply insTemp readings from the primary ARTIK board. However, we will now use the remote ARTIK board outside to monitor and transmit outTemp readings and to control our improvised air conditioner. A Web-page control panel driven by Node-RED will display the data and provide threshold settings.

In this tutorial, we will scale the readings to make them a little more like real Fahrenheit temperatures (even though they will still come from photosensors instead of temperature sensors for quicker response). We will also name the published topics appropriately (for example, the 'brightness' topic we used for the Photon now changes to 'outTemp' for the ARTIK board).

Preparation

Here we will be referring to the main and remote ARTIK boards and associated code.

  1. Connect both ARTIK boards to the host PC, running separate USB cables and terminal emulator instances to each.
  2. Connect each board to Wi-Fi® through the usual dhclient wlan0 sequence, and check for correct connectivity.
  3. Launch Mosquitto on the main board.
  4. Copy three sound effect files by right-clicking each of these links and saving to any convenient temporary location on your PC: ac.wavac-run.wavac-off.wav
  5. Use scp to load the sound effects files to the root directory on the remote board.

Main ARTIK Controller Arduino Code

The Arduino code for the main board is provided below. It is similar to that developed in the previous tutorial but does not use Web calls. The code creates two MQTT clients: one to ARTIK Cloud (SSL/TLS-secured) and a separate one to Mosquitto (local and unsecured).

Improvements

We added a getIPAddress routine to simplify setup. Now, the Arduino code just reads the localhost address directly from an ifconfig wlan0 statement, so you need not specify the mqttServer address. To implement this, we use shell commands, which we'll talk about in the "remote" code section below.

We also needed a parseBuffer routine, sort of the inverse of the loadBuffer routine from previous tutorials, to deconstruct the incoming json payloads from ARTIK Cloud. You'll see an example in the Taking Action section of what those messages look like. It might help you understand this routine better.

Setup Instructions

  1. Copy the code below to the Arduino IDE, replacing Device ID in three places and Device Token in one place.
  2. Pick the correct network under Arduino Tools->Port.
  3. Compile and load the code to your main ARTIK board through the Arduino IDE.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#include <WiFi.h>
#include <MQTTClient.h>
#include <ArduinoJson.h>
#include <DebugSerial.h>

//-------------
// ARTIK Cloud MQTT params
char mqttCloudServer[]     = "api.artik.cloud";
int  mqttCloudPort         = 8883;
char mqttCloudClientName[] = "ARTIK-Arduino";
char mqttCloudUsername[]   = "_your_DEVICE_ID_goes_here_"; // FIX
char mqttCloudPassword[]   = "_your_DEVICE_TOKEN_goes_here_"; // FIX
char mqttCloudDataOut[]    = "/v1.1/messages/_your_DEVICE_ID_goes_here_"; // FIX
char mqttCloudActionsIn[]  = "/v1.1/actions/_your_DEVICE_ID_goes_here_"; // FIX

WiFiSSLClient ipCloudStack;
MQTTClient mqttCloudClient;

//------------
// Mosquitto local MQTT params
char mqttServer[16]; //ip address acquisition is automatic now
int  mqttPort         = 1883;
char mqttClientName[] = "ARTIK-Master Indoor";
float insTemp, desTemp = 80, outTemp, insTempPrev = 0;
bool compOnCtl = false;
int bufLength = 0;
int scalingFactor;
int debounce = 0;

char buf[128];

WiFiClient ipStack;
MQTTClient mqttClient;

void setup() {

  DebugSerial.begin(115200);

  getIPAddress(); //automatic now

  scalingFactor = analogRead(0) / 37.5;

  mqttCloudClient.begin(mqttCloudServer, mqttCloudPort, ipCloudStack);

  DebugSerial.println("start ARTIK Cloud connect");
  int rc = mqttCloudClient.connect(mqttCloudClientName, mqttCloudUsername, mqttCloudPassword);
  DebugSerial.print("connect result:");
  DebugSerial.println(rc);
  mqttCloudClient.subscribe(mqttCloudActionsIn);

  mqttClient.begin(mqttServer, mqttPort, ipStack);

  if (!mqttClient.connected()) {
    mqttClient.connect(mqttClientName);
    delay(500);
    mqttClient.subscribe("outTemp");
    mqttClient.subscribe("desTemp");
    mqttClient.subscribe("insTemp");
  }
}

void loop() {
  mqttClient.loop();
  mqttCloudClient.loop();
  delay(100);
  DebugSerial.print(debounce);
  DebugSerial.print("*");

  insTemp = 110 - (analogRead(0) / scalingFactor); // from ARTIK photosensor
  if (insTemp < 50) insTemp = 50;

  if (insTemp > insTempPrev + 2 || insTemp < insTempPrev - 2) {
    sprintf(buf, "%4.0f", insTemp);
    mqttClient.publish("insTemp", buf);
    insTempPrev = insTemp;
  }
  if (insTemp > desTemp) {
    if (debounce++ > 7)
    {
      debounce = 12; //countdown value
      if (compOnCtl == false) {
        compOnCtl = true;
        mqttClient.publish("compOnCtl", "true");
        DebugSerial.print("!");
      }
    }
  } else { // insTemp must be <= desTemp
    if (debounce-- <= 0) {
      debounce = 0;
      if (compOnCtl == true) {
        compOnCtl = false;
        mqttClient.publish("compOnCtl", "false");
        DebugSerial.print("X");
      }
    }
  }
}

void getIPAddress() { //figure out IP address from ifconfig
  int indx = 0;
  Process p;
  p.runShellCommand("ifconfig wlan0 | grep 'inet ' | awk '{ print $2 }'");
  while (p.running());
  while (p.available()) {
    char c = p.read();
    mqttServer[indx++] = c;
  }
  mqttServer[indx - 1] = '\0';
  DebugSerial.print("wlan0 IP address:");
  DebugSerial.println(mqttServer);
}

void messageReceived(String topic, String payload, char * bytes, unsigned int length) {
  String s = payload;// convert payload to float
  float temp = s.toFloat();

  if (topic == "outTemp") { // from remote sensor
    outTemp = temp;
    sendToArtikCloud(insTemp, desTemp, outTemp);
  }
  else if (topic == "insTemp") { // from internal analog read
    insTemp = temp;
    sendToArtikCloud(insTemp, desTemp, outTemp);
  }
  else if (topic == "desTemp") { // from Node-RED slider
    desTemp = temp;
    insTempPrev = 0; // force trigger of new sensing round
    DebugSerial.print("desTemp:");
    DebugSerial.println(desTemp);
    sendToArtikCloud(insTemp, desTemp, outTemp);
  }
  else {
    DebugSerial.print("Action:"); //
    parseBuffer(payload);
  }
}

void sendToArtikCloud(float insTemp, float desTemp, float outTemp) {
  loadBuffer(insTemp, desTemp, outTemp); // load current values into the buffer
  mqttCloudClient.publish(mqttCloudDataOut, buf);
}

void loadBuffer(float insTemp, float desTemp, float outTemp) {
  StaticJsonBuffer<200> jsonBuffer;
  JsonObject& dataPair = jsonBuffer.createObject();

  dataPair["insTemp"] = insTemp;
  dataPair["desTemp"] = desTemp;
  dataPair["outTemp"] = outTemp;

  dataPair.printTo(buf, sizeof(buf));
}

void parseBuffer(String _payload) {
  StaticJsonBuffer<200> jsonBuffer;
  String json = _payload;
  JsonObject& root = jsonBuffer.parseObject(json);
  const char* nameparam = root["actions"][0]["name"];
  const char* actionSetLowTemp = root["actions"][0]["parameters"]["actionSetLowTemp"];
  const char* actionStopAC = root["actions"][0]["parameters"]["actionStopAC"];

  DebugSerial.print(nameparam);
  DebugSerial.print(actionSetLowTemp);
  DebugSerial.println(actionStopAC);

  if (strcmp(nameparam, "setTemperature") == 0) {
    desTemp = String(actionSetLowTemp).toFloat();
    mqttClient.publish("desTemp", actionSetLowTemp);
  }
  if (strcmp(nameparam, "setOff") == 0) {
    compOnCtl = false;
    mqttClient.publish("compOnCtl", "false");
  }
}

Remote A/C Unit ARTIK Controller Arduino Code

For the outdoor temp sensor and A/C Unit Controller, we require only a tiny amount of code to publish outTemp and to subscribe to receive compOnCtl commands.

Improvements

Note the Process class runShellCommand operations. They bring the convenience of Linux command line operations into our Arduino sketch. Using them here, we:

  • Play a "sound effect" file each time we receive a control command over MQTT telling us to turn the compressor on or off.
  • Run a script to read and figure out who our local MQTT broker is automatically, eliminating the need to manually change the IP address in the sketch.

The curl command string may look strange, but it's just trying to open the MQTT port at each IP address to see if it gets a "connected" state. Only the address of the MQTT broker will return this state successfully, with a line beginning with an asterisk (*).

Try copying the command strings themselves to your Linux prompt to run them and see how they work. Once you see how easy it is, we're sure you will find many additional ways to use runShellCommand routines.

Setup Instructions

  1. Copy the code below to the Arduino IDE.
  2. Pick the correct network (the other one this time) under Arduino Tools->Port.
  3. Compile and load the code to your remote ARTIK board through the Arduino IDE.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// THIS SKETCH IS FOR STANDALONE ARTIK ACTING AS OUTDOOR TEMP SENSOR / AC COMPRESSOR CONTROLLER

#include <WiFi.h>
#include <MQTTClient.h>
#include <DebugSerial.h>

//-------------
// Mosquitto local MQTT params
char mqttServer[16];
int  mqttPort         = 1883;
char mqttClientName[] = "ARTIK-Remote A/C";
float  outTemp, outTempPrev = 0;
bool compOnCtl = false;
int retryCount = 0;
int bufLength = 0;
int scalingFactor;
FILE * ipFile;
FILE * checkFile;

char buf[128];

WiFiClient ipStack;
MQTTClient mqttClient;
Process p;

void setup() {

  int rc = 0;
  DebugSerial.begin(115200);
  pinMode(2, OUTPUT);
  scalingFactor = analogRead(0) / 75;
  if (scalingFactor < 5) scalingFactor = 5; // avoid div by 0 later
  
  // this command saves list of possible ip addresses
  Process ip;
  String ipcmd = "ip neigh | grep 'lladdr' |  grep -F '.' | awk '{ print $1 }' > ipneighbors";
  ip.runShellCommand(ipcmd);
  ipFile = fopen("ipneighbors", "r");

  //this section tries each neighboring address to see if port 1883 is open
  //if curl can connect, it's an MQTT broker
  int checkSize = 0;
  char curlcode[20];
  do {
    fgets(mqttServer, 20, ipFile);
    DebugSerial.print("Trying:");
    DebugSerial.println(mqttServer);
    char * e = strchr(mqttServer, '\n'); //search for CR
    if (e == NULL) {
      DebugSerial.println("No MQTT broker found!");
      exit(0);
    }
    int eolPosn = ((int)(e - mqttServer));
    mqttServer[eolPosn] = '\0'; //replace with EOL

    String cmdline = "curl -vs --connect-timeout 2 http://" + String(mqttServer) + ":1883 2>&1 | grep 'Connected' > out";
    ip.runShellCommand(cmdline);

    //If the word "connected" isn't found then it's not an MQTT broker port
    checkFile = fopen("out", "r");
    fgets(curlcode, 12, checkFile);
    fclose(checkFile);
  } while (curlcode[0] != '*');
  fclose(ipFile);
  mqttClient.begin(mqttServer, mqttPort, ipStack);
}

void loop() {
  if (!mqttClient.connected()) {
    DebugSerial.println("reconnecting");
    mqttClient.connect(mqttClientName);
    delay(500);
    if (mqttClient.connected()) {
      retryCount = 0;
      mqttClient.subscribe("compOnCtl");
    } else retryCount = retryCount + 1;
    if (retryCount > 10)
      mqttClient.disconnect();
  } else {
    mqttClient.loop();
    delay(100);
  }

  outTemp = 150 - (analogRead(0) / scalingFactor); // from ARTIK photosensor
  if (outTemp < 0) outTemp = 0;

  if (outTemp > outTempPrev + 2 || outTemp < outTempPrev - 2) {
    sprintf(buf, "%4.0f", outTemp);
    mqttClient.publish("outTemp", buf);
    outTempPrev = outTemp;
    if (outTemp < 40) {
      digitalWrite(2, LOW); // turn fan off
      p.runShellCommand("aplay ac-off.wav --nonblock --quiet");
    }
    if (outTemp > 80)
      mqttClient.publish("color", "RED");

    DebugSerial.print("*");
    DebugSerial.print(buf);
  }

  if (compOnCtl == true) {
    if (p.running() == false)
      p.runShellCommandAsynchronously("aplay ac-run.wav --nonblock --quiet ");//foreground
    mqttClient.publish("color", "GREEN");
  }
  
}

void messageReceived(String topic, String payload, char * bytes, unsigned int length) {
  if (topic == "compOnCtl") { // command from main controller
    DebugSerial.print("\ncompOnCtl:");
    DebugSerial.println(payload);
    if (payload[0] == 't') { // true
      if (compOnCtl == false) {
        digitalWrite(2, HIGH); // turn fan on
        p.runShellCommand("aplay ac.wav --nonblock --quiet &");
        compOnCtl = true;
        DebugSerial.println("*ON*");
      }
    } else if (payload[0] == 'f') { // false
      if (compOnCtl == true) {
        while (p.running()); // wait for prior audio to complete
        digitalWrite(2, LOW); // turn fan off
        p.runShellCommand("aplay ac-off.wav --nonblock --quiet &");
        compOnCtl = false;
        DebugSerial.println("*OFF*");
      }
    }
  }
}

Particle Photon Code

As an example of how the window shades might be conveniently controlled, we use the Particle Photon with its code pretty much unchanged from previous tutorials. We just send a "RED" command when outdoor temperatures exceed 80 F, and send a "GREEN" command once the air conditioner has been turned on. If you have actual hardware available to control, try some experiments with the Photon GPIO pins!

Setup Instructions

Nothing changes for the Photon setup. We haven't implemented the automatic IP address code for the Photon, so don't forget to change it manually to match that of the MQTT broker.

As soon as you apply power to the Photon, you should see a 'connect' message from Mosquitto.

Node-RED Code

For our Node-RED code, the most significant change comes with introduction of context variables, replacing the copy-to-file scheme we used previously. We also made the control panel much nicer.

Setup Instructions

Make sure you have Mosquitto running.

  1. Launch Node-RED on your ARTIK board as you would normally do.
  2. Open your Node-RED browser window at localhost:1880/# as usual.
  3. Right-click on this flow file link to save the complete set of nodes.
  4. Open the file in your text editor to replace the IP address entry after "broker": with your localhost address, then save and copy the text to your clipboard.
  5. Import it to Node-RED through the Clipboard.
  6. Click Deploy and check that all MQTT nodes are connected.
  7. Open another browser window at localhost:1880/mcp to display the gauges and slider control.

How it Works: Function Node Code

Node-RED allows assignment of node variables that persist between invocations. The variable can be accessible just by that Function node (context), by all Function nodes on the page (flow), or by all Function nodes across all pages (global). We only need flow level persistence for this exercise.

If you open any one of the function nodes connected to the MQTT input nodes, you will see code like:

1
flow.set('insTemp',msg.payload);

where we have assigned the MQTT payload to a flow-wide variable called insTemp.

Now open the Function node connected to the HTTP [get] /data node. The code creates a nested /data object that can be sent to the HTML page generated by our Template node.

1
2
3
4
5
6
7
var data = {
    insTemp: parseInt(flow.get('insTemp')),
    outTemp: parseInt(flow.get('outTemp')),
    desTemp: parseInt(flow.get('desTemp'))
}
msg.payload = data;
return msg;

How it Works: Template Node HTML Code

The Template HTML code is significantly improved. We incorporate gauges from JustGage to show the incoming published sensor values. The layout is more mobile-friendly so that you can use your Wi-Fi-enabled cell phone for monitoring and control.

To feed the gauges, we take the incoming /data object and parse it into obj, whose insTemp, desTemp, and outTemp elements we can access individually. If we had tried to access the context variables directly by using a reference like {{insTemp}}, they would not be updated dynamically. By contrast, the /data object gets resent to the page every time function myTimer() executes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
<html>

<head>
  <script src="http://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.4/raphael-min.js"></script>
  <script src="http://cdn.jsdelivr.net/justgage/1.0.1/justgage.min.js"></script>

  <style>
    html {
      font-family: Arial;
    }
  </style>
</head>

<body>
  <table width="250">
    <tr>
      <td>Temp Setting: </td>
      <td id="valGreen">80</td>
    </tr>

    <tr>
      <td width="75%"><input onchange="changeGreen(this.value)" type="range" id="slideGreen" value="80" min="60" max="90" style="width:90%"></td>
      <td><div id="change" style="height:35px"></div></td>
    </tr>

    <tr>
       <td id="acIcon"></td>
       <td><div id="changetxt"></div></td>
    </tr>

    <tr>
       <td colspan=3 id="gauge2" class="200x140px"></td>
    </tr>

    <tr>
       <td colspan=3 id="gauge" class="200x160px"></td>
    </tr>
  </table>

  <table>
    <tr>
      <td align="center" style="width:70%">Manual AC Control </td>
      <td> <button onclick="acOn()">ON</button>
      <button onclick="acOff()">OFF</button> </td>
      <td></td>
    </tr>
  </table>

  <script>
    function acOn() {
      console.log('AC on');
    }
    function acOff() {
      console.log('AC off');
    }

    var x;
    var iconColor = 'Black';
    var myVar = setInterval(myTimer, 500);

    function myTimer() {
      // Get incoming data from MQTT nodes as part of "data" object
      var obj;
      var site = new XMLHttpRequest();

      site.open("GET", "/data", true);
      site.send();

      site.onreadystatechange = function() {
        if (site.readyState == 4 && site.status == 200) {
          obj = JSON.parse(site.responseText);

          console.log(obj);

          // Refresh the gauges
          g2.refresh(obj.insTemp);
          g.refresh(obj.outTemp);

          // Refresh the slider area
          document.getElementById('slideGreen').value = obj.desTemp;

          var valGreen = parseInt(document.getElementById('valGreen').innerHTML);

          if (obj.desTemp != valGreen) {
            document.getElementById('change').style.backgroundColor = "rgb(255," + obj.desTemp + ", 0)";
            document.getElementById('changetxt').innerHTML = "Action:" + obj.desTemp;
          } else {
            document.getElementById('change').style.backgroundColor = "rgb(0," + Math.floor(obj.desTemp * 2.5) + ", 0)";
            document.getElementById('changetxt').innerHTML = obj.desTemp;
          }

          if (obj.compOnCtl == "true") {
            if (iconColor=='Black') {
              document.getElementById('acIcon').innerHTML = "<font color='Gray' size='24'><b>*</b></font>";
              iconColor='Gray';
            } else {
              document.getElementById('acIcon').innerHTML = "<font color='Black' size='24'><b>*</b></font>";
              iconColor='Black';
            }
          } else {
            document.getElementById('acIcon').innerHTML = "<font color='LightGray' size='24'><b>*</b></font>";
          }
        }
      }
    }
    </script>

    <script>
      var g = new JustGage({
        id: "gauge",
        value: 70,
        min: 0,
        max: 150,
        title: "Outside Temp (F)"
      });

      var g2 = new JustGage({
        id: "gauge2",
        value: 70,
        min: 50,
        max: 100,
        title: "Inside Temp (F)"
      });
    </script>

    <script>
      changeAll();

      function changeGreen(value) {
        document.getElementById('valGreen').innerHTML = value;
        changeAll();
      }

      function changeAll() {
        var g = document.getElementById('valGreen').innerHTML;
        var r = 0;
        var b = 0;

        // Send out new slider setting
        var MQTTsite = new XMLHttpRequest();
        MQTTsite.open("POST", "/mcp", true);
        MQTTsite.setRequestHeader('Content-Type', 'application/json');
        MQTTsite.send(JSON.stringify({
          "sliderSetting": g
        }));
      }
  </script>

</body>
</html>

Final Setup Checklist

Up to this point, you should have set up the following.

Main ARTIK Board

  • Photosensor (insTemp) still in place from previous tutorial
  • Load Mosquitto and Node-RED
  • Load Arduino program to communicate with remote board, Node-RED, and ARTIK Cloud, and to read sensor
  • Ensure Device ID (2x) and Device Token are correct.

Remote ARTIK Board

  • Add photosensor (outTemp) same as on first board
  • Connect fan (or LED) and speaker
  • Load Arduino program to read sensor, communicate with first board, and control model A/C unit
  • Load sound effects .wav files.

Particle Photon Board

  • Check and make sure IP address is correct.

Node-RED Browser Windows

  • Deploy main flow from localhost:1880/#
  • Bring up Web control panel at localhost:1880/mcp

Now comes the easy part: Programming ARTIK Cloud with Rules.

Taking Action

With our infrastructure ready, it's a simple matter to teach ARTIK Cloud the rules of when and why it should send Actions.

Set Up Rules

Rules are set up through My ARTIK Cloud (not the Developer Dashboard).

  1. Go to My ARTIK Cloud and log in.

  2. Click on

    My ARTIK Cloud  
    ^ then select Rules
  3. Click on

    + New Rule
    ^
  4. Set up Rules as follows.

    • IF Economy A/C Database outTemp is more than 104 and desTemp is less than 75 THEN send to Economy A/C Database the action setTemperature with actionSetLowTemp = 75‌
    • IF Economy A/C Database insTemp is less than 60 THEN send to Economy A/C Database the action setOff with actionStopAC = true‌

That's it! That's all there is to creating Rules to make Actions take place.

Test 1: Turn on the Air Conditioner

The slider on the Web display panel stands in for the homeowner's thermostat temperature setting control. Let's try it out.

  1. Slide the temperature setting to the left.
  2. Verify that the sound effect is triggered and the fan turns on.
  3. Slide the control back and forth to check it out thoroughly.
  4. Finish up by moving the control to the left so that the fan stays on.

Note in the code that the displayed temperature setting is returned through the MQTT path. If you open a second instance of this Web page on a mobile device, you will see that both reflect the settings of each other.

Test 2: Check the AC Compressor Cycling

Let's verify that the ARTIK Cloud compressor shutdown rule is having the desired effect.

To test the rule: Verify the Disable AC Compressor rule by using the mouse to hover over the rule to make its Test button appear, then click it.

> Test
^

You can see that the Compressor on the outside temp controller board has been turned off. The message coming from ARTIK Cloud looks like this.

  {"actions":[{"name":"setOff","parameters":{"actionStopAC":true}}]}

The main ARTIK module receives this action and sends out the local message

  mqttClient.publish("compOnCtl","false");

To restore operation: Slide the temperature control to a higher temperature, and then to a lower one again.

To verify real operation: We need to "fool" our stand-in temperature sensor into returning a "cold" value. Shine a light on the photosensor – when it gets below 60 F, you should see the Rule from ARTIK Cloud take effect.

Test 3: Close the Blinds

To represent possible operation of controlled blinds, we have the Particle Photon set to change color when the outside temperature goes up or the AC turns on. You could easily connect its GPIO pins to a relay to control motorized drapes or blinds in the house.

  1. Wave your hand over the outdoor temperature sensor (remote ARTIK board).
  2. Verify that the red LED comes on.
  3. Slide the temperature control so that the A/C turns on.
  4. Verify that the green LED comes on.

Test 4: Check the Thermostat Limit

Let's make sure automatic indoor temperature limiting is applied when outside temperatures get high.

  1. Slide the control and set Desired Temp to 68 F (or thereabouts).
  2. Hold your hand over the remote temperature sensor board to simulate an increase in temperature. Watch the Web console outdoor temperature indicator.
  3. When the outdoor temp gets above 104 F, notice the slider. You should see that it has reset to 75 F.

Summary

You have seen how a small amount of MQTT code can support a sophisticated application, easily handling the typical needs of a complex IoT solution. You set up Actions from ARTIK Cloud to interact with the local code – and it was not all that difficult!

We hope you noticed how minor a task it is to make real "actions" take place. You can play an audio or video clip, trigger recording of one, or do a hundred other things, just as simply as you can toggle an I/O pin. So why settle for just bare minimum functionality in your project?


Coming up next: We take care of some unanswered questions.

  • Our current design used a sensor link that, at most, you password-protected for security. However, it was not SSL/TLS-secured. A determined hacker could eventually break it. That sensor data – temperature readings – is of seemingly little value for a hacker to exploit. But what if an evil-doer could inject false readings throughout the neighborhood to the point where it triggers a power grid overload?

  • Compiling and loading an Arduino sketch is pretty straightforward, but as you saw here, it got pretty complicated to do it with two boards. Then there's the matter of loading Mosquitto and Node-RED. Wouldn't it be nice to have them all self-load on reset?

  • Connecting to Wi-Fi sometimes seems hit-and-miss. You may have noticed that our remote board code attempts to reconnect when its connection is lost. How can we extend that ease-of-use and reliability across all devices in our IoT network?


Last updated on: