Remote Sensor Control Using MQTT Publish and Subscribe

This example demonstrates how to use the MQTT publish - subscribe architecture in ThingSpeak™. The MQTT protocol is a low-overhead device messaging system. Using MQTT, subscribe to field 1 of a control channel. When you update the control channel, the posted value is sent to your device. The servo rotates to the specified angle. The device measures the network strength and updates the storage channel.

Supported Hardware

  • ESP8266, NodeMCU, WeMOS

  • Arduino MKR1000

  • Arduino Uno, Mega, Due, or Leonardo with wireless network connection

  • Particle Photon (with slight code and schematic adjustments)

The example is designed to work with only a few extra components, namely a single servo motor. You use the on board Wi-Fi antenna to make Wi-Fi strength measurements.

These images show sample channel output from the storage channel. Field 1 stores the angle of the servo motor that is set from the control channel, and field 2 shows the measured Wi-Fi strength value.

Data analysis shows that the orientation of the WeMOS hardware has a directional effect on the measured signal strength.

Prerequisites

1) Create a ThingSpeak Channel for the subscribe control as shown in Collect Data in a New Channel. The subscribe channel holds the angle for the servo motor. When the servo motor angle is updated, the subscribed device receives the angle as an MQTT message. The device sets the servo angle and measures the new wireless network strength at that angle.

2) Create another ThingSpeak channel for the published data. The publish channel records the set angle and the signal strength data.

3) In the Channel Settings view, enable fields 1 and 2 for the publish channel. To distinguish between the fields, give each field a descriptive name.

4) Note the read and write API keys on the API Keys tab in the Channel Settings view (circled in the image).

Required Hardware

  • WeMOS D1 Mini, or one of the following devices with changes to the libraries used: NodeMCU, ESP8266-01, ESP8266-04, ESP8266-12, ESP8266-12E, Arduino® MKR1000, or other Arduino with Ethernet or wireless network connection

  • Servo motor (for example, Futaba S3003)

  • Jumper wires (at least 3)

  • USB cable

Schematic and Connections

1) Connect D5 on the WeMOS D1 Mini the signal line of the servo.

2) Connect the ground wire of the servo to the ground on the WeMOS board.

3) Connect the servo power to 3.3 V. Using 5 V directly can overload the USB power limit in some cases.

Program Your Arduino

Use the Arduino IDE to program your device.

1) Download the latest Arduino IDE.

2) Add the PubSubClient to the library manager if it is not already there. Select Sketch > Include Library > Manage Libraries. Search for PubSubClient. Select the PubSubClient library and select Install.

3) Add the PubSubClient to the sketch. Select Sketch > Include Library > Manage Libraries. Select PubSubClient to add it to your sketch.

4) Add the ESP8266Wifi library to the library manager and sketch if it is not already there. Follow the same steps as for the PubSubClient library.

5) Add the servo library to the library manager and sketch if it is not already there. Follow the same steps as for the libraries.

6) Create the application:Open a new window in the Arduino IDE, and save the file. Add the code provided in the Code section. Be sure to edit the wireless network information, channel IDs, and API keys.

7) After you successfully upload your program, you can monitor the output using the serial monitor. Upload a value to your ThingSpeak control channel in the range of 0 to 175. You can copy the GET request format from the API Keys tab or modify this text with your write API key. Enter each URL directly into the address bar of your browser, changing YOUR WRITE API KEY to the write API key for your channel.

https://api.thingspeak.com/update?api_key=YOUR WRITE API KEY&field1=<angle value>

The device publishes the angle and the Wi-Fi signal strength to the storage channel whenever you post to the subscribe channel. Make sure that your angle value is in the range of 0 to 175.

Code

1) Begin by including libraries and initializing the control pins.

#include <PubSubClient.h>
#include <ESP8266WiFi.h>
#include <Servo.h> 
#define ANGLE_FIELD 0   
#define DATA_FIELD 1                                  // Data field to post the signal strength to.

2) Define and initialize the variables. Be sure to edit the wireless network information, channel ID, and API keys. Find your channel ID on the top of the main page for your channel.

char ssid[] = "ssid";                                 // Change this to your network SSID (name).
char pass[] = "pass!";                                // Change this to your network password.
const char* server = "mqtt.thingspeak.com"
char mqttUserName[] = "Servo_control";                // Use any name.
char mqttPass[] = "MMMMMMMMMMMMMMMM";                 // Change to your MQTT API key from Account > MyProfile.
long readChannelID=0000;
char readAPIKey[]="XXXXXXXXXXXXXXXX";
long writeChannelID=528510;
char writeAPIKey[]   = "YYYYYYYYYYYYYYYY";

