0

Can't connect to adafruit.io
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Can't connect to adafruit.io

by ncoleman3 on Fri Jun 21, 2019 1:26 pm

Hello! I have a good grasp of basic Arduino stuff, but this is my first IoT project and I'm having trouble. I'm trying to recreate this project (basically an IFTTT triggered thermal printer): https://www.nyccnc.com/arduino-thermal-printer/ and I can't seem to connect to adafruit.io with their code (below). I thought it might be the SSL fingerprint, since this project is kinda old, so I changed it to (what I think is) the right code. I've tested another adafruit.io example and I was able to connect and receive data, but it seems like the whole process for connecting is different in this sketch than in the adafruit.io example. Any pointers in the right direction would be appreciated!

Code: Select all | TOGGLE FULL SIZE
// *********************************************************
// **** Include headers for all required libraries here ****
// *********************************************************
#include <Adafruit_Thermal.h>
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <SoftwareSerial.h>

// *********************************************************
// **** Define data classes here ****
// *********************************************************
/* Used to hold the couple bits of HTTP response header info we want and the body of the response */
class HTTP_Response
{
public:
String hdr_status = "";
bool is_chunked = true;
long body_len = 0;
String body = "";
};

// *************************************************************
// **** Put #defines, constants, static strings, etc here ****
// *************************************************************
/* A couple macros to make adding multilevel debug statements easier */
#define DEBUG_LEVEL_1(x) {if (DEBUG_LEVEL > 0) {Serial.print(x);}}
#define DEBUG_LEVEL_2(x) {if (DEBUG_LEVEL > 1) {Serial.print(x);}}

/* Set this to 0 for no debug messages, 1 for basic debug messages, and 2 for all debug messages */
const int DEBUG_LEVEL = 2;

/* Your wifi SSID and password */
const char* WIFI_SSID = "WIFI";
const char* WIFI_PASS = "PASSWORD";

/* Your Adafruit IO username, AIO key, and feed name */
const char* AIO_USERNAME = "USERNAME";
const char* AIO_KEY = "MY KEY";
const char* AIO_FEED1 = "maintenance";
/* you can add more feeds here and pass them into the data functions to manage multiple feeds */

/* Adafruit IO host and SSL port. Should not have to change these */
const char* AIO_HOST = "io.adafruit.com";
const int AIO_SSL_PORT = 443;

/*  , taken from Adafruit IO library. Used to make sure the TLS/SSL connection is using the proper Adafruit certificate (a security thing)*/
const char* AIO_SSL_FINGERPRINT = "77 00 54 2D DA E7 D8 03 27 31 23 99 EB 27 DB CB A5 4C 57 18";

/* Adafruit IO API root path */
const char* AIO_API_ROOT_PATH = "/api/v2/";

/* Pin definitions for software serial port that drives the thermal printer */
#define RX_PIN 4
#define TX_PIN 5

// *********************************************************
// **** Declare global variables here ****
// *********************************************************
SoftwareSerial printer_port(RX_PIN, TX_PIN);
Adafruit_Thermal printer(&printer_port);
WiFiClientSecure aio_client;

// **************************************************************************************
// **** Arduino setup() function - put one time initialization and setup code here ****
// **************************************************************************************
void setup()
{
// Huzzah 8266 board has a general purpose red LED attached to Digital pin 0 so set that pin as an output
pinMode(0,OUTPUT);

// Open the USB serial port which will be used to print debug and status messages to the Arduino IDE serial monitor
Serial.begin(115200);

// Wait for the serial port initialization to complete
while(!Serial);

// Open the serial port that talks to the thermal printer
printer_port.begin(19200); //JWS: this was 19200

// Wait for the serial port initialization to complete
while(!printer_port);

// Initialize the printer
printer.begin();

// Connect to WiFi
DEBUG_LEVEL_1("Connecting to WiFi ");
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(250);
DEBUG_LEVEL_1(".");
}

DEBUG_LEVEL_1("\nWiFi connected\nIP address: " + WiFi.localIP().toString() + "\n");
}



