Difference between revisions of "MQTT: Arduino Simple Read Write"

From OnnoWiki
Jump to navigation Jump to search
(Undo revision 47817 by Onnowpurbo (talk))
Line 21: Line 21:
 
This simple sensor application gathers light sensor data which is published to the broker.  Commands can be sent via MQTT to the sensor.
 
This simple sensor application gathers light sensor data which is published to the broker.  Commands can be sent via MQTT to the sensor.
  
Arduino - Hardware
+
==Arduino - Hardware==
  
 
The circuits I constructed allow the Arduino device to read light levels using an analog input and (optionally) control an LED based on a set light level.  The optional LED portion of the circuit makes the Arduino a very simple control system which controls an output based on input values it senses.
 
The circuits I constructed allow the Arduino device to read light levels using an analog input and (optionally) control an LED based on a set light level.  The optional LED portion of the circuit makes the Arduino a very simple control system which controls an output based on input values it senses.
Line 29: Line 29:
 
Parts List
 
Parts List
  
    Arduino Device (in this example an Uno)
+
* Arduino Device (in this example an Uno)
    Arduino Ethernet Shield
+
* Arduino Ethernet Shield
    Photocell
+
* Photocell
    Resistors (10k, 330 ohm)
+
* Resistors (10k, 330 ohm)
    LED
+
* LED
    Wires
+
* Wires
  
 
Wiring Diagram
 
Wiring Diagram
 
Photo of Completed Circuit
 
Photo of Completed Circuit
  
Arduino - Software / MQTT
+
==Arduino - Software / MQTT==
  
 
There are four main tasks the Arduino software needs to take care of for this example:
 
There are four main tasks the Arduino software needs to take care of for this example:
  
    Gather light sensor readings periodically
+
* Gather light sensor readings periodically
    Publish sensor readings via MQTT
+
* Publish sensor readings via MQTT
    Listen for commands via MQTT
+
* Listen for commands via MQTT
    Control the LED based on a setpoint
+
* Control the LED based on a setpoint
  
 
An MQTT client is created in the setup function.
 
An MQTT client is created in the setup function.
Line 52: Line 52:
 
Every loop, the following happens:
 
Every loop, the following happens:
  
    The MQTT client connects (if it is not already connected).
+
* The MQTT client connects (if it is not already connected).
    Based on the “sensing mode” the application decides how to drive the LED.  It could be OFF, ON or SENSE.  In the SENSE case a light reading is taken and the LED is driven according to a hardcoded setpoint.
+
* Based on the “sensing mode” the application decides how to drive the LED.  It could be OFF, ON or SENSE.  In the SENSE case a light reading is taken and the LED is driven according to a hardcoded setpoint.
    In the SENSE case, if 5 seconds have elapsed since the last reading a new reading is published via MQTT.
+
* In the SENSE case, if 5 seconds have elapsed since the last reading a new reading is published via MQTT.
  
 
When connecting an MQTT client a callback function is specified to handle any incoming MQTT messages on the subscribed topic(s).  In this function we check for commands in the proper JSON format and adjust our mode if the command specifies a new mode.
 
When connecting an MQTT client a callback function is specified to handle any incoming MQTT messages on the subscribed topic(s).  In this function we check for commands in the proper JSON format and adjust our mode if the command specifies a new mode.
Line 62: Line 62:
 
Below is the Arduino code.  Note: This requires both the Ethernet library and MQTT Client Library to be installed in your Arduino library folder.  See an example here on how to install Arduino libraries.  (it’s easy!)
 
Below is the Arduino code.  Note: This requires both the Ethernet library and MQTT Client Library to be installed in your Arduino library folder.  See an example here on how to install Arduino libraries.  (it’s easy!)
  
#include <SPI.h>
+
#include <SPI.h>
#include <PubSubClient.h>
+
#include <PubSubClient.h>
#include <Ethernet.h>
+
#include <Ethernet.h>
 
+
/*
+
/*
* LightSensorMqttDemo
+
  * LightSensorMqttDemo
*
+
  *
* A simple m2m.io platform demo for Arduino.
+
  * A simple m2m.io platform demo for Arduino.
*/
+
  */
 
