Погодный информер с дисплеем 128X64 narodmon/openweather/privatbank/covid/tp-link

p-a-h-a

★✩✩✩✩✩✩
18 Фев 2019
35
27
Привет, выкладываю промежуточный результат длительного проекта погодного информера, который берет прогноз погоды Openweathermap. Также можно выводить текущие данные с датчиков подключенных к Narodmon.ru
Железо: Дисплей 5$ на контроллере ST7920_128X64
ESP32 и соединительные провода.
Скетч содержит комментарии и состоит из множества коротких вкладок. Чтоб не быть галословным прикреплю фото что умеет:
изображение_viber_2021-02-11_23-41-47.jpg
Верхняя строка - данные с метеостанции (О ней написано тут https://community.alexgyver.ru/threads/meteostancija-narodnogo-monitoringa.3529/)
Если по какой-то причине нет подключения к народному мониторингу - данные берутся с Openweathermap.
Для повторения проекта обязательно необходимо зарегистрироваться на https://home.openweathermap.org/users/sign_in и получить APPID.
Также в случае использования своих, приватных датчиков необходимо получить ключ API на https://narodmon.com/ (профиль-мои программы - получить ключ API)
Для чтения своих приватных датчиков реализована авторизация на народном мониторинге. Одна проблема - ключ действует 3 месяца((

Данные по ковиду берутся для Украины, если захотите - разберетесь как поменять URL для другой страны.
Прогноз погоды выводится текущий, на сегодня и еще на 3 дня. Хотя скеч получает информацию на 7 дней.
Также программа интервально пингует гугл и при отсутствии пинга прибивает роутер (только TP-LINK) пока он не перезагрузится.
Если не нужна эта функция - достаточно закомментировать TPLINKreboot(); в loop.
Там же можно закомментировать POST_Narodmon(); и принимать погоду только Openweathermap.
Для формирования запроса о ковиде используется время NTP чтоб запросить данные за последние 2 дня и посчитать разницу (сколько добавилось..)
Так же реализована (но отключена) функция aGPS.
Микроконтроллер все время в глубоком сне, изредка просыпается обновляя информацию на дисплее и пингуя гугл.
Архив с проектом Arduino прикрепляю.
изображение_viber_2021-02-12_00-29-04.jpgизображение_viber_2021-02-12_00-29-05.jpgизображение_viber_2021-02-12_00-29-042.jpg
 

Вложения

Изменено:
  • Лойс +1
Реакции: bort707

p-a-h-a

★✩✩✩✩✩✩
18 Фев 2019
35
27
Актуальные вкладки проекта:
рекомендую #define debug false заменить на true
Openweather:
void Openweather() {
  const char openweatheGET[] PROGMEM = "http://api.openweathermap.org/data/2.5/onecall?";
  const char openweatheGETend[] PROGMEM = "&units=metric&lang=ru&exclude=minutely,hourly&appid=";
  static const char filter_arr_OWM[] PROGMEM = "{timezone:true,current:{temp:true,pressure:true,wind_speed:true,wind_deg:true,humidity:true,weather:[{id:true}]},daily:[{temp:{day:true,night:true},wind_speed:true,wind_deg:true,weather:[{id:true}]},{temp:{day:true,night:true},pressure:true,wind_speed:true,wind_deg:true,weather:[{id:true}]}]}";// 294 байта
  char openweatherHTTPaddres[strlen(openweatheGET) + strlen(openweatheLatLon) + strlen(openweatheGETend) + strlen(openweatherAPPID) + 1] = ""; // переменная в которой будет совмещен адрес запроса с токеном
  strcat(openweatherHTTPaddres, openweatheGET);// Копируем в переменную кусочки HTTPS запроса
  strcat(openweatherHTTPaddres, openweatheLatLon);
  strcat(openweatherHTTPaddres, openweatheGETend);
  strcat(openweatherHTTPaddres, openweatherAPPID);
  if (debug) Serial.println(openweatherHTTPaddres);
  HTTPClient http;
  http.begin(openweatherHTTPaddres);
  http.GET();
    StaticJsonDocument<496> filter;
  deserializeJson(filter, filter_arr_OWM);
  if (debug) {serializeJsonPretty(filter, Serial); Serial.println("\n\n\n\n\n\n");}

  DynamicJsonDocument Answer(2048);                                 // Инициализируем буфер под JSON // Эта константа определяет размер буфера под содержимое JSON 8ми датчиков (расчитывается тут https://arduinojson.org/v5/assistant/)
  deserializeJson(Answer, http.getString(), DeserializationOption::Filter(filter));                     // Парсим JSON-содержимое ответа сервера
  http.end();
  //Serial.println(Answer.as<String>()); //Выводим содержимое что прислал сервер
  if (debug) {serializeJsonPretty(Answer, Serial); Serial.println();} //Выводим содержимое что прислал сервер красиво по строчкам

  if (Answer["current"]["weather"][0]["id"].as<String>() == "null") {
    Serial.println("Данные openweathermap.org не получены. Перезагрузка.");
    ESP.restart();
  }
  boolean data_ok = true;
  if (Answer["current"]["temp"] == "null" || Answer["current"]["wind_speed"] == "null") {
    esp_sleep_enable_timer_wakeup(60e6);
    data_ok = false;
  }

  if (data_ok) { //Только если получили данные - обновляем их в переменных отображения на дисплее
    //Забиваем структуру вывода на дисплей текущей погодой
    LCD.temp = Answer["current"]["temp"]; //Температура
    LCD.wind_speed[0] = Answer["current"]["wind_speed"]; //Скорость ветра
    LCD.pressure = Answer["current"]["pressure"]; //Давление мПа
    LCD.pressure = LCD.pressure * 100 / 133; //Переводим в мм.рт.ст
    LCD.humidity = Answer["current"]["humidity"]; //Влажность
    LCD.wind_deg[0] = Answer["current"]["wind_deg"]; //Направление ветра
    LCD.ID_now = Answer["current"]["weather"][0]["id"].as<String>().toInt(); // id погоды
    //Забиваем структуру вывода на дисплей прогнозом на 4 дня
    for (int Day = 0; Day < 4; Day++) {
      LCD.temp_day[Day] = round(Answer["daily"][Day]["temp"]["day"]);
      LCD.temp_night[Day] = round(Answer["daily"][Day]["temp"]["night"]);
      LCD.ID[Day] = Answer["daily"][Day]["weather"][0]["id"].as<String>().toInt();
      LCD.wind_speed[Day] = Answer["daily"][Day]["wind_speed"];
      LCD.wind_deg[Day] = Answer["daily"][Day]["wind_deg"];
      LCD.wind[Day] = wind_deg_to_Word(Answer["daily"][Day]["wind_deg"]);
    }
  }

  if (debug) {
    //// Погода сейчас
    Serial.println("Данные openweathermap.org " + Answer["timezone"].as<String>());//город
    Serial.println("ID " + Answer["current"]["weather"][0]["id"].as<String>());// Иконка
    Serial.println("Температура " + Answer["current"]["temp"].as<String>()); // Температура
    Serial.println("Скорость ветра " + Answer["current"]["wind_speed"].as<String>() + " м/с"); // Скорость ветра
    Serial.println("Давление " + Answer["current"]["pressure"].as<String>() + " Па");
    Serial.println("Влажность " + Answer["current"]["humidity"].as<String>() + " %");
    Serial.println("Направление ветра " + Answer["current"]["wind_deg"].as<String>() + " градусов метеорологических");
    Serial.println("Направление ветра: " + wind_deg_to_Word(Answer["current"]["wind_deg"]) + "\r\n");

    ////Прогноз наперед
    time_t unix;
    time_t timezone = Answer["timezone_offset"].as<time_t>();
    for (byte Day = 1; Day < 8; Day++) {
      Serial.println(Day);
      Serial.println("Температура днем " + Answer["daily"][Day]["temp"]["day"].as<String>() + " 'С");
      Serial.println("Температура ночью " + Answer["daily"][Day]["temp"]["night"].as<String>() + " 'С");
      Serial.println("Ветер " + Answer["daily"][Day]["wind_speed"].as<String>() + " м/с");
      Serial.println("Направление ветра " + Answer["daily"][Day]["wind_deg"].as<String>() + " градусов метеорологических");
      Serial.println("Направление ветра: " + wind_deg_to_Word(Answer["daily"][Day]["wind_deg"]));
      Serial.println("Иконка " + Answer["daily"][Day]["weather"][0]["id"].as<String>() + "\r\n");
    }
  }
  Answer.clear();
}

String wind_deg_to_Word(int wind_deg) {
  if (wind_deg < 22) return "с"; // Инфа по переводу градусов в направление ветра тут https://studopedia.org/11-92976.html
  if (wind_deg < 45) return "ссв";
  if (wind_deg < 67) return "св";
  if (wind_deg < 90) return "всв";
  if (wind_deg < 112) return "в";
  if (wind_deg < 135) return "вюв";
  if (wind_deg < 157) return "юв";
  if (wind_deg < 180) return "ююв";
  if (wind_deg < 202) return "ю";
  if (wind_deg < 225) return "ююз";
  if (wind_deg < 247) return "юз";
  if (wind_deg < 270) return "зюз";
  if (wind_deg < 292) return "з";
  if (wind_deg < 315) return "зсз";
  if (wind_deg < 337) return "сз"; //сз
  if (wind_deg < 361) return "с";
  return "--";
}
Narodmon:
void POST_Narodmon() {
  HTTPClient http;
  http.begin("http://narodmon.ru/api");
  http.addHeader("Content-Type", "application/json");

  StaticJsonDocument<200> doc; // Создаем и наполняем json для последующей отправки на сервер
  doc["cmd"] = "sensorsValues";
  doc["sensors"] = SensorID;
  doc["uuid"] = MD5(WiFi.macAddress());
  doc["api_key"] = api_key;

  http.POST(doc.as<String>());// Json запрос на сервер
  if (debug) Serial.println(doc.as<String>());
  doc.clear();

  StaticJsonDocument<48> filter;
  filter["sensors"][0]["value"] = true;
  if (debug) {serializeJson(filter, Serial); Serial.println("\n");}

  DynamicJsonDocument Answer(300);                                 // Инициализируем буфер под JSON // Эта константа определяет размер буфера под содержимое JSON 8ми датчиков (расчитывается тут https://arduinojson.org/v5/assistant/)
  deserializeJson(Answer, http.getString(), DeserializationOption::Filter(filter));// Парсим JSON-содержимое ответа сервера
  http.end();
  //Serial.println(Answer.as<String>()); //Выводим содержимое что прислал сервер
  if (debug) {
    serializeJsonPretty(Answer, Serial);  //Выводим содержимое что прислал сервер красиво по строчкам
    Serial.println();
  }
  boolean data_ok=true;
  int sensorNum = NumberOFsensorsNarodmon();
  for (int i = 0; i < sensorNum; i++) {// Парсим значение датчиков и выводим в сериал
   String val = Answer["sensors"][i]["value"];
    if (val == "null"){
      LOGIN_Narodmon();//Если нет данных с датчика(приватного) то логинимся
      data_ok=false;
      esp_sleep_enable_timer_wakeup(60e6);
      break;
    }
  }
  if (data_ok){ //Только если получили данные - обновляем их в переменных отображения на дисплее
  LCD.temp=Answer["sensors"][3]["value"];
  LCD.temp=round(LCD.temp*10)/10;//Температура
  LCD.pressure=round(Answer["sensors"][2]["value"]);//Давление
  LCD.humidity=Answer["sensors"][1]["value"];//Влажность
//  LCD.battary_voltage=Answer["sensors"][4]["value"];//Батарея
  }
if (debug){
Serial.println("Время передачи " + Answer["sensors"][0]["value"].as<String>() + " сек");
Serial.println("Влажность " + Answer["sensors"][1]["value"].as<String>() + " %");
Serial.println("Атмосферное давление " + Answer["sensors"][2]["value"].as<String>() + " мм рт. ст.");
Serial.println("Температура на улице " + Answer["sensors"][3]["value"].as<String>() + " градусов цельсия");
Serial.println("Напряжение аккумулятора " + Answer["sensors"][4]["value"].as<String>() + " В");
Serial.println("Уровень сигнала WI-FI " + Answer["sensors"][5]["value"].as<String>() + "%");
Serial.println();
}
Answer.clear();

}


int NumberOFsensorsNarodmon() {
  int Num = 1;
  for (int i = 0; i < strlen(SensorID); i++) {
    Num += (SensorID[i] == ',');
  }
  Num *=(strlen(SensorID) != 0);
  return Num;
}
Login narodmon:
void LOGIN_Narodmon() {
  HTTPClient http;
  http.begin("http://narodmon.ru/api");
  http.addHeader("Content-Type", "application/x-www-form-urlencoded");

  StaticJsonDocument<256> doc;
  doc["cmd"] = "userLogon";
  doc["login"] = narodmonLogin;
  doc["hash"] = MD5(MD5(WiFi.macAddress()) + MD5(narodmonPassword));
  doc["uuid"] = MD5(WiFi.macAddress());
  doc["api_key"] = api_key;
  doc["lang"] = "ru";

  http.POST(doc.as<String>());// Json запрос на сервер
  if (debug) Serial.println(doc.as<String>());
  if (debug) Serial.println(http.getString());//Ответ сервера
  http.end();
  doc.clear();
}

String MD5(String buf) { // Считаем MD5 хэш для строки до 255 символов.
  unsigned char content[buf.length()], mbedtls_md5sum[16];
  for (byte i = 0; i < buf.length(); i++) {
    content[i] = buf.charAt(i);
  }  String MD5;
  int ret = mbedtls_md5_ret(content, buf.length(), mbedtls_md5sum);
  for (byte i = 0; i < 16; i++) {
    if (mbedtls_md5sum[i] < 0x10) MD5 += "0";
    MD5 += String(mbedtls_md5sum[i], HEX);//Serial.printf("%02x", mbedtls_md5sum[i]);
  } return MD5;
}
Privatbank:
void Privat24() {
  HTTPClient http;
  http.begin("https://api.privatbank.ua/p24api/pubinfo?json&exchange&coursid=5");
  http.GET();
  DynamicJsonDocument Answer(1200);                                 // Инициализируем буфер под JSON // Эта константа определяет размер буфера под содержимое JSON 8ми датчиков (расчитывается тут https://arduinojson.org/v5/assistant/)
  deserializeJson(Answer, http.getString());                     // Парсим JSON-содержимое ответа сервера
  http.end();
  //Serial.println(Answer.as<String>()); //Выводим содержимое что прислал сервер
  if (debug) {
    serializeJsonPretty(Answer, Serial); Serial.println(); //Выводим содержимое что прислал сервер красиво по строчкам
    Serial.print(String(Answer[0]["buy"].as<String>().substring(0, 5)) + " USD "); // Покупка USD
    Serial.println(Answer[0]["sale"].as<String>().substring(0, 5)); // Продажа USD
    Serial.print(String(Answer[1]["buy"].as<String>().substring(0, 5)) + " EUR "); // Покупка EUR
    Serial.println(Answer[1]["sale"].as<String>().substring(0, 5)); //Продажа EUR
  }
  boolean data_ok = true;
  if (Answer[0]["buy"] == "null" || Answer[1]["buy"] == "null") {
    esp_sleep_enable_timer_wakeup(60e6);
    data_ok = false;
  }

  if (data_ok) { //Только если получили данные - обновляем их в переменных отображения на дисплее
    LCD.USD[0] = Answer[0]["buy"];
    LCD.USD[1] = Answer[0]["sale"];
    LCD.EURO[0] = Answer[1]["buy"];
    LCD.EURO[1] = Answer[1]["sale"];
  }
    Answer.clear();
}
 
  • Лойс +1
Реакции: besfamilny

pushpop

✩✩✩✩✩✩✩
24 Фев 2021
7
2
p-a-h-a, а зачем ID датчиков стрингом в Json отправлять?
const char SensorID[] PROGMEM = "57282,51830,51843,54544,4309"; // Уникальные номера датчиков narodmon.com, с которых хотим видеть показани
сервер хоть и принимает такой вариант и обрабатывает без ошибок не правильнее ли будет массивом?

C++:
uint32_t SensorID[] = {57282,51830,51843,54544,4309};

  StaticJsonDocument<200> doc; // Создаем и наполняем json для последующей отправки на сервер
  
  doc["cmd"] = "sensorsValues";
  JsonArray sensors = doc.createNestedArray("sensors");
  for (byte i = 0; i < 5; i++){
  sensors.add(SensorID[i]); 
  }
  doc["uuid"] = MD5(WiFi.macAddress());
  doc["api_key"] = api_key;
запрос будет таким
C++:
  {"cmd":"sensorsValues","sensors":[57282,51830,51843,54544,4309],"uuid":"UUID","api_key":"API_KEY"}
я только изучаю тему и интересуюсь данным вопросом...
 

Kiber

✩✩✩✩✩✩✩
21 Мар 2023
1
0
@p-a-h-a,подскажите пожалуйста ,что может вызывать эту ошибку?
Код:
C:\Users\Кирик\Documents\Arduino\get_data_from_narodmon_ishodnik\get_data_from_narodmon_adaptation.ino\get_data_from_narodmon_adaptation.ino.ino: In function 'String MD5(String)':
C:\Users\Кирик\Documents\Arduino\get_data_from_narodmon_ishodnik\get_data_from_narodmon_adaptation.ino\get_data_from_narodmon_adaptation.ino.ino:134:66: error: 'mbedtls_md5_ret' was not declared in this scope

exit status 1

Compilation error: 'mbedtls_md5_ret' was not declared in this scope