// *************************************************************
// **** Arduino loop() function - put main loop code here ****
// *************************************************************
void loop()
{
// Query the Adafruit IO feed defined at the top of the file, restricting the response to just the id and value fields
HTTP_Response resp;
digitalWrite(0,0);
GetLastData(AIO_FEED1, &resp);
digitalWrite(0,1);

// Create the ArduinoJson objects needed to parse the response
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(resp.body);

// If the parsing did not succeed, print out an error message to the serial port
// If it did succeed, process the results
if (root.success() == false)
{
DEBUG_LEVEL_1("Error parsing response body as JSON\n");
DEBUG_LEVEL_2("Body:\n" + resp.body + "\n");
}
else
{
// Extract the id and value fields into separate variables
String id = root["id"];
String value = root["value"];

DEBUG_LEVEL_1(String("id:") + id + "\n");
DEBUG_LEVEL_1(String("value:\n") + value + "\n");

// If we actually got a message, do something with it
if (id.length() > 0 && value.length() > 0)
{
// Print the body of the response to the thermal printer
PrintToThermalPrinter( value, 'S', 'L', false, false );

printer.feed();
printer.feed();

// Delete the message from Adafruit IO
DeleteData( AIO_FEED1, id, &resp);

}
}

// Check for messages every 5 seconds
delay(5000);

/* other main loop code here .... */
}

// *************************************************************
// **** Gets the first message in a given feed ****
// *************************************************************
void GetFirstData( String aio_feed, HTTP_Response* resp )
{
DEBUG_LEVEL_1("Getting first message in feed " + aio_feed + "\n");
SendQueryAndGetResponse("GET", aio_feed, "first", "id,value", resp);
}

// *************************************************************
// **** Gets the last message in a given feed ****
// *************************************************************
void GetLastData( String aio_feed, HTTP_Response* resp )
{
DEBUG_LEVEL_1("Getting last message in feed " + aio_feed + "\n");
SendQueryAndGetResponse("GET", aio_feed, "last", "id,value", resp);
}

// *************************************************************
// **** Gets all messages in a given feed ****
// *************************************************************
void GetAllData( String aio_feed, HTTP_Response* resp )
{
DEBUG_LEVEL_1("Getting all messages in feed " + aio_feed + "\n");
SendQueryAndGetResponse("GET", aio_feed, "", "id,value", resp);
}

// *************************************************************
// **** Deletes a message with a given ID in a given feed ****
// *************************************************************
void DeleteData( String aio_feed, String message_id, HTTP_Response* resp )
{
DEBUG_LEVEL_1("Deleting message in feed " + aio_feed + " with id=" + message_id + "\n");
SendQueryAndGetResponse("DELETE", aio_feed, message_id, "", resp);

// Check the response and if there was an error deleting the message, print an error to the serial port (dependent on DEBUG_LEVEL)
if (resp->hdr_status.equalsIgnoreCase("200 OK") == false)
{
DEBUG_LEVEL_1("Error deleting message with id=" + message_id + "\nStatus=" + resp->hdr_status + "\n");
}
}

