Обработать строку ArduinoJson по https

Q-Tuzoff
Q-Tuzoff аватар
Offline
Зарегистрирован: 22.02.2018

Собственно, как?

Есть вот такая строка:

{"status":200,"message":"rates","data":{"USDRUB":"63.1457","EURRUB":"68.9687"}}

Нужно достать курсы валют из неё.

С народным мониторингом разобрался, работает, вот функция:

void GetNarodmon() {
  char host[] = "narodmon.ru";
  WiFiClient client;
  if (!client.connect(host, 80)) {
    Serial.println("connection failed");
    return;
  }
  client.println(F("GET /api/sensorsValues?sensors=106470,106471&uuid=842e159496e712&api_key=8QM HTTP/1.1"));
  client.println(F("Host: narodmon.ru"));
  client.println(F("Connection: close"));
  if (client.println() == 0) {
    Serial.println(F("Failed to send request"));
    return;
  }
  
    // Check HTTP status
  char status[32] = {0};
  client.readBytesUntil('\r', status, sizeof(status));
  // It should be "HTTP/1.0 200 OK" or "HTTP/1.1 200 OK"
  if (strcmp(status, "HTTP/1.1 200 OK") != 0) {
    Serial.print(F("Unexpected response: "));
    Serial.println(status);
    return;
  }

  // Skip HTTP headers
  char endOfHeaders[] = "\r\n\r\n";
  if (!client.find(endOfHeaders)) {
    Serial.println(F("Invalid response"));
    return;
  }

  const size_t capacity = JSON_ARRAY_SIZE(2) + JSON_OBJECT_SIZE(1) + 2*JSON_OBJECT_SIZE(6) + 170;
  DynamicJsonBuffer jsonBuffer(capacity);

  JsonObject& root = jsonBuffer.parseObject(client);
  if (!root.success()) {
    Serial.println(F("Parsing failed!"));
    return;
  }
  
  JsonObject& sensors_0 = root["sensors"][0];
  float sensors_0_value = sensors_0["value"]; // 748.37

  JsonObject& sensors_1 = root["sensors"][1];
  float sensors_1_value = sensors_1["value"]; // -4.1
  
  Serial.printf("Давление: %f\n", sensors_0_value);
  Serial.printf("Температура: %f\n", sensors_1_value);
}

В монитор порта всё выводится, приделал так же и OpenWeather, всё аналогично. А вот ни с одним HTTPS сайтом работать не получается. Может, кто подскажет что сделать? У HTTPS 443 порт, это я знаю. Пробовал client объявить WiFiClientSecure, вроде как соединяется, но ничего парсить оттуда не желает. Все валютные сайты находятся на HTTPS.

Застревает на этой строке client.readBytesUntil('\r', status, sizeof(status));

Статус читать не хочет. Присылает ответ 400.

Я уже ненавижу тех, кто придумал этот проклятый https с этим самым fingerprint'ом...

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Q-Tuzoff пишет:

Есть вот такая строка:

{"status":200,"message":"rates","data":{"USDRUB":"63.1457","EURRUB":"68.9687"}}

Нужно достать курсы валют из неё.

В монитор порта всё выводится ...

Так есть такая строка? Вы её получаете и печатаете в монитор порта? Я правильно понял? Или она только теоретически есть, а получить Вы её не можете?

Если она у Вас есть и в монитор порта печатается хорошо, то кто Вам мешает её распарсить как строку, а не вычитыванием из сети с помощью  client.readBytesUntil?

Т.е. я не понял суть проблемы.

Q-Tuzoff
Q-Tuzoff аватар
Offline
Зарегистрирован: 22.02.2018

ЕвгенийП пишет:
получить Вы её не можете

Не могу. Я так понимаю, проблема именно в https. Сайт - currate.ru.

Есть крайне кривая реализация, подсмотрел у AlexGyver'а. Там он берет библиотеку httpclient и добывает fingerprint со своего сайта, который (о, чудесное совпадение) просто http.

