diff --git a/lib/WeatherData/library.json b/lib/WeatherData/library.json new file mode 100644 index 0000000..7b6fd93 --- /dev/null +++ b/lib/WeatherData/library.json @@ -0,0 +1,7 @@ +{ + "name": "BlinkLed", + "version": "1.0.0", + "description": "A simple LED control library with internal LED helper.", + "authors": [{ "name": "TiiJay14" }], + "frameworks": "arduino" +} diff --git a/lib/WeatherData/src/WeatherFetcher.cpp b/lib/WeatherData/src/WeatherFetcher.cpp new file mode 100644 index 0000000..72d39fd --- /dev/null +++ b/lib/WeatherData/src/WeatherFetcher.cpp @@ -0,0 +1,187 @@ +#include "WeatherFetcher.h" +#include +#include +#include + +#include + +WeatherFetcher::WeatherFetcher(const char *api_key) : API_KEY(api_key) {} + +bool WeatherFetcher::connectToWiFi(const char *ssid, const char *password) +{ + ColorSerial::info("Verbinde mit WiFi: "); + ColorSerial::info(ssid); + + WiFi.begin(ssid, password); + + int attempts = 0; + while (WiFi.status() != WL_CONNECTED && attempts < 20) + { + delay(1000); + ColorSerial::info("."); + attempts++; + } + + ColorSerial::wifiStatus(); + + return WiFi.status() == WL_CONNECTED; +} + +Weather WeatherFetcher::actual_weather(const char *city, const char *lang, bool test_mode) +{ + if (test_mode) + { + return create_test_weather(); + } + + if (WiFi.status() != WL_CONNECTED) + { + Serial.println("Keine WiFi Verbindung!"); + return weather; + } + + String weather_url = + "https://api.weatherapi.com/v1/current.json?key=" + String(API_KEY) + + "&q=" + String(city) + "&aqi=yes&lang=" + String(lang); + + Serial.println("Hole Wetterdaten von: " + weather_url); + + String json_data = fetch_from_url(weather_url); + + if (!json_data.isEmpty()) + { + weather = parse_weather_json(json_data); + } + else + { + Serial.println("Fehler beim Abrufen der Wetterdaten!"); + } + + return weather; +} + +String WeatherFetcher::fetch_from_url(const String &url) +{ + HTTPClient http; + String payload = ""; + + http.begin(url); + http.setTimeout(10000); + + int httpCode = http.GET(); + + if (httpCode == HTTP_CODE_OK) + { + payload = http.getString(); + Serial.println("Daten erfolgreich empfangen"); + } + else + { + Serial.printf("HTTP Fehler: %d\n", httpCode); + Serial.println(http.errorToString(httpCode)); + } + + http.end(); + return payload; +} + +Weather WeatherFetcher::parse_weather_json(const String &json_data) +{ + Weather weather; + + const size_t capacity = JSON_OBJECT_SIZE(20) + JSON_OBJECT_SIZE(10) + JSON_OBJECT_SIZE(15) + 2048; + DynamicJsonDocument doc(capacity); + + DeserializationError error = deserializeJson(doc, json_data); + + if (error) + { + Serial.print("JSON Parsing fehlgeschlagen: "); + Serial.println(error.c_str()); + return weather; + } + + // Location Daten parsen - jetzt direkt mit Arduino String + if (doc.containsKey("location")) + { + JsonObject location = doc["location"]; + weather.location.name = location["name"].as(); + weather.location.region = location["region"].as(); + weather.location.country = location["country"].as(); + weather.location.localtime = location["localtime"].as(); + } + + // Current Condition Daten parsen + if (doc.containsKey("current")) + { + JsonObject current = doc["current"]; + weather.current.last_updated = current["last_updated"].as(); + weather.current.temp_c = current["temp_c"].as(); + weather.current.is_day = current["is_day"].as(); + weather.current.wind_kph = current["wind_kph"].as(); + weather.current.wind_dir = current["wind_dir"].as(); + weather.current.pressure_mb = current["pressure_mb"].as(); + weather.current.humidity = current["humidity"].as(); + weather.current.cloud = current["cloud"].as(); + weather.current.feelslike_c = current["feelslike_c"].as(); + + // Condition Daten parsen + if (current.containsKey("condition")) + { + JsonObject condition = current["condition"]; + weather.current.condition.text = condition["text"].as(); + weather.current.condition.icon = condition["icon"].as(); + } + + // Air Quality Daten parsen + if (current.containsKey("air_quality")) + { + JsonObject air_quality = current["air_quality"]; + weather.current.air_quality.co = air_quality["co"].as(); + weather.current.air_quality.no2 = air_quality["no2"].as(); + weather.current.air_quality.o3 = air_quality["o3"].as(); + weather.current.air_quality.so2 = air_quality["so2"].as(); + weather.current.air_quality.pm2_5 = air_quality["pm2_5"].as(); + weather.current.air_quality.pm10 = air_quality["pm10"].as(); + weather.current.air_quality.us_epa_index = air_quality["us-epa-index"].as(); + weather.current.air_quality.gb_defra_index = air_quality["gb-defra-index"].as(); + } + } + + Serial.println("Wetterdaten erfolgreich geparsed"); + return weather; +} + +Weather WeatherFetcher::create_test_weather() +{ + Weather test_weather; + + test_weather.location.name = "Grosshansdorf"; + test_weather.location.region = "Schleswig-Holstein"; + test_weather.location.country = "Germany"; + test_weather.location.localtime = "2023-10-01 12:30"; + + test_weather.current.last_updated = "2023-10-01 12:30"; + test_weather.current.temp_c = 15.5; + test_weather.current.is_day = 1; + test_weather.current.condition.text = "Sonnig"; + test_weather.current.condition.icon = "//cdn.weatherapi.com/weather/64x64/day/113.png"; + test_weather.current.wind_kph = 12.3; + test_weather.current.wind_dir = "NW"; + test_weather.current.pressure_mb = 1013.0; + test_weather.current.humidity = 65; + test_weather.current.cloud = 10; + test_weather.current.feelslike_c = 15.2; + + test_weather.current.air_quality.co = 250.0; + test_weather.current.air_quality.no2 = 12.5; + test_weather.current.air_quality.o3 = 45.2; + test_weather.current.air_quality.so2 = 2.1; + test_weather.current.air_quality.pm2_5 = 8.5; + test_weather.current.air_quality.pm10 = 12.3; + test_weather.current.air_quality.us_epa_index = 1; + test_weather.current.air_quality.gb_defra_index = 2; + + Serial.println("Test-Wetterdaten generiert"); + return test_weather; +} \ No newline at end of file diff --git a/lib/WeatherData/src/WeatherFetcher.h b/lib/WeatherData/src/WeatherFetcher.h new file mode 100644 index 0000000..909d0ba --- /dev/null +++ b/lib/WeatherData/src/WeatherFetcher.h @@ -0,0 +1,27 @@ +#ifndef WEATHER_FETCHER_H +#define WEATHER_FETCHER_H + +#include "WeatherStructs.h" +#include +#include + +class WeatherFetcher +{ +private: + Weather weather; + const char *API_KEY; + +public: + WeatherFetcher(const char *api_key); + bool connectToWiFi(const char *ssid, const char *password); + Weather actual_weather(const char *city = "grosshansdorf", + const char *lang = "de", + bool test_mode = false); + +private: + String fetch_from_url(const String &url); + Weather parse_weather_json(const String &json_data); + Weather create_test_weather(); +}; + +#endif \ No newline at end of file diff --git a/lib/WeatherData/src/WeatherStructs.h b/lib/WeatherData/src/WeatherStructs.h new file mode 100644 index 0000000..113f33c --- /dev/null +++ b/lib/WeatherData/src/WeatherStructs.h @@ -0,0 +1,53 @@ +#ifndef WEATHERSTRUCTS_H +#define WEATHERSTRUCTS_H + +#include + +struct Location +{ + String name = ""; + String region = ""; + String country = ""; + String localtime = ""; +}; + +struct Condition +{ + std::optional text; + std::optional icon; +}; + +struct AirQuality +{ + double co = 0.0; + double no2 = 0.0; + double o3 = 0.0; + double so2 = 0.0; + double pm2_5 = 0.0; + double pm10 = 0.0; + int us_epa_index = 0; + int gb_defra_index = 0; +}; + +struct CurrentCondition +{ + String last_updated; + double temp_c = 0.0; + int is_day = 0; + Condition condition; + double wind_kph = 0.0; + String wind_dir; + double pressure_mb = 0.0; + int humidity = 0; + int cloud = 0; + double feelslike_c = 0.0; + AirQuality air_quality; +}; + +struct Weather +{ + Location location; + CurrentCondition current; +}; + +#endif \ No newline at end of file diff --git a/restapi/mock-weather.json b/restapi/mock-weather.json new file mode 100644 index 0000000..a94606c --- /dev/null +++ b/restapi/mock-weather.json @@ -0,0 +1,57 @@ +{ + "location": { + "name": "Grosshansdorf", + "region": "Schleswig-Holstein", + "country": "Germany", + "lat": 53.6667, + "lon": 10.2833, + "tz_id": "Europe/Berlin", + "localtime_epoch": 1761383733, + "localtime": "2025-10-25 11:15" + }, + "current": { + "last_updated_epoch": 1761383700, + "last_updated": "2025-10-25 11:15", + "temp_c": 10.2, + "temp_f": 50.4, + "is_day": 1, + "condition": { + "text": "Overcast", + "icon": "//cdn.weatherapi.com/weather/64x64/day/122.png", + "code": 1009 + }, + "wind_mph": 17.7, + "wind_kph": 28.4, + "wind_degree": 208, + "wind_dir": "SSW", + "pressure_mb": 991.0, + "pressure_in": 29.26, + "precip_mm": 0.03, + "precip_in": 0.0, + "humidity": 82, + "cloud": 100, + "feelslike_c": 6.9, + "feelslike_f": 44.5, + "windchill_c": 5.4, + "windchill_f": 41.8, + "heatindex_c": 9.0, + "heatindex_f": 48.3, + "dewpoint_c": 6.3, + "dewpoint_f": 43.4, + "vis_km": 10.0, + "vis_miles": 6.0, + "uv": 0.1, + "gust_mph": 25.3, + "gust_kph": 40.7, + "air_quality": { + "co": 155.678, + "no2": 9.678, + "o3": 45.0, + "so2": 2.078, + "pm2_5": 4.678, + "pm10": 6.178, + "us-epa-index": 1, + "gb-defra-index": 1 + } + } +} diff --git a/restapi/weather.http b/restapi/weather.http new file mode 100644 index 0000000..33a6cec --- /dev/null +++ b/restapi/weather.http @@ -0,0 +1,12 @@ +@city = "grosshansdorf" +@lang = en +@forecast_days = 3 +# could be 60(mins) or 24(day) +@time_period_forecast = 60 +@key = 3545ce42d0ba436e8dc164532250410 + +### aktuelles Wetter +GET https://api.weatherapi.com/v1/current.json?key={{key}}&q={{city}}&aqi=yes&lang={{lang}} + +### Vorhersage für n Tage (max 3 Tage for Free-Plan!) +GET https://api.weatherapi.com/v1/forecast.json?q={{city}}&days={{forecast_days}}&tp={{time_period_forecast}}&key={{key}}&lang=de