+
#define MQTT_SERVER "q.m2m.io"
+
#define MQTT_SERVER "q.m2m.io"
 
+
// MAC Address of Arduino Ethernet Sheild (on sticker on shield)
+
// MAC Address of Arduino Ethernet Sheild (on sticker on shield)
byte MAC_ADDRESS[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x31, 0xB8 };
+
byte MAC_ADDRESS[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x31, 0xB8 };
PubSubClient client;
+
PubSubClient client;
 
+
// Pin 9 is the LED output pin
+
// Pin 9 is the LED output pin
int ledPin = 9;
+
int ledPin = 9;
// Analog 0 is the input pin
+
// Analog 0 is the input pin
int lightPinIn = 0;
+
int lightPinIn = 0;
 
+
// defines and variable for sensor/control mode
+
// defines and variable for sensor/control mode
#define MODE_OFF    0  // not sensing light, LED off
+
#define MODE_OFF    0  // not sensing light, LED off
#define MODE_ON    1  // not sensing light, LED on
+
#define MODE_ON    1  // not sensing light, LED on
#define MODE_SENSE  2  // sensing light, LED controlled by software
+
#define MODE_SENSE  2  // sensing light, LED controlled by software
int senseMode = 0;
+
int senseMode = 0;
 
+
unsigned long time;
+
unsigned long time;
 
+
char message_buff[100];
+
char message_buff[100];
 
+
void setup()
+
void setup()
{
+
{
  // initialize the digital pin as an output.
+
  // initialize the digital pin as an output.
  pinMode(ledPin, OUTPUT);
+
  pinMode(ledPin, OUTPUT);
 
+
 
  // init serial link for debugging
+
  // init serial link for debugging
  Serial.begin(9600);
+
  Serial.begin(9600);
 
+
 
  if (Ethernet.begin(MAC_ADDRESS) == 0)
+
  if (Ethernet.begin(MAC_ADDRESS) == 0)
  {
+
  {
      Serial.println("Failed to configure Ethernet using DHCP");
+
      Serial.println("Failed to configure Ethernet using DHCP");
      return;
+
      return;
  }
+
  }
 
+
  client = PubSubClient(MQTT_SERVER, 1883, callback);
+
  client = PubSubClient(MQTT_SERVER, 1883, callback);
}
+
}
 
+
void loop()
+
void loop()
{
+
{
  if (!client.connected())
+
  if (!client.connected())
  {
+
  {
      // clientID, username, MD5 encoded password
+
      // clientID, username, MD5 encoded password
      client.connect("arduino-mqtt", "john@m2m.io", "00000000000000000000000000000");
+
      client.connect("arduino-mqtt", "john@m2m.io", "00000000000000000000000000000");
      client.publish("io.m2m/arduino/lightsensor", "I'm alive!");
+
      client.publish("io.m2m/arduino/lightsensor", "I'm alive!");
      client.subscribe("io.m2m/arduino/lightsensor");
+
      client.subscribe("io.m2m/arduino/lightsensor");
  }
+
  }
 
+
 
  switch (senseMode) {
+
  switch (senseMode) {
    case MODE_OFF:
+
    case MODE_OFF:
      // light should be off
+
      // light should be off
      digitalWrite(ledPin, LOW);
+
      digitalWrite(ledPin, LOW);
      break;
+
      break;
    case MODE_ON:
+
    case MODE_ON:
      // light should be on
+
      // light should be on
      digitalWrite(ledPin, HIGH);
+
      digitalWrite(ledPin, HIGH);
      break;
+
      break;
    case MODE_SENSE:
+
    case MODE_SENSE:
      // light is adaptive to light sensor
+
      // light is adaptive to light sensor
     
+
       
      // read from light sensor (photocell)
+
      // read from light sensor (photocell)
      int lightRead = analogRead(lightPinIn);
+
      int lightRead = analogRead(lightPinIn);
 
+
      // if there is light in the room, turn off LED
+
      // if there is light in the room, turn off LED
      // else, if it is "dark", turn it on
+
      // else, if it is "dark", turn it on
      // scale of light in this circit is roughly 0 - 900
+
      // scale of light in this circit is roughly 0 - 900
      // 500 is a "magic number" for "dark"
+
      // 500 is a "magic number" for "dark"
      if (lightRead > 500) {
+
      if (lightRead > 500) {
        digitalWrite(ledPin, LOW);
+
        digitalWrite(ledPin, LOW);
      } else {
+
      } else {
        digitalWrite(ledPin, HIGH);
+
        digitalWrite(ledPin, HIGH);
      }
+
      }
     
+
     
      // publish light reading every 5 seconds
+
      // publish light reading every 5 seconds
      if (millis() > (time + 5000)) {
+
      if (millis() > (time + 5000)) {
        time = millis();
+
        time = millis();
        String pubString = "{\"report\":{\"light\": \"" + String(lightRead) + "\"}}";
+
        String pubString = "{\"report\":{\"light\": \"" + String(lightRead) + "\"}}";
        pubString.toCharArray(message_buff, pubString.length()+1);
+
        pubString.toCharArray(message_buff, pubString.length()+1);
        //Serial.println(pubString);
+
        //Serial.println(pubString);
 
         client.publish("io.m2m/arduino/lightsensor", message_buff);
 
         client.publish("io.m2m/arduino/lightsensor", message_buff);
      }
+
      }
     
+
     
     
+
     
  }
+
  }
 
+
 
  // MQTT client loop processing
+
  // MQTT client loop processing
  client.loop();
+
  client.loop();
}
+
}
 