rkit
Offline
Зарегистрирован: 23.11.2016

У базовых ардуино не хватит сил для работы с ssl. И где ты нашел инструкцию по работе с http вручную - пометь себе, чтобы больше никогда не соваться в эту помойку.

b707
Offline
Зарегистрирован: 26.05.2017

как получить сертификат с нужного сайта и использовать его в коде

https://techtutorialsx.com/2017/11/18/esp32-arduino-https-get-request/

ЗЫ - сам не пробовал

rkit
Offline
Зарегистрирован: 23.11.2016

b707 пишет:

esp32

b707
Offline
Зарегистрирован: 26.05.2017

rkit пишет:

b707 пишет:

esp32

И что?

ТС ни словом не обмолвился, что у него за ардуина. А судя по тому, что он пытался использовать библиотеку WiFiSecure - у него либо 8266, либо ЕСП32

rkit
Offline
Зарегистрирован: 23.11.2016

Если esp, то есть готовая https библиотека, и этим бредом тем более не надо заниматься.

Q-Tuzoff
Q-Tuzoff аватар
Offline
Зарегистрирован: 22.02.2018

rkit пишет:

У базовых ардуино не хватит сил для работы с ssl

Ну, разумеется у меня не ардуина, а ESP, о чём и говорят строчки WiFiClient в моём коде. :)

Примеры встречал, но они какие-то слишком специфичные, вроде для чтения строки с реддита, не очень понятно как переделать под свои нужды, хоть и пытался.

Понимаю, что fingerprint вручную - это кошмарный велосипед Франкенштейна, но по крайней мере так работает хоть...

Потом вот пример есть https://sprut.ai/client/article/224, но тоже не понимаю как его переделать.

Q-Tuzoff
Q-Tuzoff аватар
Offline
Зарегистрирован: 22.02.2018

rkit пишет:

Если esp, то есть готовая https библиотека, и этим бредом тем более не надо заниматься.

Можно подробнее?

rkit
Offline
Зарегистрирован: 23.11.2016
Q-Tuzoff
Q-Tuzoff аватар
Offline
Зарегистрирован: 22.02.2018

Да не, не то всё же, это видел уже. Fingerprint вручную вводят. А если действие сертификата прекратится, тогда что? Опять залезать в код программы и вписывать другой? Конечно, можно поднять домашний сервер для его получения, но это, сами понимаете, бред. Сейчас так сделано уже, но оно как-то то работает, то нет. ArduinoJson надежнее в этом плане.

b707
Offline
Зарегистрирован: 26.05.2017

Q-Tuzoff пишет:

ArduinoJson надежнее в этом плане.

надежнее чем? тем что надежно не работает? Вы вроде с этим на форум пришли?

rkit
Offline
Зарегистрирован: 23.11.2016

Может быть откроешь документацию к библиотеке и посмотришь, не умеет ли она работать без фингерпринта? (подсказка: она умеет).

Причем тут arduinojson - я вообще хз, ты путаешь теплое с мягким.

Q-Tuzoff
Q-Tuzoff аватар
Offline
Зарегистрирован: 22.02.2018

Ну, у меня есть две реализации, одна через HTTPClient, другая - через Json и WiFiClient. Даже тот же http с народного мониторинга у первой нестабилен, проскакивают порой какие-то непонятные цифры.

Решил сделать через Json, столкнулся с проблемой https, вот и спрашиваю как сделать-то?

http://forum.amperka.ru/threads/esp8266-get-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81-%D0%B8%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%86%D0%B8%D0%B8-%D1%81-%D1%81%D0%B0%D0%B9%D1%82%D0%B0.11284/

Тут вот Gyver даже спрашивает как сделать, я так понимаю он для своего проекта хотел запросы с гугла делать, но тоже столкнулся с SSL проклятущим. И сделал он эти запросы через костыли в виде своего сайта, где добывается fingerprint для HTTPS, я даже нашел этот проект. Так и что делать людям, у которых нет своего сайта? Поднимать свой? Вручную вписывать fingerprint?