WiFiClient client;                                    // Initialize the Wi-Fi client library.
PubSubClient mqttClient( client );                    // Initialize the PuBSubClient library.
Servo myservo;  // Create servo object to control a servo .

int fieldsToPublish[8]={1,1,0,0,0,0,0,0};             // Change to allow multiple fields.
float dataToPublish[8];                               // Holds your field data.
int changeFlag=0;                                     // Let the main loop know ther is new data to set.
int servo_pos=0;                                      // Servo position

3) Define the function prototypes in this code.

//  
// Prototypes
//

// Handle messages from MQTT subscription.
int mqttSubscriptionCallback(char* topic, byte* payload, unsigned int length);  

// Generate a unique client ID and connect to MQTT broker.
void mqttConnect();  

// Subscribe to a field or feed from a ThingSpeak channel.
int mqttSubscribe( long subChannelID,int field,char* readKey, int unSub);

// Publish messages to a channel feed.
void mqttPublish(long pubChannelID, char* pubWriteAPIKey, float dataArray[], int fieldArray[]);

// Connect to a given Wi-Fi SSID
int connectWifi();

// Measure the Wi-Fi signal strength.
void updateRSSIValue();

// Build a random client ID for MQTT connection.
void getID(char clientID[], int idLength);

4) Initialize pins for input and output, start the serial monitor, and initialize the MQTT client in the setup routine.

void setup() {
Serial.begin( 115200 );
Serial.println( "Start" );
int status = WL_IDLE_STATUS; // Set temporary Wi-Fi status.
       
    connectWifi();  // Connect to Wi-Fi network.
    mqttClient.setServer( server, 1883 ); // Set the MQTT broker details.
    mqttClient.setCallback( mqttSubscriptionCallback );   // Set the MQTT message handler function.
    myservo.attach(14);  // Attach the servo on GIO2 to the servo object. 
    myservo.write(90);  // Start in the middle.

}

5) Each time the main loop executes, check to see if data from the MQTT subscription is available for processing. Then set the servo position to match the data. Make sure the wireless and MQTT clients are active and maintain a connection to the client server.

void loop() {
    
    if (WiFi.status() != WL_CONNECTED) {
        connectWifi();
    }
    
    if (!mqttClient.connected())
    {
       
       mqttConnect(); // Connect if MQTT client is not connected.
        
         if(mqttSubscribe( readChannelID,1,readAPIKey,0 )==1 ){
                Serial.println( " Subscribed " );
            }
    }
    
    mqttClient.loop(); // Call the loop to maintain connection to the server.                         

    if ((servo_pos>175)||(servo_pos<0)){
    servo_pos=0;
    }
   
    if (changeFlag){
      
        changeFlag=0;
        myservo.write(servo_pos);
        dataToPublish[ANGLE_FIELD]=servo_pos;
        delay(1100);                       // Wait for ThingSpeak to publish.
        Serial.println( "Servo value " + String( servo_pos ) );
        mqttPublish( writeChannelID, writeAPIKey, dataToPublish, fieldsToPublish );
    }
    
    delay(1);
}

6) Use the mqttSubscriptionCallback function to handle incoming MQTT messages. The program runs smoother if the main loop performs the processing steps instead of the callback. In this function, use flags to cause changes in the main loop.

/**
 * Process messages received from subscribed channel via MQTT broker.
 *   topic - Subscription topic for message.
 *   payload - Field to subscribe to. Value 0 means subscribe to all fields.
 *   mesLength - Message length.
 */

int mqttSubscriptionCallback( char* topic, byte* payload, unsigned int mesLength ) {
    
    char p[mesLength + 1];
    memcpy( p, payload, mesLength );
    p[mesLength] = NULL;
    Serial.print( "Answer: " );
    Serial.println( String(p) );
    servo_pos=atoi( p );
    changeFlag=1;
}

7) Use the MQTTConnect function to set up and maintain a connection to the MQTT. This function generates a random client ID for connecting to the ThingSpeak MQTT server.

void mqttConnect()
{
    char clientID[ 9 ];
    
    // Loop until connected.
    while ( !mqttClient.connected() )
    {

        getID(clientID,8);
       
        // Connect to the MQTT broker.
        Serial.print( "Attempting MQTT connection..." );
        if ( mqttClient.connect( clientID, mqttUserName, mqttPass ) )
        {
            Serial.println( "Connected with Client ID:  " + String( clientID ) + " User "+ String( mqttUserName ) + " Pwd "+String( mqttPass ) );
           
        } else
        {
            Serial.print( "failed, rc = " );
            // See https://pubsubclient.knolleary.net/api.html#state for the failure code explanation.
            Serial.print( mqttClient.state() );
            Serial.println( " Will try again in 5 seconds" );
            delay( 5000 );
        }
    }
}

