How to receive JSON response from REST API using ESP8266 Arduino framework - arduino

I am trying to use Beyond Verbal RST API to post voice sample data over HTTP post method from ESP8266. The first step for the API communication is to get access token using the POST method. You can check the following codes. With this code I am just getting "failed to Post" response on serial output.
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <ESP8266HTTPClient.h>
const char *ssid = "xxxxxx";
const char *pass = "xxxxxx";
String token;
HTTPClient https;
WiFiClientSecure client;
String getRecordID(String stoken);
void setup() {
Serial.begin(115200);
Serial.println("connecting to network..");
WiFi.begin(ssid, pass);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("conntected to network..");
}
void loop() {
String ret;
token = getAccessToken();
delay(2000);
Serial.println(token);
}
String getAccessToken(){
// client.setInsecure();
const char * host = "token.beyondverbal.com";
const uint16_t port = 443;
const char * path = "/token";
StaticJsonBuffer<1000> jb;
String res;
Serial.println("conntecting to server..");
if (https.begin(client, host, port, path)) {
https.addHeader("Content-Type", "x-www-formurlencoded");
int httpsCode = https.POST("grant_type=client_credentials&apiKey=1d0956a4-3deb-431a-b3e0-45f5c371fe99");
if (httpsCode > 0) {
if (httpsCode == HTTP_CODE_OK) {
JsonObject& obj = jb.parseObject(https.getString());
String token = obj["access_token"];
if (obj.success()) {
res = token;
} else {
res = "failed to parse json";
}
}
} else {
res = "failed to Post";
}
} else {
res = "failed to connect to server";
}
https.end();
return res;
}
Check out the guideline documentation and please read the authentication part. I have followed the steps and tried in several ways, but still no luck.
But my API code and others parameter are ok. I have tried API post method from Mozilla Firefox addon and different platform. From everywhere I got the token successfully. But I am still unable to get the token with my code.
Please check and me a solution regarding the issue.

Use these libraries. ESPAsyncTCP, asyncHTTPrequest
then use below code. Code for sample.
#include <ESPAsyncTCP.h>
#include <asyncHTTPrequest.h>
asyncHTTPrequest client;
asyncHTTPrequest client2;
void onClientStateChange(void * arguments, asyncHTTPrequest * aReq, int readyState) {
Serial.println(readyState);
switch (readyState) {
case 0:
// readyStateUnsent // Client created, open not yet called
break;
case 1:
// readyStateOpened // open() has been called, connected
break;
case 2:
// readyStateHdrsRecvd // send() called, response headers available
break;
case 3:
// readyStateLoading // receiving, partial data available
break;
case 4:
// readyStateDone // Request complete, all data available.
#ifdef SERIAL_DEBUG
Serial.println(aReq->responseHTTPcode());
#endif
if (aReq->responseHTTPcode() != 200) {
#ifdef SERIAL_DEBUG
Serial.println("return");
#endif
return;
}
String response = aReq->responseText();
#ifdef SERIAL_DEBUG
Serial.println(response.c_str());
#endif
break;
}
}
void setupClient() {
String URL = "dummy.restapiexample.com/api/v1/create";
client.setTimeout(5);
client.setDebug(false);
client.onReadyStateChange(onClientStateChange);
client.open("POST", URL.c_str());
client.setReqHeader("Content-Type", "application/json");
client.send("{\"name\":\"test\",\"salary\":\"123\",\"age\":\"23\"}");
String URL2 = "jsonplaceholder.typicode.com/users";
client2.setTimeout(5);
client2.setDebug(false);
client2.onReadyStateChange(onClientStateChange);
client2.open("GET", URL2.c_str());
client2.send();
}
Always connect with async client as it will not block your main execution until you will get a response.

Related

ESP32: Send a simple TCP Message and receive the response