negavoid
Offline
Зарегистрирован: 09.07.2016

Да

Q-Tuzoff
Q-Tuzoff аватар
Offline
Зарегистрирован: 22.02.2018

negavoid пишет:

Да

Вы же несерьезно?)

Ну есть же реализации, тот же реддит, запрос для биткоинов. Как переделать под другой сайт эти примеры?

вот это https://sprut.ai/client/article/224

под сайт currate.ru

negavoid
Offline
Зарегистрирован: 09.07.2016

Раньше было только через фингерпринт.

Сейчас, говорят, можно без:

https://buger.dread.cz/simple-esp8266-https-client-without-verification-of-certificate-fingerprint.html

https://forum.arduino.cc/index.php?topic=643540.0

Если через сторонний сервер, то получить фингерпринт можно так (сорри, парсер обрамляет в <a>):

openssl s_client -connect http://www.server.com:443 | openssl x509 -fingerprint -noout

 

Q-Tuzoff
Q-Tuzoff аватар
Offline
Зарегистрирован: 22.02.2018

Спасибо, попробую.

Фингерпринт сейчас расшарен уже на локальном сервере, знаю как это, подсмотрел всё у того же Gyver'а PHP-код. Но это, повторюсь - большая ересь и дичь. С другой стороны, если посмотреть - это надо только мне, так что почему бы и нет... Только вот почему-то работает не очень стабильно. Иногда не успевает получить фингерпринт, а иногда ответ от сервера не приходит и вместо нормальных курсов пишет 0 и 1.99... Круто, обесцененные доллар и евро.)) narodmon и OpenWeather сделаны по примеру моего первого поста и здесь всё очень стабильно, ответ приходит всегда.

Q-Tuzoff
Q-Tuzoff аватар
Offline
Зарегистрирован: 22.02.2018

В общем, как говорится, есть хорошая новость и, естественно, плохая. 

void get_currate() {
  std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure);
  client->setInsecure();
  HTTPClient https;

  if (https.begin(*client, "https://currate.ru/api/?get=rates&pairs=USDRUB,EURRUB&key=f96bc28bab2")) {  // HTTPS
    Serial.println("[HTTPS] GET...");
    int httpCode = https.GET();

    // httpCode will be negative on error
    if (httpCode > 0) {
      // HTTP header has been send and Server response header has been handled
      Serial.printf("[HTTPS] GET... code: %d\n", httpCode);
      // file found at server?
      if (httpCode == HTTP_CODE_OK) {
        String payload = https.getString();
        const size_t capacity = JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(3) + 70;
        DynamicJsonBuffer jsonBuffer(capacity);
        
        JsonObject& root = jsonBuffer.parseObject(payload);
        
        float data_USDRUB = root["data"]["USDRUB"]; // "63.1457"
        float data_EURRUB = root["data"]["EURRUB"]; // "68.9687"
        Serial.printf("Доллар: %f\n", data_USDRUB);
        Serial.printf("Евро: %f\n", data_EURRUB);
      }
    } else {
      Serial.printf("[HTTPS] GET... failed, error: %s\n\r", https.errorToString(httpCode).c_str());
    }

    https.end();
  } else {
    Serial.printf("[HTTPS] Unable to connect\n\r");
  }
}

Работает этот код как надо, но лишь отдельно. Обрадовался сначала - "щас как внедрю, да как заработает!".

В моем большом проекте работать отказывается, уходит в ошибку Serial.printf("[HTTPS] GET... failed, error: %s\n\r", https.errorToString(httpCode).c_str());

С чем это связано? Без понятия, если честно. Даже в сетапе не хочет работать. Загоняю отдельно - работает. Поубирал из сетапа все свои функции кроме самых нужных - такая же ахинея, не работает и всё тут. Других реализаций HTTPclient в проекте больше нет.

dizzel
Offline
Зарегистрирован: 21.03.2016