+
// handles message arrived on subscribed topic(s)
+
// handles message arrived on subscribed topic(s)
void callback(char* topic, byte* payload, unsigned int length) {
+
void callback(char* topic, byte* payload, unsigned int length) {
 
+
  int i = 0;
+
  int i = 0;  
 
+
  //Serial.println("Message arrived:  topic: " + String(topic));
+
  //Serial.println("Message arrived:  topic: " + String(topic));
  //Serial.println("Length: " + String(length,DEC));
+
  //Serial.println("Length: " + String(length,DEC));
 
+
 
  // create character buffer with ending null terminator (string)
+
  // create character buffer with ending null terminator (string)
  for(i=0; i<length; i++) {
+
  for(i=0; i<length; i++) {
    message_buff[i] = payload[i];
+
    message_buff[i] = payload[i];
  }
+
  }
  message_buff[i] = '\0';
+
  message_buff[i] = '\0';
 
+
 
  String msgString = String(message_buff);
+
  String msgString = String(message_buff);
 
+
 
  //Serial.println("Payload: " + msgString);
+
  //Serial.println("Payload: " + msgString);
 
+
 
  if (msgString.equals("{\"command\":{\"lightmode\": \"OFF\"}}")) {
+
  if (msgString.equals("{\"command\":{\"lightmode\": \"OFF\"}}")) {
    senseMode = MODE_OFF;
+
    senseMode = MODE_OFF;
  } else if (msgString.equals("{\"command\":{\"lightmode\": \"ON\"}}")) {
+
  } else if (msgString.equals("{\"command\":{\"lightmode\": \"ON\"}}")) {
    senseMode = MODE_ON;
+
    senseMode = MODE_ON;
  } else if (msgString.equals("{\"command\":{\"lightmode\": \"SENSE\"}}")) {
+
  } else if (msgString.equals("{\"command\":{\"lightmode\": \"SENSE\"}}")) {
    senseMode = MODE_SENSE;
+
    senseMode = MODE_SENSE;
  }
+
  }
}
+
}
  
mqtt.io MQTT Client - Publish / Subscribe
+
==mqtt.io MQTT Client - Publish / Subscribe==
  
 
To communicate with our Arduino we’ll use the websocket MQTT client at mqtt.io.
 
To communicate with our Arduino we’ll use the websocket MQTT client at mqtt.io.
Line 208: Line 208:
 
For this example I wrote a quick Play! app in Scala.  The code below shows the LightReading model.  When the all() method is invoked, a call is generated to the past API endpoint requesting our “report:light” whatevers.  (“whatevers” = whatever you want to send)
 