// **************************************************************************************
// **** Makes a connection to the Adafruit IO web service, transmits the given ****
// **** request, receives the response, and parses out the header info we want ****
// **** and response body. Debug/logging messages are dumped out the serial port ****
// **** as the function works through all the steps. ****
// **************************************************************************************
bool SendQueryAndGetResponse(String query_type, String aio_feed, String message_id, String include_fields, HTTP_Response* resp)
{
// Create a secure connection to the Adafruit IO site
DEBUG_LEVEL_1(String("Connecting to https://") + AIO_HOST + "\n");

if (aio_client.connect(AIO_HOST, AIO_SSL_PORT) == true)
{
DEBUG_LEVEL_1("Connected\n");
}
else
{
DEBUG_LEVEL_1("Connection failed\n");
return false;
}

// Verify the fingerprint of the SSL certificate being used by the Adafruit site as a bit of a
// security measure to make sure we weren't somehow redirected elsewhere
/* JWS commenting this out:
if (aio_client.verify(AIO_SSL_FINGERPRINT, AIO_HOST) == false)
{
DEBUG_LEVEL_1("SSL certificate doesn't match expected certificate\n");
return false;
}
*/

// Build up the URL to query the proper Adafruit IO feed for the proper user and using the given data retrieval function (ie. first, next, previous, last)
String url = String(AIO_API_ROOT_PATH) + AIO_USERNAME + "/feeds/" + aio_feed + "/data";

// If we were given a message id, tack it on
if (message_id.length() > 0)
{
url += "/" + message_id;
}

// If we were given a list of specific fields to limit the response to, tack those onto the URL
if (include_fields.length() > 0)
{
url += "?include=" + include_fields;
}

// Build up the full HTTP GET request with the above URL and the required headers
String get_req = query_type + " " + url + " HTTP/1.1\r\n" +
"Host: " + AIO_HOST + "\r\n" +
"User-Agent: ESP8266\r\n" +
"X-AIO-KEY: " + AIO_KEY + "\r\n" +
"Content-Type: application/json\r\n" +
"Connection: close\r\n" +
"\r\n";

// Print the GET request to the serial monitor if needed and transmit it to Adafruit IO
// Putting the debug print first is by design because printing to the serial port takes some
// finite amount of time and while it might not matter, it's probably best to drop into the
// response parsing code immediately after sending the request out and not risk missing incoming
// characters due to being tied up printing to the serial port.
DEBUG_LEVEL_1("Sending request\n");
DEBUG_LEVEL_2(get_req);
aio_client.print(get_req);
DEBUG_LEVEL_1("Waiting for response\n");

// The "Connection: close" header in the GET request tells the Adafruit IO server to close it's connection with us
// when it's finished returning the response. That lets us receive data in a loop until the connection is closed.

// First response data we encounter is the response header which is processed differently than the body
bool getting_headers = true;
while (aio_client.connected())
{
// If there is some data available, process it
while (aio_client.available())
{
String line;
char c;

if (getting_headers == true)
{
// Headers are divided into CRLF delimited lines. Read data until a newline character is encountered then process the line
// We pull data from the aio_client stream one character at a time instead of using readStringUntil('\n') because that function
// doesn't put the terminator character in the string and also this way we can dump *exactly* what comes in to the serial port
// for debugging purposes.
line = "";
c = '\0';
while (c != '\n')
{
c = aio_client.read();
DEBUG_LEVEL_2(c);
line += c;
}

// If we run into an empty line, we've reached the end of the header section of the response
if (line == "\r\n")
{
getting_headers = false;
DEBUG_LEVEL_2("Done Headers\n");
DEBUG_LEVEL_2("Raw Response:\n");
}
else
{
// Parse the current header line. If it's interesting to us, the relevant HTTP_Response structure field will be set
if (ParseHeaderLine(line, resp) == false)
{
return false;
}
}
}
else // if not doing the header, process the response body
{
// The body of the response may be a simple series of characters with no special processing required or it may be
// "chunked" into distinct segments, each of which has a leading indicator of the chunk size (in hex)
if (resp->is_chunked == true)
{
// First time through here we will have just finished the header section and be sitting at the first chunk size.
// We don't actually care much about the chunk size though. We'll just skip the chunk size rows and concatenate the
// body content rows until there's nothing left to process. Since the rows come in size/content pairs, if we
// pull two rows from the stream here, every time we get back here we should be properly aligned to pull the next two.

// Skip chunk size line
c = '\0';
while (c != '\n')
{
c = aio_client.read();
DEBUG_LEVEL_2(c);
}

// Read body content line and append to response body
line = "";
c = '\0';
while (c != '\n')
{
c = aio_client.read();
DEBUG_LEVEL_2(c);
line += c;
}

// When we get a chunked body, the above will leave the CRLF characters tacked onto the end of 'line' which we don't want
// so strip them off before appending 'line' to the response body
line.remove(line.length() - 2);
resp->body += line;
}
else
{
// For non-chunked data, just append all available characters to the response body
for (int i = 0; i < aio_client.available(); i++)
{
resp->body += aio_client.read();
}
}
} // process response body
} // while (aio_client.available())
} // while (aio_client.connected())

// We should have the entire response body now so set the body_len field in the HTTP_Response structure
resp->body_len = resp->body.length();
DEBUG_LEVEL_2("Processed response:\n");
DEBUG_LEVEL_2(resp->body + "\n");
DEBUG_LEVEL_1("Response received\n");
}

