JSON Parser HTTP Request mit ESP8266

Moin,

habe schon hier im Forum geschaut und auch ein 2 Jahre alten post gefunden, der mir leider nicht weitergeholfen hat - die Basis des Codes habe ich auch benutzt.

So nun zum Problem. Habe eine Bekannte die anfängt zu camen (camgirl) und gerne einen "Abo Counter" haben möchte. Sowas kennt man ja schon von yt und co.

Der erste Teil wäre nun das parsen, wo ich ja nicht weiterkomme :frowning: ,danach müsste ich noch die Werte an die Led Segmente ausgeben.

Also mal im quelltext ihres Profils geschaut und sah ihre follower, User Agent verbietet mir aber das parsen.

Habe dann ihre Zugangsdaten bekommen und fand das ich mir ein Token generieren konnte, was ich auch gemacht habe.
Hier ist auch das Problem habe noch nie mit einer api/json gearbeitet.
Die meisten Beispiele sind in json v5, also kurz ArduinoJson gedowngradet.

So ist der aufbau der json:

{
"username": "abc",
"time_online": -1, 
"tips_in_last_hour": null, 
"num_followers": 1, <- das will ich haben
"token_balance": 0, <- das will ich haben
"satisfaction_score": 100, 
"num_tokened_viewers": 0, 
"votes_down": 0, 
"votes_up": 1, 
"last_broadcast": "2000-01-01T01:11:111.111111", 
"num_registered_viewers": 0, 
"num_viewers": 0
}

Mein jetziger Code:

#include <ESP8266HTTPClient.h>
#include <ArduinoJson.h>
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <WiFiUdp.h> 

WiFiClient client;
const char WiFiSSID[] = "wlanssid";     //### your Router SSID
const char WiFiPSK[]  = "wlanpw"; //### your Router Password

const char* server = "chaturbate.com";  // server's address
const char* resource = "/statsapi/?username=abc123&token=langertoken";      // http resource "Kanalinfo" 

const unsigned long BAUD_RATE = 115200;                 // 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
bool isConnected(long timeOutSec) {
  timeOutSec = timeOutSec * 1000;  int z = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(200);
    Serial.print(".");
    if (z == timeOutSec / 200) { return false; }
    z++;  }  return true;}

// The type of data that we want to extract from the page
struct UserData {
  char num_followers[32];
  char token_balance[32];
 };

// ARDUINO entry point #1: runs once when you press reset or power the board
void setup() {
  initSerial();
  WiFi.mode(WIFI_STA);
  WiFi.begin(WiFiSSID, WiFiPSK);
  if (isConnected(30)) {
    Serial.println(F("WLAN läuft"));     }}

// ARDUINO entry point #2: runs over and over again forever
void loop() {
  if (connect(server)) {
    if (sendRequest(server, resource) && skipResponseHeaders()) {
      UserData userData;
      if (readReponseContent(&userData)) {
        printUserData(&userData);
      }
    }
  }
  disconnect();
  wait();
}

// Initialize Serial port
void initSerial() {
  Serial.begin(BAUD_RATE);
  while (!Serial) {
    ;  // wait for serial port to initialize
  }
  Serial.println("Serial ready");
}

// 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.0");
  client.print("Host: ");
  client.println(host);
  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;
}

// Parse the JSON from the input string and extract the interesting values
// Here is the JSON we need to parse
//{
//  "username": "abc",
//  "time_online": -1, 
//  "tips_in_last_hour": null, 
//  "num_followers": 1, 
//  "token_balance": 0, 
//  "satisfaction_score": 100, 
//  "num_tokened_viewers": 0, 
//  "votes_down": 0, 
//  "votes_up": 1, 
//  "last_broadcast": "2000-01-01T01:11:118.111111", 
//  "num_registered_viewers": 0, 
//  "num_viewers": 0
//  }



bool readReponseContent(struct UserData* userData) {
  // Compute optimal size of the JSON buffer according to what we need to parse.
  // See https://bblanchon.github.io/ArduinoJson/assistant/
  const size_t BUFFER_SIZE =
      JSON_OBJECT_SIZE(1)    // the root object has 2 elements
      + JSON_OBJECT_SIZE(5)  // the "entity" object has 5 elements
      + MAX_CONTENT_SIZE;    // additional space for strings

  // Allocate a temporary memory pool
  DynamicJsonBuffer jsonBuffer(BUFFER_SIZE);
  JsonObject& root = jsonBuffer.parseObject(client);
  if (!root.success()) {
    Serial.println("JSON parsing failed!");
    return false;
  }

  // Here were copy the strings we're interested in
  strcpy(userData->num_followers, root["num_followers"]);
  strcpy(userData->token_balance, root["token_balance"]);
  // It's not mandatory to make a copy, you could just use the pointers
  // Since, they are pointing inside the "content" buffer, so you need to make
  // sure it's still in memory when you read the string
  return true;
}