Извините, что я немного не по теме. Вы утверждаете, что код в первом сообщении переделывается под OpenWaetherMap и успешно парсит прогноз на завтрашний день? Я просто сейчас уперся в реализацию выдергивания с помощью ArduinoJson данных из прогноза о погоде. Там просто вылетает огромная json-простыня по запросу и у моей ЕСП происходит банально overflow.

b707
Offline
Зарегистрирован: 26.05.2017

Q-Tuzoff пишет:

В моем большом проекте работать отказывается, уходит в ошибку Serial.printf("[HTTPS] GET... failed, error: %s\n\r", https.errorToString(httpCode).c_str());

С чем это связано?

может банально оперативки не хватает? JSON ответ в мегабайт или больше - обычное дело, а у вас в коде несколько запросов сразу...

Я конечно понимаю, что JSON это удобно, ничего знать не надо - но как оно обращается с памятью - точно не понятно. Парсить вручную может оказаться более эффективным, хотя конечно и более трудоемким

Q-Tuzoff
Q-Tuzoff аватар
Offline
Зарегистрирован: 22.02.2018

dizzel,

берете код json запроса и вставляете вот сюда и, собственно, всё. Вставляете полученный код в свой запрос и вместо переменной json (её можно вообще удалить) подставляете переменную из которой берется строка (в моём случае это client). Можно выводить переменные и делать с ними всё что угодно. Только обратите внимание на версию своей библиотеки arduinojson. Можно поступить еще проще, если Вам нужен только OpenWeather. Есть библиотека ESP8266_Weather_Station (по-моему, она есть в менеджере библиотек Arduino), можете в ней пример OpenWeatherMapCurrentDemo взять и сделать под себя.

b707,

понятно, попробую ESP32 взять. Возможно, 8266 не справляется с кучей кода и SSL запросом и даже json ни при чём. С fingerprint'ом опять же работает, но иногда не может получить и его с локального сервера. А вообще запросов немного ж: OpenWeather, narodmon и вот эта https валюта. С 3 запросами не справляется чтоль? При чём убирал OpenWeather и narodmon и всё равно не работает. Пробовал и другую реализацию через библиотеку ESP8266WiFi - отдельно работает тоже, а в общей программе так же не работает.

upd. Никто не знает случайно как на ESP32 сделать такой же защищенный запрос без фингерпринта? Библиотека ESP8266WiFi не работает с 32, а с WiFi.h ругается на BearSSL.

upd2. БИНГО! Разобрался. Достаточно использовать библиотеки WiFi.h и WiFiClientSecure.h. И без BearSSL всё работает в большой программе. Только вот проблема в том, что ESP32 - не Wemos.) Нужно кнопку нажимать(замыкать ногу) для прошивки. Придется курить OTA.

dizzel
Offline
Зарегистрирован: 21.03.2016

Да, я в курсе и про ArduinoJson и про ESP8266_Weather_Station. В первом варианте я не добился рабочего кода. В "Parsing program" парсится массив символов. Этот вариант отсек сразу. С массивом код вылетал с оверфлоу. Вариант с "serialize" не заработал. Ассистент для этого варианта не предлагает полного готового кода. Я пытался внедрить в него парсинг потока. Не заработало. В интернете есть примеры и инфа работы с разными версиями этой библиотеки, а для последней ничего этого нет, а она очень отличается.

ESP8266_Weather_Station вроде бы работает, но как она выдергивает прогнозы из сводки для меня загадка. Мне не удалось заставить ее парсить прогноз на следующий день на 12.00, например. Она предлагала 00.00, 09.00, 03.00 и т.д. и т.п. в разных комбинациях, но нужного не предлагала. Информации о том, как работает библиотека, что от чего зависит практически нет.

Однако выход я нашел. Это библиотека opeWeatherMap-ESP8266. Информации по ней тоже мало, но там хотя бы интуитивно понятно что делать чтобы получить нужный результат.