8) Use getID to generate a random client ID for connection to the MQTT server.

/**
 * Build a random client ID.
 *   clientID - Character array for output
 *   idLength - Length of clientID (actual length is one character longer for NULL)
 */

void getID(char clientID[], int idLength){
static const char alphanum[] ="0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";                        // For random generation of the client ID.

    // Generate client ID.
    for (int i = 0; i < idLength ; i++) {
        clientID[ i ] = alphanum[ random( 51 ) ];
    }
    clientID[ idLength ] = '\0';
    
}

9) Use mqttSubscribe to receive updates from the LED control field. In this example, you subscribe to a field, but you can also use this function to subscribe to the whole channel feed. Call the function with field = 0 to subscribe to the whole feed.

/**
 * Subscribe to fields of a channel.
 *   subChannelID - Channel to subscribe to.
 *   field - Field to subscribe to. Value 0 means subscribe to all fields.
 *   readKey - Read API key for the subscribe channel.
 *   unSub - Set to 1 for unsubscribe.
 */
 
int mqttSubscribe( long subChannelID, int field, char* readKey, int unsubSub ){
    String myTopic;
    
    // There is no field zero, so if field 0 is sent to subscribe to, then subscribe to the whole channel feed.
    if (field==0){
        myTopic="channels/"+String( subChannelID )+"/subscribe/json/"+String( readKey );
    }
    else{
        myTopic="channels/"+String( subChannelID )+"/subscribe/fields/field"+String( field )+"/"+String( readKey );
    }
    
    Serial.println( "Subscribing to " +myTopic );
    Serial.println( "State= " + String( mqttClient.state() ) );

    if ( unsubSub==1 ){
        return mqttClient.unsubscribe(myTopic.c_str());
    }
    return mqttClient.subscribe( myTopic.c_str() ,0 );
    
}

10) The mqttUnsubscribe function is not used in the code, but you can use it to end a subscription.

/**
 * Unsubscribe channel
 *   subChannelID - Channel to unsubscribe from.
 *   field - Field to unsubscribe subscribe from. The value 0 means subscribe to all fields.
 *   readKey - Read API key for the subscribe channel.
 */

int mqttUnSubscribe(long subChannelID,int field,char* readKey){
    String myTopic;
    
    if (field==0){
        myTopic="channels/"+String( subChannelID )+"/subscribe/json/"+String( readKey );
    }
    else{
        myTopic="channels/"+String( subChannelID )+"/subscribe/fields/field"+String( field )+"/"+String( readKey );
    }
    return mqttClient.unsubscribe( myTopic.c_str() );
    
}

11) Use the mqttPublish function to send your Wi-Fi RSSI data to a ThingSpeak channel.

/**
 * Publish to a channel
 *   pubChannelID - Channel to publish to.
 *   pubWriteAPIKey - Write API key for the channel to publish to.
 *   dataArray - Binary array indicating which fields to publish to, starting with field 1.
 *   fieldArray - Array of values to publish, starting with field 1.
 */

void mqttPublish(long pubChannelID, char* pubWriteAPIKey, float dataArray[], int fieldArray[]) {
    int index=0;
    String dataString="";
    
    updateRSSIValue();  // Make sure the stored value is updated.
    
    // 
    while (index<8){
        
        // Look at the field array to build the posting string to send to ThingSpeak.
        if (fieldArray[ index ]>0){
          
            dataString+="&field" + String( index+1 ) + "="+String( dataArray [ index ] );
        }
        index++;
    }
    
    Serial.println( dataString );
    
    // Create a topic string and publish data to ThingSpeak channel feed.
    String topicString ="channels/" + String( pubChannelID ) + "/publish/"+String( pubWriteAPIKey );
    mqttClient.publish( topicString.c_str(), dataString.c_str() );
    Serial.println( "Published to channel " + String( pubChannelID ) );
}

12) Connect your device to a wireless network using the connectWiFi function.

int connectWifi()
{
    
    while ( WiFi.status() != WL_CONNECTED ) {
        WiFi.begin( ssid, pass );
        delay( 2500 );
        Serial.println( "Connecting to Wi-Fi" ); 
    }
    Serial.println( "Connected" );

}

13) Use the updateRSSIValue function to read the signal strength for the network that you are presently connected to.

void updateRSSIValue(){

   long rssi = WiFi.RSSI();  
   Serial.print( "RSSI:" );
   Serial.println(rssi);
   dataToPublish[ DATA_FIELD ]=float( rssi );

}

See Also

|

Related Topics

External Websites