// Print the data extracted from the JSON
void printUserData(const struct UserData* userData) {
  Serial.print("Deine Follower:");
  Serial.println(userData->num_followers);
  Serial.print("Deine Token:");
  Serial.println(userData->token_balance);
}

// Close the connection with the HTTP server
void disconnect() {
  Serial.println("Disconnect");
  client.stop();
}

// Pause for a 10 minute
void wait() {
  Serial.println("Wait 600 seconds");
  delay(600000);
}

Sehe gerade das ich #include <WiFiUdp.h> nicht brauche. Mein weiterer Gedanke war, dass das Programm vielleicht mit http die Anfrage macht. Aber in der url ist https, könnte hier ein Fehler sein?

Im Monitor bekomme ich

connect to chaturbate.com
connected
GET /statsapi/?username=abc123&token=langertoken
JSON parsing failed!
Disconnect

Habe mir auch schon überlegt den ganzen Code neu zumachen und gleich ArduinoJson v6 zu benutzten, sollte ich das lieber machen oder ist im obigem Code nur ein kleiner Fehler den ich übersehe?

Für HTTPS musst Du WiFiClientSecure benutzen und Dir den Fingerprint des Servers besorgen.
Erst wenn das läuft, macht es Sinn, dass Du Dich ums JSON-Parsen kümmerst.

Gruß Tommy

Danke, schau mir das im laufe des Tages mal an.

Da ich mich jetzt schon einige zeit mit Arduino JSON beschäfigt habe schaue mal auf die orginal Seite von Arduino JSON >Hier< dort gibt es einen Assistant dort kannst du dein JSON reinkopieren und er gibt die sogar die fertigen JSON Code aus um an deine Daten zu kommen. Man kann sogar wählren ob V5 oder V6.

Schaue die auch die Videos auf YT von Benoit Blanchon an der erklärt alles ziemlich "Userfreundlich" hat mir sehr geholfen JSON zu verstehen. Obwohl ich dann auch Arduino Streaming pasrer umschwenken musste da die JSON zu groß für die ESP82er wurde.

Gruß
DerDani

const size_t capacity = JSON_OBJECT_SIZE(12) + 260;
DynamicJsonBuffer jsonBuffer(capacity);

const char* json = "{\"username\":\"abc\",\"time_online\":-1,\"tips_in_last_hour\":null,\"num_followers\":1,\"token_balance\":0,\"satisfaction_score\":100,\"num_tokened_viewers\":0,\"votes_down\":0,\"votes_up\":1,\"last_broadcast\":\"2000-01-01T01:11:111.111111\",\"num_registered_viewers\":0,\"num_viewers\":0}";

JsonObject& root = jsonBuffer.parseObject(json);

const char* username = root["username"]; // "abc"
int time_online = root["time_online"]; // -1
int num_followers = root["num_followers"]; // 1
int token_balance = root["token_balance"]; // 0
int satisfaction_score = root["satisfaction_score"]; // 100
int num_tokened_viewers = root["num_tokened_viewers"]; // 0
int votes_down = root["votes_down"]; // 0
int votes_up = root["votes_up"]; // 1
const char* last_broadcast = root["last_broadcast"]; // "2000-01-01T01:11:111.111111"
int num_registered_viewers = root["num_registered_viewers"]; // 0
int num_viewers = root["num_viewers"]; // 0

Das kommt beim Assitant raus dort in den INts hast du dann alles stehen.

const size_t capacity = JSON_OBJECT_SIZE(12) + 240;
DynamicJsonDocument doc(capacity);

const char* json = "{\"username\":\"abc\",\"time_online\":-1,\"tips_in_last_hour\":null,\"num_followers\":1,\"token_balance\":0,\"satisfaction_score\":100,\"num_tokened_viewers\":0,\"votes_down\":0,\"votes_up\":1,\"last_broadcast\":\"2000-01-01T01:11:111.111111\",\"num_registered_viewers\":0,\"num_viewers\":0}";

deserializeJson(doc, json);

const char* username = doc["username"]; // "abc"
int time_online = doc["time_online"]; // -1
int num_followers = doc["num_followers"]; // 1
int token_balance = doc["token_balance"]; // 0
int satisfaction_score = doc["satisfaction_score"]; // 100
int num_tokened_viewers = doc["num_tokened_viewers"]; // 0
int votes_down = doc["votes_down"]; // 0
int votes_up = doc["votes_up"]; // 1
const char* last_broadcast = doc["last_broadcast"]; // "2000-01-01T01:11:111.111111"
int num_registered_viewers = doc["num_registered_viewers"]; // 0
int num_viewers = doc["num_viewers"]; // 0

