There are a lot of different cloud services in the Internet working as a storage to accumulate telemetry data from IoT devices. It’s quite comfortable services. You don’t need to spend some time to install a server to collect data from your IoT devices and do some analytic job. All these things you get “out of the box” just after creating account.
One of the most popular service – http://www.thingspeak.com by MatLab. Other variants are Blynk, Cayenne and etc.. Sparkfun had been quite popular recently, but, seems, the project was closed.
It’s not so hard to install own local IoT server. There are a lot of OpenSource variants: OpenHab, Domoticz, OpenMotics,
The service http://www.thingspeak.com is so popular since the free account offer very good conditions:
GET/POST request to thingspeak.com
There are a lot of ways how to send telemetry data from ESP8266 microcontroller to http://www.thingspeak.com: MQTT protocol and POST/GET
The easiest one is an insecure GET request to the server. The code examplehttps://www.mathworks.com/help/thingspeak/MoistureMonitor.html available at the thingspeak.com site. Other examples and GitHub. Thingspeak communication library on GitHub.
One of the problems sending data to thingspeak.com – there is no
The code example how to POST/GET requests without using
#include "ESP8266WiFi.h"
char* host = "api.thingspeak.com";
int numOfRepeat = 0; //re-request number
long lastResult = -1; //last record number got from thingspeak.com
const long maxNumOfRepeats = 4; //Max nymber of re-requests
long channelID = 6625XX; //Change to your Account -> MyProfile -> Channels -> My Channels
char writeAPIKey[] = "A5OKIYQSAFXXXXXX"; // Change to your channel Write API Key.
WiFiClient client;
//If field was successfully updated - return true, otherwise - false
//Send data to thingspeak using POST or GET
bool UpdateChannelField(int index, char* writeAPIKey, bool isGET)
{
if (connectWiFi())
{
if (client.connect(host, 80))
{
String url = "/update?api_key=" + String(writeAPIKey) + "&field" + inputs[index].field + "=" + inputs[index].value;
String req = isGET ? "GET" : "POST";
Serial.println("Send [" + req + "] request. URL: http://" + String(host) + url);
if (isGET)
{
client.print(String("GET ") + url + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36\r\n" +
"Connection: close\r\n\r\n");
}
else
{
client.println("POST /update HTTP/1.1");
client.println("Host: " + String(host));
client.println("User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36");
client.println("Connection: close");
client.println("X-THINGSPEAKAPIKEY: " + String(writeAPIKey));
client.println("Content-Type: application/x-www-form-urlencoded");
client.println("Content-Length: " + String(url.length()));
client.println("");
client.print(url);
}
String line = client.readString();
client.stop();
if (CheckUpdate(line))
{
numOfRepeat = 0;
}
else
{
Serial.println("Current number of repeats: " + String(numOfRepeat+1) + " of " + String(maxNumOfRepeats));
if (numOfRepeat < maxNumOfRepeats)
{
Serial.println("Repeat sending data.");
numOfRepeat++;
UpdateChannelField(index, writeAPIKey, isGET); //Repeat updating field
}
}
}
else
{
Serial.println("Couldn't connect to the server [" + String(host) + ":80]");
}
}
return false;
}
//Check the result from thingspeak.com
bool CheckUpdate(String line)
{
bool result = false;
int len = line.length();
int pos = line.lastIndexOf("\n");
String res = line.substring(pos);
res.trim();
long i = -1;
if (isValidNumber(res))
{
i = res.toInt();
Serial.println("Result: " + String(i));
if (i == 0)
{
result = false;
}
else
{
if (i != lastResult)
{
lastResult = i;
result = true;
Serial.println("The field was successfully updated.");
}
}
}
if (result == false)
{
Serial.println("The field wasn't updated.");
//Serial.println("Response: " + line);
}
Serial.println();
return result;
}
//Check either the string is a valid number or not
boolean isValidNumber(String str)
{
boolean isNum=false;
for(byte i=0;i<str.length();i++)
{
isNum = isDigit(str.charAt(i)) || str.charAt(i) == '+' || str.charAt(i) == '.' || str.charAt(i) == '-';
if(!isNum) return false;
}
return isNum;
}
MQTT protocol with thinspeak .com
The main problem when using MQTT protocol –
#include "PubSubClient.h"
const char* server = "mqtt.thingspeak.com";
long channelID = 662XXX; //Change to your Account -> MyProfile -> Channels -> My Channels
char writeAPIKey[] = "A5OKIYQSAFHUTXXX"; // Change to your channel Write API Key.
char mqttUserName[] = "XXX"; // Can be any name.
char mqttAPIKey[] = "G8UYMVHO6CS6XXXX"; // Change this your MQTT API Key from Account -> MyProfile.
char readAPIKey[] = "81Y5MLWESY15XXXX"; // Change to your channel Read API Key.
String topicString;
enum PIN { Cold = 0, Hot = 1, Last };
struct Input {
String name;
Bounce debouncer;
bool laststate;
double value;
int pin;
int field;
};
Input inputs[Last] = {
{"Cold", Bounce(), false, 39.703, D5, 1},
{"Hot", Bounce(), false, 25.203, D6, 2}
};
PubSubClient mqttClient(client); // Initialize the PuBSubClient library.
static const char alphanum[] ="0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"; // For random generation of client ID.
void initMQTT()
{
// Create a topic string and publish data to ThingSpeak channel feed.
topicString ="channels/" + String(channelID) + "/publish/"+String(writeAPIKey);
mqttClient.setServer(server, 1883); // Set the MQTT broker details.
mqttClient.setCallback(mqttSubscriptionCallback);
}
void mqttSubscriptionCallback(char* topic, byte* payload, unsigned int length)
{
Serial.print("Message arrived [" + String(topic) + "]: ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
/**
* 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);
}
String MQTTConnectionStatus(int code)
{
switch (code)
{
case MQTT_CONNECTION_TIMEOUT: //-4
return "The server didn't respond within the keepalive time";
break;
case MQTT_CONNECTION_LOST: //-3 :
return "The network connection was broken";
break;
case MQTT_CONNECT_FAILED: //-2
return "The network connection failed";
break;
case MQTT_DISCONNECTED: //-1
return "The client is disconnected cleanly";
break;
case MQTT_CONNECTED: //0
return "The client is connected";
break;
case MQTT_CONNECT_BAD_PROTOCOL: //1
return "The server doesn't support the requested version of MQTT";
break;
case MQTT_CONNECT_BAD_CLIENT_ID: //2
return "The server rejected the client identifier";
break;
case MQTT_CONNECT_UNAVAILABLE: //3
return "The server was unable to accept the connection";
case MQTT_CONNECT_BAD_CREDENTIALS: //4
return "The username/password were rejected";
break;
case MQTT_CONNECT_UNAUTHORIZED: //5
return "The client was not authorized to connect";
break;
default:
return "Unknown";
break;
}
}
/**
* Build a random client ID
* clientID - char array for output
* idLength - length of clientID (actual length will be one longer for NULL)
*/
void getID(char clientID[], int idLength)
{
// Generate ClientID
for (int i = 0; i < idLength; i++) {
clientID[i] = alphanum[random(51)];
}
clientID[idLength] = '\0';
}
bool reconnect()
{
char clientID[9];
Serial.println("Attempting MQTT connection...");
getID(clientID, 8);
// Connect to the MQTT broker
if (mqttClient.connect(clientID, mqttUserName, mqttAPIKey))
{
String str = "Connected with Client ID: " + String(clientID) + ", Username: " + mqttUserName + " , API Key: " + mqttAPIKey;
Serial.println(str);
for (int i = 1; i < Last; i++)
{
if (mqttSubscribe(channelID, i, readAPIKey, 0) == 1)
{
Serial.println("Subscribed to the channel [" + String(channelID) +"], the field [field" + String(i) + "].");
}
}
return true;
}
else
{
Serial.print("Connection failed with status: " + MQTTConnectionStatus(mqttClient.state())); // See https://pubsubclient.knolleary.net/api.html#state for the failure code explanation.
}
return false;
}
void mqttpublish(int index)
{
if (!mqttClient.connected())
{
reconnect();
}
if (mqttClient.connected())
{
mqttClient.loop(); // Call the loop regularly to allow the client to process incoming messages and maintain its connection to the server.
// Create data string to send to ThingSpeak
String data = String("field" + String(inputs[index].field) + "=" + String(inputs[index].value)); // + "&field2=" + String(inputs[Hot].value));
String str = "Trying to send data to server [" + String(server) + "]:\r\n";
str += "\tData: " + data + "\r\n";
str += "\tTopic string: " + topicString;
Serial.println(str);
bool result = mqttClient.publish(topicString.c_str(), data.c_str()); //ThingSpeak always return true. :-(
if (result)
{
Serial.println("Data successfully published to channel: " + String(channelID));
}
else
{
Serial.println("Error data sending.");
}
}
}
ESP8266 WiFi related code
I used WiFiManager to avoid hardcoding of SSID and password. Function
#include "ESP8266WiFi.h"
#include "DNSServer.h"
#include "ESP8266WebServer.h"
#include "WiFiManager.h" //https://github.com/tzapu/WiFiManager
//SSID of your network
char ssidWiFi[32]; //SSID of your Wi-Fi router
char passWiFi[32]; //Password of your Wi-Fi router
void setupWiFi()
{
//Static parameters for AP
IPAddress _ip = IPAddress(192,168, 1, 5);
IPAddress _gw = IPAddress(192, 168, 1, 1);
IPAddress _sn = IPAddress(255, 255, 255, 0);
//WiFiManager intialization. Once its business is done, there is no need to keep it around
WiFiManager wifiManager;
//reset saved settings
//wifiManager.resetSettings();
wifiManager.setAPStaticIPConfig(_ip, _gw, _sn);
//fetches ssid and pass from eeprom and tries to connect
//if it does not connect it starts an access point with the specified name
//and goes into a blocking loop awaiting configuration
if (!wifiManager.autoConnect("ResourcesMeterAP"))
{
Serial.println("failed to connect, we should reset as see if it connects");
delay(3000);
ESP.reset();
delay(5000);
}
else
{
String pass = WiFi.psk();
String ssid = WiFi.SSID();
strcpy(passWiFi, pass.c_str());
strcpy(ssidWiFi, ssid.c_str());
Serial.print("Wi-Fi connected to " + String(ssidWiFi) + ". IP address: ");
Serial.println(WiFi.localIP()); //IP2STR( WiFi.localIP()));
Serial.print("Strength ...: ");
Serial.println(WiFi.RSSI());
}
}
bool connectWiFi()
{
if (WiFi.status() != WL_CONNECTED)
{
Serial.println("Tryng to connect to Wi-Fi AP with SSID [" + String(ssidWiFi) + "]");
WiFi.mode(WIFI_STA);
WiFi.begin(ssidWiFi, passWiFi);
//WiFi.begin();
int connRes = WiFi.waitForConnectResult();
if (connRes != WL_CONNECTED)
{
Serial.println("Wi-Fi connection to " + WiFi.SSID() + " failed. Status " + String(WiFi.status()) + ", " + WiFiconnectionStatus(WiFi.status()) + ", Retrying later...\n");
WiFi.disconnect();
return false;
}
else {
Serial.print("Wi-Fi connected to " + WiFi.SSID() + ". IP address: ");
Serial.println(WiFi.localIP()); //IP2STR( WiFi.localIP()));
Serial.print("Strength ...: ");
Serial.println(WiFi.RSSI());
}
}
return true;
}
String WiFiconnectionStatus(int which)
{
switch (which)
{
case WL_CONNECTED:
return "Connected";
break;
case WL_NO_SSID_AVAIL:
return "Network is not availible";
break;
case WL_CONNECT_FAILED:
return "Wrong password";
break;
case WL_IDLE_STATUS:
return "Idle status";
break;
case WL_DISCONNECTED:
return "Disconnected";
break;
default:
return "Unknown";
break;
}
}