Fix Weather API request parse data and Spanish translation

pull/176/head
Eduardo 2025-09-17 19:17:41 +01:00
parent 3751c366ab
commit 04141cd4fb
100 changed files with 1785 additions and 84 deletions

0
printermonitor/OctoPrintClient.h Executable file → Normal file
View File

View File

@ -22,6 +22,7 @@ SOFTWARE.
*/
#include "OpenWeatherMapClient.h"
#include <WiFiClientSecure.h>
OpenWeatherMapClient::OpenWeatherMapClient(String ApiKey, int CityIDs[], int cityCount, boolean isMetric, String language) {
updateCityIdList(CityIDs, cityCount);
@ -31,6 +32,7 @@ OpenWeatherMapClient::OpenWeatherMapClient(String ApiKey, int CityIDs[], int cit
}
void OpenWeatherMapClient::updateWeatherApiKey(String ApiKey) {
ApiKey.trim();
myApiKey = ApiKey;
}
@ -42,100 +44,109 @@ void OpenWeatherMapClient::updateLanguage(String language) {
}
void OpenWeatherMapClient::updateWeather() {
WiFiClient weatherClient;
String apiGetData = "GET /data/2.5/group?id=" + myCityIDs + "&units=" + units + "&cnt=1&APPID=" + myApiKey + "&lang=" + lang + " HTTP/1.1";
WiFiClientSecure weatherClient;
weatherClient.setTimeout(15000);
weatherClient.setInsecure();
#ifdef ESP8266
weatherClient.setBufferSizes(1024, 512);
#endif
Serial.println("Obteniendo datos de clima");
Serial.println(apiGetData);
result = "";
if (weatherClient.connect(servername, 80)) { //starts client connection, checks for connection
weatherClient.println(apiGetData);
weatherClient.println("Host: " + String(servername));
weatherClient.println("User-Agent: ArduinoWiFi/1.1");
weatherClient.println("Connection: close");
weatherClient.println();
}
else {
Serial.println("conexión fallida al obtener datos del clima"); //error message if no client connect
Serial.println();
String firstId = myCityIDs;
int comma = firstId.indexOf(',');
if (comma > 0) firstId.remove(comma);
myApiKey.trim();
String path = "/data/2.5/weather?id=" + firstId +
"&units=" + units +
"&appid=" + myApiKey +
"&lang=" + lang;
Serial.println("\n[OWM] GET " + path);
if (!weatherClient.connect(servername, 443)) {
Serial.println("[OWM] Conexión TLS fallida");
weathers[0].cached = true;
weathers[0].error = "TLS connect fail";
return;
}
while(weatherClient.connected() && !weatherClient.available()) delay(1); //waits for data
weatherClient.print(String("GET ") + path + " HTTP/1.1\r\n" +
"Host: " + String(servername) + "\r\n" +
"User-Agent: ESP8266-OWM/1.0\r\n" +
"Connection: close\r\n\r\n");
Serial.println("Esperando por datos");
String statusLine = weatherClient.readStringUntil('\n');
statusLine.trim();
Serial.println("[OWM] " + statusLine);
if (!statusLine.startsWith("HTTP/1.1 ")) {
weathers[0].cached = true;
weathers[0].error = "Invalid HTTP";
return;
}
int httpCode = statusLine.substring(9, 12).toInt();
// Check HTTP status
char status[32] = {0};
weatherClient.readBytesUntil('\r', status, sizeof(status));
Serial.println("Response Header: " + String(status));
if (strcmp(status, "HTTP/1.1 200 OK") != 0) {
Serial.print(F("Unexpected response: "));
Serial.println(status);
if (!weatherClient.find("\r\n\r\n")) {
Serial.println("[OWM] No end-of-headers");
weathers[0].cached = true;
weathers[0].error = "Bad headers";
return;
}
// Skip HTTP headers
char endOfHeaders[] = "\r\n\r\n";
if (!weatherClient.find(endOfHeaders)) {
Serial.println(F("Respuesta no válida"));
return;
}
const size_t bufferSize = 710;
const size_t bufferSize = 4096;
DynamicJsonBuffer jsonBuffer(bufferSize);
JsonObject& root = jsonBuffer.parseObject(weatherClient);
if (!root.success()) {
Serial.println("[OWM] JSON parse FAIL");
weathers[0].cached = true;
weathers[0].error = "JSON parse fail";
return;
}
String cod = root["cod"].is<const char*>() ? (const char*)root["cod"] : String((int)root["cod"]);
if (cod != "200") {
weathers[0].cached = true;
weathers[0].error = root["message"].is<const char*>() ? (const char*)root["message"] : "cod!=200";
Serial.println("[OWM] API error: " + weathers[0].error);
return;
}
JsonObject& main = root["main"];
JsonObject& wind = root["wind"];
JsonObject& sys = root["sys"];
JsonArray& wArr = root["weather"];
weathers[0].lat = String((double)root["coord"]["lat"]);
weathers[0].lon = String((double)root["coord"]["lon"]);
weathers[0].dt = String((long) root["dt"]);
weathers[0].city = root["name"].is<const char*>() ? (const char*)root["name"] : "";
weathers[0].country = sys["country"].is<const char*>() ? (const char*)sys["country"] : "";
weathers[0].temp = String((double)main["temp"]);
weathers[0].humidity = String((int) main["humidity"]);
weathers[0].wind = String((double)wind["speed"]);
if (wArr.size() > 0) {
JsonObject& w0 = wArr[0];
weathers[0].condition = w0["main"].is<const char*>() ? (const char*)w0["main"] : "";
weathers[0].description = w0["description"].is<const char*>() ? (const char*)w0["description"] : "";
weathers[0].weatherId = String((int)w0["id"]);
weathers[0].icon = w0["icon"].is<const char*>() ? (const char*)w0["icon"] : "";
} else {
weathers[0].condition = "";
weathers[0].description = "";
weathers[0].weatherId = "";
weathers[0].icon = "";
}
weathers[0].cached = false;
weathers[0].error = "";
// Parse JSON object
JsonObject& root = jsonBuffer.parseObject(weatherClient);
if (!root.success()) {
Serial.println(F("Fallo al parsear los datos del clima!"));
weathers[0].error = "Fallo al parsear los datos del clima!";
return;
Serial.println("[OWM] OK city=" + weathers[0].city +
" temp=" + weathers[0].temp +
" icon=" + weathers[0].icon);
}
weatherClient.stop(); //stop client
if (root.measureLength() <= 150) {
Serial.println("Error No parece que hayamos obtenido los datos. Tamaño: " + String(root.measureLength()));
weathers[0].cached = true;
weathers[0].error = (const char*)root["message"];
Serial.println("Error: " + weathers[0].error);
return;
}
int count = root["cnt"];
for (int inx = 0; inx < count; inx++) {
weathers[inx].lat = (const char*)root["list"][inx]["coord"]["lat"];
weathers[inx].lon = (const char*)root["list"][inx]["coord"]["lon"];
weathers[inx].dt = (const char*)root["list"][inx]["dt"];
weathers[inx].city = (const char*)root["list"][inx]["name"];
weathers[inx].country = (const char*)root["list"][inx]["sys"]["country"];
weathers[inx].temp = (const char*)root["list"][inx]["main"]["temp"];
weathers[inx].humidity = (const char*)root["list"][inx]["main"]["humidity"];
weathers[inx].condition = (const char*)root["list"][inx]["weather"][0]["main"];
weathers[inx].wind = (const char*)root["list"][inx]["wind"]["speed"];
weathers[inx].weatherId = (const char*)root["list"][inx]["weather"][0]["id"];
weathers[inx].description = (const char*)root["list"][inx]["weather"][0]["description"];
weathers[inx].icon = (const char*)root["list"][inx]["weather"][0]["icon"];
Serial.println("lat: " + weathers[inx].lat);
Serial.println("lon: " + weathers[inx].lon);
Serial.println("dt: " + weathers[inx].dt);
Serial.println("city: " + weathers[inx].city);
Serial.println("country: " + weathers[inx].country);
Serial.println("temp: " + weathers[inx].temp);
Serial.println("humidity: " + weathers[inx].humidity);
Serial.println("condition: " + weathers[inx].condition);
Serial.println("wind: " + weathers[inx].wind);
Serial.println("weatherId: " + weathers[inx].weatherId);
Serial.println("description: " + weathers[inx].description);
Serial.println("icon: " + weathers[inx].icon);
Serial.println();
}
}
String OpenWeatherMapClient::roundValue(String value) {
float f = value.toFloat();

View File

@ -0,0 +1,311 @@
/** The MIT License (MIT)
Copyright (c) 2018 David Payne
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#include "OpenWeatherMapClient.h"
OpenWeatherMapClient::OpenWeatherMapClient(String ApiKey, int CityIDs[], int cityCount, boolean isMetric, String language) {
updateCityIdList(CityIDs, cityCount);
updateLanguage(language);
myApiKey = ApiKey;
setMetric(isMetric);
}
void OpenWeatherMapClient::updateWeatherApiKey(String ApiKey) {
myApiKey = ApiKey;
}
void OpenWeatherMapClient::updateLanguage(String language) {
lang = language;
if (lang == "") {
lang = "es";
}
}
void OpenWeatherMapClient::updateWeather() {
WiFiClient weatherClient;
String apiGetData = "GET /data/2.5/group?id=" + myCityIDs + "&units=" + units + "&cnt=1&APPID=" + myApiKey + "&lang=" + lang + " HTTP/1.1";
Serial.println("Obteniendo datos de clima");
Serial.println(apiGetData);
result = "";
if (weatherClient.connect(servername, 80)) { //starts client connection, checks for connection
weatherClient.println(apiGetData);
weatherClient.println("Host: " + String(servername));
weatherClient.println("User-Agent: ArduinoWiFi/1.1");
weatherClient.println("Connection: close");
weatherClient.println();
}
else {
Serial.println("conexión fallida al obtener datos del clima"); //error message if no client connect
Serial.println();
return;
}
while(weatherClient.connected() && !weatherClient.available()) delay(1); //waits for data
Serial.println("Esperando por datos");
// Check HTTP status
char status[32] = {0};
weatherClient.readBytesUntil('\r', status, sizeof(status));
Serial.println("Response Header: " + String(status));
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 (!weatherClient.find(endOfHeaders)) {
Serial.println(F("Respuesta no válida"));
return;
}
const size_t bufferSize = 710;
DynamicJsonBuffer jsonBuffer(bufferSize);
weathers[0].cached = false;
weathers[0].error = "";
// Parse JSON object
JsonObject& root = jsonBuffer.parseObject(weatherClient);
if (!root.success()) {
Serial.println(F("Fallo al parsear los datos del clima!"));
weathers[0].error = "Fallo al parsear los datos del clima!";
return;
}
weatherClient.stop(); //stop client
if (root.measureLength() <= 150) {
Serial.println("Error No parece que hayamos obtenido los datos. Tamaño: " + String(root.measureLength()));
weathers[0].cached = true;
weathers[0].error = (const char*)root["message"];
Serial.println("Error: " + weathers[0].error);
return;
}
int count = root["cnt"];
for (int inx = 0; inx < count; inx++) {
weathers[inx].lat = (const char*)root["list"][inx]["coord"]["lat"];
weathers[inx].lon = (const char*)root["list"][inx]["coord"]["lon"];
weathers[inx].dt = (const char*)root["list"][inx]["dt"];
weathers[inx].city = (const char*)root["list"][inx]["name"];
weathers[inx].country = (const char*)root["list"][inx]["sys"]["country"];
weathers[inx].temp = (const char*)root["list"][inx]["main"]["temp"];
weathers[inx].humidity = (const char*)root["list"][inx]["main"]["humidity"];
weathers[inx].condition = (const char*)root["list"][inx]["weather"][0]["main"];
weathers[inx].wind = (const char*)root["list"][inx]["wind"]["speed"];
weathers[inx].weatherId = (const char*)root["list"][inx]["weather"][0]["id"];
weathers[inx].description = (const char*)root["list"][inx]["weather"][0]["description"];
weathers[inx].icon = (const char*)root["list"][inx]["weather"][0]["icon"];
Serial.println("lat: " + weathers[inx].lat);
Serial.println("lon: " + weathers[inx].lon);
Serial.println("dt: " + weathers[inx].dt);
Serial.println("city: " + weathers[inx].city);
Serial.println("country: " + weathers[inx].country);
Serial.println("temp: " + weathers[inx].temp);
Serial.println("humidity: " + weathers[inx].humidity);
Serial.println("condition: " + weathers[inx].condition);
Serial.println("wind: " + weathers[inx].wind);
Serial.println("weatherId: " + weathers[inx].weatherId);
Serial.println("description: " + weathers[inx].description);
Serial.println("icon: " + weathers[inx].icon);
Serial.println();
}
}
String OpenWeatherMapClient::roundValue(String value) {
float f = value.toFloat();
int rounded = (int)(f+0.5f);
return String(rounded);
}
void OpenWeatherMapClient::updateCityIdList(int CityIDs[], int cityCount) {
myCityIDs = "";
for (int inx = 0; inx < cityCount; inx++) {
if (CityIDs[inx] > 0) {
if (myCityIDs != "") {
myCityIDs = myCityIDs + ",";
}
myCityIDs = myCityIDs + String(CityIDs[inx]);
}
}
}
void OpenWeatherMapClient::setMetric(boolean isMetric) {
if (isMetric) {
units = "metric";
} else {
units = "imperial";
}
}
String OpenWeatherMapClient::getWeatherResults() {
return result;
}
String OpenWeatherMapClient::getLat(int index) {
return weathers[index].lat;
}
String OpenWeatherMapClient::getLon(int index) {
return weathers[index].lon;
}
String OpenWeatherMapClient::getDt(int index) {
return weathers[index].dt;
}
String OpenWeatherMapClient::getCity(int index) {
return weathers[index].city;
}
String OpenWeatherMapClient::getCountry(int index) {
return weathers[index].country;
}
String OpenWeatherMapClient::getTemp(int index) {
return weathers[index].temp;
}
String OpenWeatherMapClient::getTempRounded(int index) {
return roundValue(getTemp(index));
}
String OpenWeatherMapClient::getHumidity(int index) {
return weathers[index].humidity;
}
String OpenWeatherMapClient::getHumidityRounded(int index) {
return roundValue(getHumidity(index));
}
String OpenWeatherMapClient::getCondition(int index) {
return weathers[index].condition;
}
String OpenWeatherMapClient::getWind(int index) {
return weathers[index].wind;
}
String OpenWeatherMapClient::getWindRounded(int index) {
return roundValue(getWind(index));
}
String OpenWeatherMapClient::getWeatherId(int index) {
return weathers[index].weatherId;
}
String OpenWeatherMapClient::getDescription(int index) {
return weathers[index].description;
}
String OpenWeatherMapClient::getIcon(int index) {
return weathers[index].icon;
}
boolean OpenWeatherMapClient::getCached() {
return weathers[0].cached;
}
String OpenWeatherMapClient::getMyCityIDs() {
return myCityIDs;
}
String OpenWeatherMapClient::getError() {
return weathers[0].error;
}
String OpenWeatherMapClient::getWeatherIcon(int index)
{
int id = getWeatherId(index).toInt();
String W = ")";
switch(id)
{
case 800: W = "B"; break;
case 801: W = "Y"; break;
case 802: W = "H"; break;
case 803: W = "H"; break;
case 804: W = "Y"; break;
case 200: W = "0"; break;
case 201: W = "0"; break;
case 202: W = "0"; break;
case 210: W = "0"; break;
case 211: W = "0"; break;
case 212: W = "0"; break;
case 221: W = "0"; break;
case 230: W = "0"; break;
case 231: W = "0"; break;
case 232: W = "0"; break;
case 300: W = "R"; break;
case 301: W = "R"; break;
case 302: W = "R"; break;
case 310: W = "R"; break;
case 311: W = "R"; break;
case 312: W = "R"; break;
case 313: W = "R"; break;
case 314: W = "R"; break;
case 321: W = "R"; break;
case 500: W = "R"; break;
case 501: W = "R"; break;
case 502: W = "R"; break;
case 503: W = "R"; break;
case 504: W = "R"; break;
case 511: W = "R"; break;
case 520: W = "R"; break;
case 521: W = "R"; break;
case 522: W = "R"; break;
case 531: W = "R"; break;
case 600: W = "W"; break;
case 601: W = "W"; break;
case 602: W = "W"; break;
case 611: W = "W"; break;
case 612: W = "W"; break;
case 615: W = "W"; break;
case 616: W = "W"; break;
case 620: W = "W"; break;
case 621: W = "W"; break;
case 622: W = "W"; break;
case 701: W = "M"; break;
case 711: W = "M"; break;
case 721: W = "M"; break;
case 731: W = "M"; break;
case 741: W = "M"; break;
case 751: W = "M"; break;
case 761: W = "M"; break;
case 762: W = "M"; break;
case 771: W = "M"; break;
case 781: W = "M"; break;
default:break;
}
return W;
}

0
printermonitor/OpenWeatherMapClient.h Executable file → Normal file
View File

0
printermonitor/RepetierClient.h Executable file → Normal file
View File

0
printermonitor/TimeClient.cpp Executable file → Normal file
View File

0
printermonitor/TimeClient.h Executable file → Normal file
View File

0
printermonitor/WeatherStationFonts.h Executable file → Normal file
View File

0
printermonitor/libs/ArduinoJson/ArduinoJson.h Executable file → Normal file
View File

0
printermonitor/libs/ArduinoJson/CHANGELOG.md Executable file → Normal file
View File

0
printermonitor/libs/ArduinoJson/CMakeLists.txt Executable file → Normal file
View File

0
printermonitor/libs/ArduinoJson/CONTRIBUTING.md Executable file → Normal file
View File

0
printermonitor/libs/ArduinoJson/LICENSE.md Executable file → Normal file
View File

0
printermonitor/libs/ArduinoJson/README.md Executable file → Normal file
View File

0
printermonitor/libs/ArduinoJson/SUPPORT.md Executable file → Normal file
View File

0
printermonitor/libs/ArduinoJson/appveyor.yml Executable file → Normal file
View File

0
printermonitor/libs/ArduinoJson/banner.svg Executable file → Normal file
View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

0
printermonitor/libs/ArduinoJson/keywords.txt Executable file → Normal file
View File

0
printermonitor/libs/ArduinoJson/src/ArduinoJson.h Executable file → Normal file
View File

0
printermonitor/libs/ArduinoJson/src/ArduinoJson.hpp Executable file → Normal file
View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

View File

@ -24,14 +24,14 @@ SOFTWARE.
// Additional Contributions:
/* 15 Jan 2019 : Owen Carter : Add psucontrol option and processing */
/* 18 Feb 2022 : Robert von Könemann @vknmnn : Lets us select Moonraker + fix usage of AMPM/24Hrs time */
/* 17 Sep 2025 : Eduardo Romero @eduardorq : Fix Weather API request parse data and Spanish translation */
/**********************************************
* Edit Settings.h for personalization
***********************************************/
#include "Settings.h"
#define VERSION "3.0"
#define VERSION "3.1"
#define HOSTNAME "PrintMon-"
#define CONFIG "/conf.txt"
@ -115,7 +115,7 @@ static const char WEB_ACTIONS[] PROGMEM = "<a class='w3-bar-item w3-button' hre
String CHANGE_FORM = ""; // moved to config to make it dynamic
static const char CLOCK_FORM[] PROGMEM = "<hr><p><input name='isClockEnabled' class='w3-check w3-margin-top' type='checkbox' %IS_CLOCK_CHECKED%> Mostrar reloj cuando la imrpesora esté apagada</p>"
static const char CLOCK_FORM[] PROGMEM = "<hr><p><input name='isClockEnabled' class='w3-check w3-margin-top' type='checkbox' %IS_CLOCK_CHECKED%> Mostrar reloj cuando la impresora esté apagada</p>"
"<p><input name='is24hour' class='w3-check w3-margin-top' type='checkbox' %IS_24HOUR_CHECKED%> Usar reloj 24 Horas</p>"
"<p><input name='invDisp' class='w3-check w3-margin-top' type='checkbox' %IS_INVDISP_CHECKED%> Voltear la orientación de la pantalla</p>"
"<p><input name='useFlash' class='w3-check w3-margin-top' type='checkbox' %USEFLASH%> Activar LED de WiFi</p>"
@ -577,10 +577,10 @@ void handleConfigure() {
server.sendContent(html);
CHANGE_FORM = "<form class='w3-container' action='/updateconfig' method='get'><h2>Configuración:</h2>"
"<p><label>" + printerClient.getPrinterType() + " API Key (se obtiene de OctoPrint)</label>"
"<p><label>" + printerClient.getPrinterType() + " API Key (se obtiene de " + printerClient.getPrinterType() + ")</label>"
"<input class='w3-input w3-border w3-margin-bottom' type='text' name='PrinterApiKey' id='PrinterApiKey' value='%OCTOKEY%' maxlength='60'></p>";
if (printerClient.getPrinterType() == "OctoPrint") {
CHANGE_FORM += "<p><label>" + printerClient.getPrinterType() + " Nombre de host (normalmente es octopi)</label><input class='w3-input w3-border w3-margin-bottom' type='text' name='PrinterHostName' value='%OCTOHOST%' maxlength='60'></p>";
CHANGE_FORM += "<p><label>" + printerClient.getPrinterType() + " Nombre de host </label><input class='w3-input w3-border w3-margin-bottom' type='text' name='PrinterHostName' value='%OCTOHOST%' maxlength='60'></p>";
}
CHANGE_FORM += "<p><label>" + printerClient.getPrinterType() + " Dirección IP (sin http://)</label>"
"<input class='w3-input w3-border w3-margin-bottom' type='text' name='PrinterAddress' id='PrinterAddress' value='%OCTOADDRESS%' maxlength='60'></p>"
@ -855,8 +855,11 @@ void displayPrinterStatus() {
} else {
html += "<div class='w3-cell-row' style='width:100%'><h2>" + weatherClient.getCity(0) + ", " + weatherClient.getCountry(0) + "</h2></div><div class='w3-cell-row'>";
html += "<div class='w3-cell w3-left w3-medium' style='width:120px'>";
html += "<img src='http://openweathermap.org/img/w/" + weatherClient.getIcon(0) + ".png' alt='" + weatherClient.getDescription(0) + "'><br>";
html += weatherClient.getHumidity(0) + "% Humedad<br>";
html += "<img src='https://openweathermap.org/img/wn/";
html += weatherClient.getIcon(0);
html += "@2x.png' alt='";
html += weatherClient.getDescription(0);
html += "'><br>";html += weatherClient.getHumidity(0) + "% Humedad<br>";
html += weatherClient.getWind(0) + " <span class='w3-tiny'>" + getSpeedSymbol() + "</span> Viento<br>";
html += "</div>";
html += "<div class='w3-cell w3-container' style='width:100%'><p>";

File diff suppressed because it is too large Load Diff