Und das ist von der V6

Gerade noch etwas gesehen bzw hast du etwas verpasst an zu passen:

GET /statsapi/?username=abc123&token=langertoken

Ich denke statt "langertoken" sollte da tatsächlich der TOKEN also die Nummernfolge drin stehen.
Gruß
DerDani

Von Benoit Blanchon hatte ich schon ein Video geschaut, hatte nicht nachgesehen ob der mehr Videos hat die sich damit befassen.
Der Assistent gefällt mir.

volvodani:
Ich denke statt "langertoken" sollte da tatsächlich der TOKEN also die Nummernfolge drin stehen.

Im Originalem Code steht natürlich das alles :slight_smile:

Tommy56:
... den Fingerprint des Servers besorgen.

Hierzu eine frage. So wie ich das bis jetzt verstanden habe ist der Fingerprint das Zertifikat nur als hash(sha1)?

chUBIt:
Hierzu eine frage. So wie ich das bis jetzt verstanden habe ist der Fingerprint das Zertifikat nur als hash(sha1)?

Ja, den kannst Du Dir z.B. im Firefox holen: Klick auf das Schloss -> > Symbol --> Weitere Info --> Zertifikat anzeigen

Gruß Tommy

Wenn du schon HTTP von Hand machst (wenigstens HTTP/1.0 und nicht 1.1), könntest du zumindest ein Parsen des HTTP-Statuscodes einbauen. Und auf der Seriellen ausgeben, damit das Debuggen einfacher wird.

100 -> Vorläufiges "OK, kannst weitermachen"
200 -> Request erfolgreich
3xx -> Redirect
401 -> Nächstes mal mit Benutzer/Passwort
4xx -> Anfragefehler auf Client-seite (nochmal versuchen machts nicht besser)
5xx -> Fehler auf Server-seite (später nochmal versuchen machts vielleicht besser...)

(Mehr unter HTTP-Statuscode)

Habe den Code geändert und an dem gleichem Tag noch eine Antwort vom Server bekommen, schon mal so gut.

neuer Code und Antwort:

#define USING_AXTLS
#include <ESP8266WiFi.h>

// force use of AxTLS (BearSSL is now default)
#include <WiFiClientSecureAxTLS.h>
using namespace axTLS;

#ifndef STASSID
#define STASSID "wlan"
#define STAPSK  "passwort"
#endif

const char* ssid = STASSID;
const char* password = STAPSK;

const char* host = "chaturbate.com";
const int httpsPort = 443;

// Use web browser to view and copy ,
// SHA1 fingerprint of the certificate 
// chaturbate cert:81 60 fe d9 d9 71 b8 ac 69 69 9c e0 e0 dd ac 15 70 0b b7 16
// root cert:24 e3 6b 8a 1f 83 d1 34 60 09 c4 c9 bc d0 29 ca 7b 21 4e 36
//const char* fingerprint[] = "" <- Das war falsch bzw so keine Antwort vom Server
const char fingerprint[] PROGMEM = "24 e3 6b 8a 1f 83 d1 34 60 09 c4 c9 bc d0 29 ca 7b 21 4e 36"; 

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.print("connecting to ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  // Use WiFiClientSecure class to create TLS connection
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored  "-Wdeprecated-declarations"
  WiFiClientSecure client;
#pragma GCC diagnostic pop
  Serial.print("connecting to ");
  Serial.println(host);
  if (!client.connect(host, httpsPort)) {
    Serial.println("connection failed");
    return;
  }

  if (client.verify(fingerprint, host)) {
    Serial.println("certificate matches");
  } else {
    Serial.println("certificate doesn't match");
  }

  String url = "/apistats/?username=username&token=GdguVaSbgzQdjagb7q7icV3w";
  Serial.print("requesting URL: ");
  Serial.println(url);

  client.print(String("GET ") + url + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "User-Agent: Mozilla/4.0 (compatible; esp8266 Lua; Windows NT 5.1)\r\n" +
               "Connection: close\r\n\r\n");

  Serial.println("request sent");
  while (client.connected()) {
    String line = client.readStringUntil('\n');
    if (line == "\r") {
      Serial.println("headers received");
      break;
    }
  }
  String line = client.readStringUntil('\n');
  if (line.startsWith("{\"state\":\"success\"")) {
    Serial.println("esp8266/Arduino CI successfull!");
  } else {
    Serial.println("esp8266/Arduino CI has failed");
  }
  Serial.println("reply was:");
  Serial.println("==========");
  Serial.println(line);
  Serial.println("==========");
  Serial.println("closing connection");
}