For this example I wrote a quick Play! app in Scala.  The code below shows the LightReading model.  When the all() method is invoked, a call is generated to the past API endpoint requesting our “report:light” whatevers.  (“whatevers” = whatever you want to send)
  
package models
+
package models
 
+
import play.api.libs.ws.WS
+
import play.api.libs.ws.WS
import play.api.libs.json.JsObject
+
import play.api.libs.json.JsObject
import com.ning.http.client.Realm
+
import com.ning.http.client.Realm
import com.codahale.jerkson.Json
+
import com.codahale.jerkson.Json
 
+
case class LightReading(timestamp: Long, value: Int)
+
case class LightReading(timestamp: Long, value: Int)
 
+
object LightReading {
+
object LightReading {
 
+
def all(): List[LightReading] = {
+
def all(): List[LightReading] = {  
 
+
var returnList: List[LightReading] = List()
+
var returnList: List[LightReading] = List()
 
+
val username = "john@m2m.io"
+
val username = "john@m2m.io"
              val password = "password"
+
              val password = "password"
 
+
val url = "http://api.m2m.io/1/past/io.m2m/arduino/lightsensor?whatevers=report:light"
+
val url = "http://api.m2m.io/1/past/io.m2m/arduino/lightsensor?whatevers=report:light"
 
+
val response = WS.url(url)
+
val response = WS.url(url)
      .withAuth(username, password, Realm.AuthScheme.BASIC)
+
      .withAuth(username, password, Realm.AuthScheme.BASIC)
      .get().await(10000).get
+
      .get().await(10000).get  
 
+
 
+
var retJson = response.json.as[JsObject]
+
var retJson = response.json.as[JsObject]
 
+
retJson.fields.map { j =>
+
retJson.fields.map { j =>
val jsobj = j._2.as[JsObject]
+
val jsobj = j._2.as[JsObject]
val m = Json.parse[Map[String, Int]](jsobj.toString)
+
val m = Json.parse[Map[String, Int]](jsobj.toString)
returnList = returnList :+ new LightReading(j._1.toLong, m.getOrElse("report:light", 0))  
+
returnList = returnList :+ new LightReading(j._1.toLong, m.getOrElse("report:light", 0))  
}
+
}
 
+
returnList
+
returnList
}
+
}
}
+
}
  
 
After deploying the application and requesting the past page, the app returns history for both values read and commands sent.  There is a separate Command model similar to the one above to request “command:lightmode” whatevers.
 
After deploying the application and requesting the past page, the app returns history for both values read and commands sent.  There is a separate Command model similar to the one above to request “command:lightmode” whatevers.
  
Conclusion
+
==Conclusion==
  
 
Admittedly, this example is very simple.  But I believe it is a good illustration of how I went from bare Arduino breadboard to retrieving sensor values from a cloud-hosted API in less than a day.  Good luck with your projects!
 
Admittedly, this example is very simple.  But I believe it is a good illustration of how I went from bare Arduino breadboard to retrieving sensor values from a cloud-hosted API in less than a day.  Good luck with your projects!

Revision as of 15:27, 8 May 2017

sumber: http://m2mio.tumblr.com/post/30048662088/a-simple-example-arduino-mqtt-m2mio

This post shows a quick example of using MQTT and the m2m.io platform with an Arduino device. I walk through the platform from end to end starting with setting up the Arduino to capture sensor data and ending with a super simple Play! app which shows the past sensor readings by consuming the data presented by the m2m.io platform API.

I hope you find it useful.

Overview

Connect. Send. Store. Use.

This example was crafted to illustrate the main concepts of the m2m.io platform.

Connect: The Arduino will connect via MQTT to the m2m broker.

Send: Sensor data is sent to the broker/platform and command data is sent to the Arduino.

Store: If formatted correctly, any MQTT traffic going to the broker will be stored for retrieval in the future.

Use: The platform provides a simple, RESTful API for client applications to make use of their stored data.

This simple sensor application gathers light sensor data which is published to the broker. Commands can be sent via MQTT to the sensor.

Arduino - Hardware

The circuits I constructed allow the Arduino device to read light levels using an analog input and (optionally) control an LED based on a set light level. The optional LED portion of the circuit makes the Arduino a very simple control system which controls an output based on input values it senses.