I want to do the same request as with the netcat "nc" command on my computer with an ESP32:
Computer:
$ nc tcpbin.com 4242
Test
Test
What I've tried so far:
Create a wifi client and listen to an answer:
Connect to a tcp server
write a message
wait and read the answer
#include <Arduino.h>
#include <WiFi.h>
WiFiClient localClient;
const char* ssid = "...";
const char* password = "...";
const uint port = 4242;
const char* ip = "45.79.112.203"; // tcpbin.com's ip
void setup() {
Serial.begin(115200);
Serial.println("Connect Wlan");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(WiFi.localIP());
}
void loop() {
sendRequest();
delay(200);
}
void sendRequest() {
if(!localClient.connected()) {
if (localClient.connect(ip, port)) {
Serial.println("localClient connected");
localClient.write('A'); //Single char
Serial.println("msg sent");
}
}
if(localClient.connected()) {
Serial.println("Start reading");
if (localClient.available() > 0) {
char c = localClient.read();
Serial.print(c);
}
Serial.println("Stop reading");
}
}
I'm pretty sure that I misunderstood something of the tcp concept during the implementation. However, after various approaches and trying other code snippets, I can't come up with a solution.
thank you in advance
regards
Leon
There are several issues with your code.
If you test nc, you will notice that the server will not acknowledge back until your press 'return'. You are sending a single byte without termination in your code, so the server is waiting for subsequent data. To terminate the data, you need to send a '\n', or instead of using client.write('A'), use client.println('A').
A network response take time, your current code expecting immediate response without waiting with if (localClient.available() > 0).
Here is the code that will work:
void sendRequest() {
if (localClient.connect(ip, port)) { // Establish a connection
if (localClient.connected()) {
localClient.println('A'); // send data
Serial.println("[Tx] A");
}
while (!localClient.available()); // wait for response
String str = localClient.readStringUntil('\n'); // read entire response
Serial.print("[Rx] ");
Serial.println(str);
}
}

How to pass the this pointer to a task inside a class?