dizzel
Offline
Зарегистрирован: 21.03.2016

Короче, не получается с этой библиотекой. Сама по себе отдельно она работает, в отдельно написанном скетче. Вставляю скетч в свой код и ничего. То есть в момент когда начинается обработка запроса происходит что-то не понятное. Перестает работать выдача данных в серийный порт. ЕСП просто пропускает вообще все строки (даже обычные)  Serial.print и порт молчит.

Парсинг методом ArduinoJson текущей погоды у ЕСП проблем не вызывает. Там не так много данных.

Короче это мука.

Если парсить прогноз с ArduinoJson и брать код из ассистента в разделе "Parsing Program", то раскладывать не получается, так как судя по всему слишком большой объем информации. Массив const char* json оказывается пустым и все переменные с нулевыми значениями.

Пользоваться кодом из раздела "Serializing program" я просто не пойму как. Там не указывается что входит в функцию, т. е. что она берет из вне чтобы это потом распарсить. Я так понимаю это и есть метод парсинга из потока, но как его подсунуть не соображу.

Пожалуйста, подскажите мне, люди добрые. Я вообще не могу разобраться как это сделать, не хватает соображалки. Прошу вас подскажите как выпарсить уже этот треклятый прогноз.

Q-Tuzoff
Q-Tuzoff аватар
Offline
Зарегистрирован: 22.02.2018

dizzel,

а что мешает сделать выборку из парсера?

Не обязательно забирать все данные из полотна, это точно ни к чему. Возьмите из того запроса только те данные, которые Вам нужны.

Вот, например, на завтра это list[9] из запроса api.openweathermap.org/data/2.5/forecast?q=Moscow&appid=[ВАШ_API]&units=metric верно?

JsonObject list_9 = list[9];
long list_9_dt = list_9["dt"]; // 1582459200

JsonObject list_9_main = list_9["main"];
float list_9_main_temp = list_9_main["temp"]; // 1.25
float list_9_main_feels_like = list_9_main["feels_like"]; // -3.53
float list_9_main_temp_min = list_9_main["temp_min"]; // 1.25
float list_9_main_temp_max = list_9_main["temp_max"]; // 1.25
int list_9_main_pressure = list_9_main["pressure"]; // 996
int list_9_main_sea_level = list_9_main["sea_level"]; // 996
int list_9_main_grnd_level = list_9_main["grnd_level"]; // 975
int list_9_main_humidity = list_9_main["humidity"]; // 96
int list_9_main_temp_kf = list_9_main["temp_kf"]; // 0

JsonObject list_9_weather_0 = list_9["weather"][0];
int list_9_weather_0_id = list_9_weather_0["id"]; // 600
const char* list_9_weather_0_main = list_9_weather_0["main"]; // "Snow"
const char* list_9_weather_0_description = list_9_weather_0["description"]; // "light snow"
const char* list_9_weather_0_icon = list_9_weather_0["icon"]; // "13d"

int list_9_clouds_all = list_9["clouds"]["all"]; // 100

float list_9_wind_speed = list_9["wind"]["speed"]; // 4.14
int list_9_wind_deg = list_9["wind"]["deg"]; // 226

float list_9_snow_3h = list_9["snow"]["3h"]; // 0.75

const char* list_9_sys_pod = list_9["sys"]["pod"]; // "d"

const char* list_9_dt_txt = list_9["dt_txt"]; // "2020-02-23 12:00:00"

Так и берите только list[9] с нужной переменной.

Q-Tuzoff
Q-Tuzoff аватар
Offline
Зарегистрирован: 22.02.2018

dizzel пишет:

Пользоваться кодом из раздела "Serializing program" я просто не пойму как.

Этот раздел, насколько я понимаю, нужен для вывода данных на какую-либо веб-страницу в esp. То есть наоборот, обратный парсинг (возможно, ошибаюсь и тут подскажут знатоки библиотеки arduinojson). Но в любом случае Вам нужен именно "Parsing program".