I started with the SparkFun Arduino Inventor’s Kit which includes the components necessary to create a light sensing circuit. This could easily be done with any Arduino device capable of hosting the Arduino Ethernet Shield and a couple standard components.

Parts List

  • Arduino Device (in this example an Uno)
  • Arduino Ethernet Shield
  • Photocell
  • Resistors (10k, 330 ohm)
  • LED
  • Wires

Wiring Diagram Photo of Completed Circuit

Arduino - Software / MQTT

There are four main tasks the Arduino software needs to take care of for this example:

  • Gather light sensor readings periodically
  • Publish sensor readings via MQTT
  • Listen for commands via MQTT
  • Control the LED based on a setpoint

An MQTT client is created in the setup function.

Every loop, the following happens:

  • The MQTT client connects (if it is not already connected).
  • Based on the “sensing mode” the application decides how to drive the LED. It could be OFF, ON or SENSE. In the SENSE case a light reading is taken and the LED is driven according to a hardcoded setpoint.
  • In the SENSE case, if 5 seconds have elapsed since the last reading a new reading is published via MQTT.

When connecting an MQTT client a callback function is specified to handle any incoming MQTT messages on the subscribed topic(s). In this function we check for commands in the proper JSON format and adjust our mode if the command specifies a new mode.

An interesting update to this app would be to handle receiving commands to change hardcoded values such as time we wait between publishing light readings or the “light”/“dark” setpoint.

Below is the Arduino code. Note: This requires both the Ethernet library and MQTT Client Library to be installed in your Arduino library folder. See an example here on how to install Arduino libraries. (it’s easy!)

#include <SPI.h>
#include <PubSubClient.h>
#include <Ethernet.h>

/*
 * LightSensorMqttDemo
 *
 * A simple m2m.io platform demo for Arduino.
 */

#define MQTT_SERVER "q.m2m.io"

// MAC Address of Arduino Ethernet Sheild (on sticker on shield)
byte MAC_ADDRESS[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x31, 0xB8 };
PubSubClient client;

// Pin 9 is the LED output pin
int ledPin = 9;
// Analog 0 is the input pin
int lightPinIn = 0;

// defines and variable for sensor/control mode
#define MODE_OFF    0  // not sensing light, LED off
#define MODE_ON     1  // not sensing light, LED on
#define MODE_SENSE  2  // sensing light, LED controlled by software
int senseMode = 0;

unsigned long time;

char message_buff[100];

void setup()
{
  // initialize the digital pin as an output.
  pinMode(ledPin, OUTPUT);
  
  // init serial link for debugging
  Serial.begin(9600);
  
  if (Ethernet.begin(MAC_ADDRESS) == 0)
  {
      Serial.println("Failed to configure Ethernet using DHCP");
      return;
  }

  client = PubSubClient(MQTT_SERVER, 1883, callback);
}

void loop()
{
  if (!client.connected())
  {
      // clientID, username, MD5 encoded password
      client.connect("arduino-mqtt", "john@m2m.io", "00000000000000000000000000000");
      client.publish("io.m2m/arduino/lightsensor", "I'm alive!");
      client.subscribe("io.m2m/arduino/lightsensor");
  }
  
  switch (senseMode) {
    case MODE_OFF:
      // light should be off
      digitalWrite(ledPin, LOW);
      break;
    case MODE_ON:
      // light should be on
      digitalWrite(ledPin, HIGH);
      break;
    case MODE_SENSE:
      // light is adaptive to light sensor
       
      // read from light sensor (photocell)
      int lightRead = analogRead(lightPinIn);

      // if there is light in the room, turn off LED
      // else, if it is "dark", turn it on
      // scale of light in this circit is roughly 0 - 900
      // 500 is a "magic number" for "dark"
      if (lightRead > 500) {
        digitalWrite(ledPin, LOW);
      } else {
        digitalWrite(ledPin, HIGH);
      }
      
      // publish light reading every 5 seconds
      if (millis() > (time + 5000)) {
        time = millis();
        String pubString = "{\"report\":{\"light\": \"" + String(lightRead) + "\"}}";
        pubString.toCharArray(message_buff, pubString.length()+1);
        //Serial.println(pubString);
       client.publish("io.m2m/arduino/lightsensor", message_buff);
      }
      
      
  }
  
  // MQTT client loop processing
  client.loop();
}