I am creating a library to manage our wifi network connection on many ESP32 devices.
Inside my library, i have a task that is looping with a 1 second pause started with an xTaskCreate inside my .begin() function. The problem is that i can't start the task if i haven't make it static in my .h file. So once the task is declared as static, i don't have access to the this-> pointer inside this task. Is there anyway i can send the this-> pointer inside my task like in parameters or something like that ?
Here is some code:
class EraWiFi{
public:
EraWiFi();
/*!
* #brief Initialize WiFi
* #return Returns true if successful
*/
bool begin(fs::FS &fs);
/*!
* #brief Start EraWiFi service
* #return Returns true if successful
*/
void downloadWiFi();
private:
static void wifiTask(void * parameter);
};
bool EraWiFi::begin(fs::FS &fs){
File file = fs.open("/WiFi.json", FILE_READ);
if(file){
DynamicJsonDocument wifi(2048);
DeserializationError error = deserializeJson(wifi, file);
file.close();
if (!error){
Serial.println("We have a VALID WiFi.json configurations file!");
Serial.println("Adding wifi networks:");
for (JsonArray arr : wifi.as<JsonArray>()){
Serial.print("SSID: ");
Serial.print(arr[0].as<char*>());
Serial.print(", KEY: ");
Serial.println(arr[1].as<char*>());
wifiMulti.addAP(arr[0].as<char*>(), arr[1].as<char*>());
}
}else{
Serial.println("We don't have a VALID WiFi.json configurations file!");
}
}else{
Serial.println("There is no WiFi.json configurations file!");
}
wifiMulti.addAP("Testing", "1234");
xTaskCreate(
this->wifiTask, // Function that should be called
"WiFiTask", // Name of the task (for debugging)
10000, // Stack size (bytes)
(void*)&this, // Parameter to pass
1, // Task priority
NULL // Task handle
);
return true;
}
void EraWiFi::downloadWiFi(){
Serial.println("Downloading WiFi.json from ErabliTEK Server.");
HTTPClient http;
// Send request
http.useHTTP10(true);
String url = "https://testing.testing.com/wifis/getWiFi/";
http.begin(url);
int httpResponseCode = http.GET();
if(httpResponseCode == HTTP_CODE_OK){
// Parse response
DynamicJsonDocument doc(2048);
DeserializationError error = deserializeJson(doc, http.getStream());
// Disconnect
http.end();
if (!error){
File file = fs.open("/WiFi.json", FILE_WRITE);
if(file){
// Serialize JSON to file
if (serializeJson(doc, file) == 0) {
Serial.println("Error saving WiFi.json!");
Serial.println(http.getString());
}else{
Serial.println("Succesfully saved WiFi.json!");
}
}
// Close the file
file.close();
}else{
Serial.println("Error downloading WiFi.json");
}
}else{
Serial.println("Problem connecting to " + url + " with http code: " + String(httpResponseCode));
}
}
void EraWiFi::wifiTask(void * parameters){
bool wifiConnected = false;
for(;;){
uint8_t wifiStatus = wifiMulti.run();
if((wifiStatus != WL_CONNECTED) and wifiConnected) {
wifiConnected = false;
Serial.println("WiFi not connected!");
}else if((wifiStatus == WL_CONNECTED) and !wifiConnected){
wifiConnected = true;
Serial.println("WiFi Connected.");
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
Serial.print("KEY: ");
Serial.println(WiFi.psk());
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
EraWiFi::downloadWiFi();
}
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
You're already doing that, you just don't realize it. Have a look at the FreeRTOS documentation on xTaskCreate() which explains how the fourth parameter pvParameters is a void pointer that gets passed on to the new task as its (one and only) input parameter.
The commented line here means you're taking the address of your EraWiFi object and passing it to the task:
xTaskCreate(
this->wifiTask,
"WiFiTask",
10000,
(void*)&this, //< Pointer gets forwarded to the task
1,
NULL
);
To use the pointer inside your task, you only need to cast it back from void*:
void EraWiFi::wifiTask(void * parameters){
EraWifi* p = static_cast<EraWifi*>(parameters); //< That's the same pointer
p->someMethod();
...
PS. Also note (before you poke at the same object from different threads, possibly causing hard-to-find bugs) that FreeRTOS provides excellent inter-thread communication facilities - the queues and task notifications often being the most useful ones. Check out their tutorial/book for an explanation.

How to access strava API?

I am trying to access strava's API on arduino esp8266. My http.GET() using the ESP8266HTTPClient.h header is returning -1 which means that it fails to connect at some point (HTTPC_ERROR_CONNECTION_REFUSED). I am able to access other sites but am just having trouble linking up to Strava. Here is Strava's api site
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <ArduinoJson.h>
const char* ssid = "**SSIDNAME**";
const char* password = "**PASSWORD**";
void setup()
{
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.println("Connecting...");
}
}
void loop()
{
if (WiFi.status() == WL_CONNECTED)
{
Serial.println("Connected!");
HTTPClient http; //Object of class HTTPClient
http.begin("https://www.strava.com/athletes/**ATHLETENUMBER**", "**MY CLIENT SECRET**");
int httpCode = http.GET();
if (httpCode > 0)
{
const size_t bufferSize = JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(8) + 370;
DynamicJsonBuffer jsonBuffer(bufferSize);
JsonObject& root = jsonBuffer.parseObject(http.getString());
const char* miles = root["all_run_totals"];
Serial.print("Total miles:");
Serial.println(miles);
}
http.end(); //Close connection
}
delay(60000);
}
This API is accessible only by TLS/SSL. The code above works for APIs hosted on an insecure http site, but Strava and most other APIs nowadays force https. To access https there is a required fingerprint you will need to provide in the sketch. This fingerprint changes from time to time (the root CA certificate changes less often, maybe every 10 years, and can also be used). The following code will not provide any fingerprint and instead access that secure API insecurely. The problem with accessing an API without verifying the fingerprint is that someone can fake the server, but I expect that to not be an issue in most use cases. This code will GET from Strava's API every 15 minutes, parse the response to JSON, and print out total miles run for an athlete. (Replace **********'ed items).
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecureBearSSL.h>
#include <ArduinoJson.h>
ESP8266WiFiMulti WiFiMulti;
void setup() {
Serial.begin(115200);
delay(4000); //4 seconds
WiFi.mode(WIFI_STA);
WiFiMulti.addAP("*****ssid name******", "*******ssid password*******");
}
void loop() {
Serial.print(milesRun());
Serial.println(" miles");
delay(900000); //wait 15 minutes
}
int milesRun() { //connect to wifi, get miles run from strava api, disconnect from wifi
if ((WiFiMulti.run() == WL_CONNECTED)) {
std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure);
client->setInsecure();
HTTPClient https;
if (https.begin(*client, "https://www.strava.com/api/v3/athletes/*****athlete#*****/stats?access_token=*****access token here *****")) { // HTTPS
int httpCode = https.GET();
if (httpCode > 0) {
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
String payload = https.getString();
const size_t capacity = 6*JSON_OBJECT_SIZE(5) + 3*JSON_OBJECT_SIZE(6) + JSON_OBJECT_SIZE(11) + 1220;
DynamicJsonBuffer jsonBuffer(capacity);
JsonObject& root = jsonBuffer.parseObject(https.getString());
if (!root.success()) {
return 0; //json parsing failed
}
JsonObject& all_run_totals = root["all_run_totals"];
int meters = all_run_totals["distance"];
return meters * 0.000621371; //to miles
}
} else {
return 0; //error check httpCode
}
https.end();
} else {
return 0; //error unable to connect
}
}
}