// *************************************************************
// **** Parses one line of the header section of an HTTP ****
// **** response, extracting a couple bits of information ****
// **** and populating the relevant fields of resp ****
// *************************************************************
bool ParseHeaderLine(String line, HTTP_Response* resp)
{
// Headers could be upper, lower, or mixed case, so convert to all upper case for string compare operations
line.toUpperCase();

// Check for a Transfer-Encoding header. We need this mostly to determine if the body is split into chunks or not.
if (line.indexOf("TRANSFER-ENCODING") > -1)
{
if (line.indexOf("CHUNKED") > -1)
{
resp->is_chunked = true;
}
else if (line.indexOf("IDENTITY") > -1)
{
resp->is_chunked = false;
}
else
{
// We don't support any other encodings (other would generally be some sort of compression)
return false;
}
}

// Check for a Content-Length header. If present, it specifies the length of the body but also implies no chunking
if (line.indexOf("CONTENT_LENGTH") > -1)
{
resp->is_chunked = false;
resp->body_len = line.substring(line.indexOf(":") + 1).toInt();
}

// Check for a Status header
if (line.indexOf("STATUS") > -1)
{
resp->hdr_status = line.substring(line.indexOf(":") + 1);
resp->hdr_status.trim();
}

return true;
}

// *************************************************************
// **** Prints the given string to the thermal printer ****
// *************************************************************
void PrintToThermalPrinter( String msg, char text_size, char justification, bool bold, bool underline )
{
DEBUG_LEVEL_1("Printing message to thermal printer\n");

// Check paper status before trying to print
if (printer.hasPaper() == false)
{
DEBUG_LEVEL_1("Printer has no paper!\n");
return;
}

printer.justify(justification);
printer.setSize(text_size);

if (bold == true)
{
printer.boldOn();
}
else
{
printer.boldOff();
}

if (underline == true)
{
printer.underlineOn();
}
else
{
printer.underlineOff();
}

printer.println("TASK/ASIGNEE/DUE DATE");
printer.print(msg);
printer.println();
printer.println();
printer.println("_____________________");

}

ncoleman3
 
Posts: 5
Joined: Fri Jun 07, 2019 4:38 pm

Re: Can't connect to adafruit.io

by adafruit_support_carter on Fri Jun 21, 2019 3:11 pm

It looks like they went low level for some reason. That approach can break with time as things change under the hood with the API. The low level stuff would have to be changed as well to match. It's best to use the Adafruit IO library, like the examples do, which takes care of all that under the hood and updates as needed for changes in the low level API.

adafruit_support_carter
 
Posts: 12039
Joined: Tue Nov 29, 2016 2:45 pm

Re: Can't connect to adafruit.io

by brubell on Tue Jun 25, 2019 10:12 am

[s]You might want to change the SSL port from 443 to 8883. Fingerprint looks OK.[/s] SSL port is OK

This example (https://github.com/adafruit/Adafruit_MQ ... sp8266.ino) works, i'd consider rewriting their example to match that or using the Adafruit IO Arduino client library instead.

brubell
 
Posts: 431
Joined: Fri Jul 17, 2015 10:33 pm

Re: Can't connect to adafruit.io

by ncoleman3 on Tue Jun 25, 2019 10:18 am

Thanks brubell and adafruit_support_center for your feedback. I definitely want this to be resilient to future API changes, so I've been trying to cobble something together using the Adafruit IO library examples. I will also look into the MQTT example brubell mentioned.

ncoleman3
 
Posts: 5
Joined: Fri Jun 07, 2019 4:38 pm

Please be positive and constructive with your questions and comments.