void loop() {
}
09:40:28.822 -> WiFi connected
09:40:28.822 -> IP address: 
09:40:28.822 -> 192.168.2.139
09:40:28.822 -> connecting to chaturbate.com
09:40:29.226 -> 
09:40:29.226 -> Exception (3):
09:40:29.226 -> epc1=0x4020283b epc2=0x00000000 epc3=0x00000000 excvaddr=0x4024895e depc=0x00000000
09:40:29.226 -> 
09:40:29.226 -> >>>stack>>>
09:40:29.226 -> 
09:40:29.226 -> ctx: cont
09:40:29.226 -> sp: 3ffffd50 end: 3fffffc0 offset: 01a0
09:40:29.226 -> 3ffffef0:  000001bb 3ffe8783 3fffff40 402030df  
09:40:29.226 -> 3fffff00:  00000000 00000000 3ffe8783 3ffe85cb  
09:40:29.226 -> 3fffff10:  0000003b 3ffe8783 3ffe8783 40203185  
09:40:29.226 -> 3fffff20:  40206150 1f5a1268 40206150 3ffe85cb  
09:40:29.226 -> 3fffff30:  3ffe85bc 3ffe851c 3ffeea0c 40201170  
09:40:29.260 -> 3fffff40:  40206080 00000000 00003a98 feefeffe  
09:40:29.260 -> 3fffff50:  00000000 3ffef7bc 3ffef7f4 3ffef9a4  
09:40:29.260 -> 3fffff60:  feefeffe feefeffe feefeffe feefeffe  
09:40:29.260 -> 3fffff70:  feefeffe feefeffe feefeffe feefeffe  
09:40:29.260 -> 3fffff80:  feefeffe feefeffe feefeffe feefeffe  
09:40:29.260 -> 3fffff90:  feefeffe feefeffe feefeffe 3ffeea74  
09:40:29.260 -> 3fffffa0:  3fffdad0 00000000 3ffeea44 40203fb8  
09:40:29.294 -> 3fffffb0:  feefeffe feefeffe 3ffe8554 40100461  
09:40:29.294 -> <<<stack<<<
09:40:29.294 -> 
09:40:29.294 ->  ets Jan  8 2013,rst cause:2, boot mode:(3,2)
09:40:29.294 -> 
09:40:29.294 -> load 0x4010f000, len 1384, room 16 
09:40:29.294 -> tail 8
09:40:29.294 -> chksum 0x2d
09:40:29.294 -> csum 0x2d
09:40:29.294 -> v8b899c12
09:40:29.294 -> ~ld

Nun nach paar Tagen schaute ich nochmal im Monitor und bekomme nun Bad Request 400 HTTP 1.1, hatte vergessen von 1.1 auf 1.0 zu ändern.

Schnell geändert und wollte hochladen, neues Problem, sogut wie jede Zeile im abwechseln: error: stray '\240', error: stray '\302' in program.
Hatte schon paar Sachen versucht aber nichts geholfen. Fehlermeldung musste ich anhängen. Hier ein kleiner ausschnitt:

Chaturbate:32:1: error: stray '\302' in program

 const char* password = STAPSK;

 ^

Chaturbate:32:1: error: stray '\240' in program

Chaturbate:45:3: error: stray '\302' in program

    Serial.begin(115200);

   ^

Chaturbate:45:3: error: stray '\240' in program

Chaturbate:46:3: error: stray '\302' in program

    Serial.println();

   ^

Verstehe nun nicht mehr warum der Server mir keine Antwort gibt und warum ich diese Fehler bekomme. Der Code hat doch genau so paar Tage zuvor funktioniert ohne Meldungen.

fehlermeldung.txt (11.9 KB)

Der war hier 09:40:29.226 -> Exception (3): schon ausgestiegen.

Besorge Dir mal den Exceptiondecoder.

Das mit stray '\240' oder einer anderen Zahl sind Zeichen, die nicht in den Quellcode gehören. Hast Du den irgendwoher kopiert (Netz, Textverarbeitung/...).

Das wird mühsam. Nimm einen Editor, bei dem man die Kodierung umschalten kann z.N. Notepad++ und öffne deine ino darin. Wechsle zwischen ANSI und UTF8 ohne BOM hin und her und schau Dir die angezeigten Zeilen an. Da wirst Du irgendwelche komischen Zeichen finden. Die musst Du löschen.

Gruß Tommy

// force use of AxTLS (BearSSL is now default)

Hat das einen speziellen Grund?

Chaturbate:32:1: error: stray '\302' in program
Chaturbate:32:1: error: stray '\240' in program

Könnte ein "No break space" (in UTF-8 -> Octal) sein. Nimm mal die Leezeichen am Anfang der Zeile weg und mach die neu (ohne Shift zu drücken).