Arduino turn char into a string

I am trying to turn a char into a string so I can extract the values I am interested in, however it just appears empty.
The variable I am interested in is content.
I am performing a get and it returns a JSON object. And want to extract the sunrise and sunset values.
#include <SPI.h>
#include <Ethernet2.h>
#include <String.h>
#include <stdlib.h>
EthernetClient client;
const char* server = "api.sunrise-sunset.org"; // server's address
const char* resource = "/json?lat=53.440&lng=0.200&date=today"; // http resource
const unsigned long BAUD_RATE = 9600; // serial connection speed
const unsigned long HTTP_TIMEOUT = 10000; // max respone time from server
const size_t MAX_CONTENT_SIZE = 512; // max size of the HTTP response
// ARDUINO entry point #1: runs once when you press reset or power the board
void setup() {
initSerial();
initEthernet();
}
// ARDUINO entry point #2: runs over and over again forever
void loop() {
if (connect(server)) {
if (sendRequest(server, resource) && skipResponseHeaders()) {
char response[MAX_CONTENT_SIZE];
String str(response);
Serial.println(str);
char* field;
char* sunset;
char* sunrise;
field = strtok(response,"{,");
while (field != NULL)
{
field = strtok (NULL, ",}");
if(field != NULL)
{
if(strstr(field, "sunrise") != NULL)
{
int length = strlen(field);
sunrise = new char[length + 1];
strncpy(sunrise, field, length + 1); // +1 to copy a terminating null as well
}
if(strstr(field, "sunset") != NULL)
{
int length = strlen(field);
sunset = new char[length + 1];
strncpy(sunset, field, length + 1); // +1 to copy a terminating null as well
}
}
}
//Serial.println("SUNRISE DATA: %s\n\n", sunrise);
//Serial.println("SUNSET DATA: %s\n\n", sunset);
free(sunrise); // do not forget to free the memory if not needed anymore
free(sunset); // do not forget to free the memory if not needed anymore
}
disconnect();
}
wait();
}
// Initialize Serial port
void initSerial() {
Serial.begin(BAUD_RATE);
while (!Serial) {
; // wait for serial port to initialize
}
Serial.println("Serial ready");
}
// Initialize Ethernet library
void initEthernet() {
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
byte ip[] = { 192,168,0,202 };
if (!Ethernet.begin(mac)) {
Serial.println("Failed to configure Ethernet");
return;
}
Serial.println("Ethernet ready");
delay(1000);
}
// Open connection to the HTTP server
bool connect(const char* hostName) {
Serial.print("Connect to ");
Serial.println(hostName);
bool ok = client.connect(hostName, 80);
Serial.println(ok ? "Connected" : "Connection Failed!");
return ok;
}
// Send the HTTP GET request to the server
bool sendRequest(const char* host, const char* resource) {
Serial.print("GET ");
Serial.println(resource);
client.print("GET ");
client.print(resource);
client.println(" HTTP/1.1");
client.print("Host: ");
client.println(server);
client.println("Connection: close");
client.println();
return true;
}
// Skip HTTP headers so that we are at the beginning of the response's body
bool skipResponseHeaders() {
// HTTP headers end with an empty line
char endOfHeaders[] = "\r\n\r\n";
client.setTimeout(HTTP_TIMEOUT);
bool ok = client.find(endOfHeaders);
if (!ok) {
Serial.println("No response or invalid response!");
}
return ok;
}
void disconnect() {
Serial.println("Disconnect");
client.stop();
}
// Pause for a 1 minute
void wait() {
Serial.println("Wait 60 seconds");
delay(60000);
}
I think there is a misunderstanding from your side. Certainly you want to process the response of the server and according to your code, this is char response[MAX_CONTENT_SIZE] where the response is stored.
Now this already is a string, more or less. An array of characters, chars. Definiton from here.
Strings are actually one-dimensional array of characters terminated by a null character '\0'. Thus a null-terminated string contains the characters that comprise the string followed by a null.
You can extract the relevant parts from it straight away.
Your response should look like something like this, according to sunrise-sunset.org/api. Note that I just copied the data into an array for testing purposes.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_CONTENT_SIZE 512
char response[MAX_CONTENT_SIZE] = \
"{\
\"results\":\
{\
\"sunrise\":\"7:27:02 AM\",\
\"sunset\":\"5:05:55 PM\",\
\"solar_noon\":\"12:16:28 PM\",\
\"day_length\":\"9:38:53\",\
\"civil_twilight_begin\":\"6:58:14 AM\",\
\"civil_twilight_end\":\"5:34:43 PM\",\
\"nautical_twilight_begin\":\"6:25:47 AM\",\
\"nautical_twilight_end\":\"6:07:10 PM\",\
\"astronomical_twilight_begin\":\"5:54:14 AM\",\
\"astronomical_twilight_end\":\"6:38:43 PM\"\
},\
\"status\":\"OK\"\
}";
You can easily process it using strtok function from string.h. Using a {, delimiter first will separate "result":{ from "sunrise .... Then you can use a }, delimiter.
With strstr you can check for "sunrise" and "sunset" field, and if you find them you can copy their value into a new string with strncpy.
int main()
{
char* field;
char* sunset;
char* sunrise;
field = strtok(response,"{,");
while (field != NULL)
{
field = strtok (NULL, ",}");
if(field != NULL)
{
if(strstr(field, "sunrise") != NULL)
{
int length = strlen(field);
sunrise = malloc(length * sizeof(char) + 1); // +1 for terminating null character '\0'
strncpy(sunrise, field, length + 1); // +1 to copy a terminating null as well
}
if(strstr(field, "sunset") != NULL)
{
int length = strlen(field);
sunset = malloc(length * sizeof(char) + 1); // +1 for terminating null character '\0'
strncpy(sunset, field, length + 1); // +1 to copy a terminating null as well
}
}
}
printf("SUNRISE DATA: %s\n\n", sunrise);
printf("SUNSET DATA: %s\n\n", sunset);
free(sunrise); // do not forget to free the memory if not needed anymore
free(sunset); // do not forget to free the memory if not needed anymore
return 0;
}
The output of this program is:
SUNRISE DATA: "sunrise":"7:27:02 AM"
SUNSET DATA: "sunset":"5:05:55 PM"
You can further process these strings with strtok again if you like. This is just an example code, you can use it to implement your solution.

Linkit One crashed after few hours while sending post

I'm bought a Linkit One last week and I'm trying to send http post (JSON) request to my remote server via sim card from the Linkit One board.
It all seems working fine but after couple hours those request stopped arriving to my server.
this is my code :
#include <LGPRS.h>
#include <LGPRSClient.h>
#include <LGPRSServer.h>
char server[] = "myserver.com";
int port = 80;
String object = "";
String Location = "";
int objSize;
String objLength;
LGPRSClient client;
void setup() {
Serial.begin(115200);
while (!LGPRS.attachGPRS("internet.golantelecom.net.il", "", "")) {
delay(500);
}
}
void loop() {
object = "value=test";
sendHttpRequest(object);
delay(5000);
}
void sendHttpRequest(String object) {
objSize = object.length();
objLength = String(objSize);
int timeOut = 0;
int index = 0;
String response = "";
if (client.connect(server, port)) {
// FOR THE CONSOLE :
Serial.println(F("POST /index.php HTTP/1.1"));
Serial.print(F("Host: "));
Serial.println(server);
Serial.println(F("Content-Type: application/x-www-form-urlencoded"));
Serial.print(F("Content-Length: "));
Serial.println(objLength);
Serial.println();
Serial.println(object);
Serial.println();
// FOR THE SERVER :
client.println(F("POST /index.php HTTP/1.1"));
client.print(F("Host: "));
client.println(server);
client.println(F("Content-Type: application/x-www-form-urlencoded"));
client.print(F("Content-Length: "));
client.println(objLength);
client.println();
client.println(object);
client.println();
}
else Serial.println("connection failed");
while (client.connected()) {
if (client.available() > 0) {
char value = client.read();
if(String(value) == "{" || index) {
response += String(value);
index++;
if(String(value) == "}") index = 0;
}
}
if (!client.connected() || timeOut == 35000) {
Serial.print("Server Response: ");
Serial.println(response);
Serial.println();
client.stop();
}
timeOut++;
}
}
And this is the log from the server :
http://s11.postimg.org/f6oriqj37/image.png
Please help me to figure out what is going on here..
thanks!
A new version of LinkIt ONE SDK v1.1 was released on 2/18/2015. You can try to download the latest version of SDK from this link, and update the firmware on the board accordingly. The companion firmware of LinkIt ONE SDK v2.0 is v1.1.05. Hope this resolves your issue.

Resources