// handles message arrived on subscribed topic(s)
void callback(char* topic, byte* payload, unsigned int length) {

  int i = 0; 

  //Serial.println("Message arrived:  topic: " + String(topic));
  //Serial.println("Length: " + String(length,DEC));
  
  // create character buffer with ending null terminator (string)
  for(i=0; i<length; i++) {
    message_buff[i] = payload[i];
  }
  message_buff[i] = '\0';
  
  String msgString = String(message_buff);
  
  //Serial.println("Payload: " + msgString);
  
  if (msgString.equals("{\"command\":{\"lightmode\": \"OFF\"}}")) {
    senseMode = MODE_OFF;
  } else if (msgString.equals("{\"command\":{\"lightmode\": \"ON\"}}")) {
    senseMode = MODE_ON;
  } else if (msgString.equals("{\"command\":{\"lightmode\": \"SENSE\"}}")) {
    senseMode = MODE_SENSE;
  }
}

mqtt.io MQTT Client - Publish / Subscribe

To communicate with our Arduino we’ll use the websocket MQTT client at mqtt.io.

Some quick notes, the username and password are entered in the Options tab before connecting. Signing in is required to see messages published on private namespaces. See the help article here for more information. The address we’ll use is the m2m MQTT broker at q.m2m.io, port 1883.

The screenshot below shows the Arduino publishing the “I’m Alive” message upon connecting. Note that this message wasn’t sent in JSON format so it won’t be stored by the platform.

Commands can be sent to the Arduino by publishing from the web client. Notice I publish commands in JSON format. This causes the commands to be stored by the platform and we’ll be able to see their history if we want to pull them up. Below is a screen shot showing publishing commands. The commands are seen in the subscribe window as they are sent.

In the sequence sent above, the Arduino turned on the LED when receiving the “ON” command and off when receiving the “OFF” command. Upon receiving the “SENSE” command it controlled the LED based on whether the light sensor was in the light or dark. This I was able to control by placing my hand over the photocell.

The screenshot below was captured while the Arduino was in SENSE mode. It continously publishes light readings every 5 seconds and this data is seen live on the MQTT topic.

Platform API - Consuming Past Data with Play! Application

Now that we have brought up the device, sent it some commands and saw it publish some sensor readings let’s use the m2m.io API to see what just happened. The API is documented in detail here.

For this example I wrote a quick Play! app in Scala. The code below shows the LightReading model. When the all() method is invoked, a call is generated to the past API endpoint requesting our “report:light” whatevers. (“whatevers” = whatever you want to send)

package models

import play.api.libs.ws.WS
import play.api.libs.json.JsObject
import com.ning.http.client.Realm
import com.codahale.jerkson.Json

case class LightReading(timestamp: Long, value: Int)

object LightReading {

	def all(): List[LightReading] = { 

		var returnList: List[LightReading] = List()

		val username = "john@m2m.io"
      	        val password = "password"

		val url = "http://api.m2m.io/1/past/io.m2m/arduino/lightsensor?whatevers=report:light"  

		val response = WS.url(url)
      		.withAuth(username, password, Realm.AuthScheme.BASIC)
      		.get().await(10000).get 


		var retJson = response.json.as[JsObject]

	 	retJson.fields.map { j =>
			val jsobj = j._2.as[JsObject]
			val m = Json.parse[Map[String, Int]](jsobj.toString)
			returnList = returnList :+ new LightReading(j._1.toLong, m.getOrElse("report:light", 0)) 
		}

		returnList
	}
}

After deploying the application and requesting the past page, the app returns history for both values read and commands sent. There is a separate Command model similar to the one above to request “command:lightmode” whatevers.

Conclusion

Admittedly, this example is very simple. But I believe it is a good illustration of how I went from bare Arduino breadboard to retrieving sensor values from a cloud-hosted API in less than a day. Good luck with your projects!


Referensi