Сохранение/восстановление/сброс настроек через веб-интерфейс.
[esp-clock.git] / weather.cpp
1 #include "Clock.h"
2 #include "AsyncHTTPRequest_Generic.h"
3 #include <Ticker.h>
4 #include <ArduinoJson.h>
5
6 Ticker* weatherTicker = nullptr;
7 AsyncHTTPRequest request;
8 Config weather;
9 char weatherData[256];
10
11 void executeWeatherRequest();
12
13 void IterateJson(JsonVariant json, const String& rootName, Config& lCfg);
14
15 void IterateJsonObject(JsonObject json, String rootName, Config& lCfg) {
16   for (JsonPair kv : json) {
17     IterateJson(kv.value(), rootName == "" ? String(kv.key().c_str()) : rootName + "." + String(kv.key().c_str()),lCfg);
18   }
19 }
20
21 void IterateJsonArray(JsonArray json, const String& rootName,  Config& lCfg) {
22   for (int i = 0; i < json.size(); i++) {
23     IterateJson(json[i], rootName == "" ? "[" + String(i) + "]" : rootName + "." + "[" + String(i) + "]", lCfg);
24   }
25 }
26
27 void IterateJson(JsonVariant json, const String& rootName, Config& lCfg) {
28   if (json.is<JsonObject>()) {
29     IterateJsonObject(json.as<JsonObject>(), rootName, lCfg);
30   } else if (json.is<JsonArray>()) {
31     IterateJsonArray(json.as<JsonArray>(), rootName, lCfg);
32   } else {
33     lCfg.setValue(rootName.c_str(), json.as<String>().c_str());
34   }
35 }
36
37 void processTemplates(char* buf, const char* strTemplate, const Config& cfg, int maxLen) {
38   buf[0] = 0;
39   char varName[34];
40   const char* ptr = strTemplate;
41   while (true) {
42     char* pos = strchr(ptr, '%');
43     if (!pos) {
44       strncat(buf, ptr, maxLen);
45       break;
46     }
47     int len = pos - ptr;
48     if (len + strlen(buf) >= maxLen) {
49       len = maxLen - strlen(buf);
50     }
51     int bufLen = strlen(buf);
52     strncpy(buf + bufLen, ptr, len);
53     buf[bufLen+len] = 0;
54     char* endpos = strchr(pos+1, '%');
55     if (!endpos) {
56       Serial.println(F("Незавершенное имя переменной"));
57       break;
58     }
59     int varLen = (endpos-1) - (pos);
60     if (varLen>64) {
61       Serial.println(F("Имя переменной слишком длинное"));
62       break;
63     }
64     if (varLen) {
65       strncpy(varName,pos+1,varLen);
66       varName[varLen] = 0;
67       const char* value = cfg.getCharValue(varName);
68       if (value) {
69         strncat(buf, value, maxLen);
70       } else {
71         strncat(buf, "??", maxLen);
72       }
73     } else {
74       strncat(buf, "%", maxLen);
75     }  
76     ptr = endpos+1;
77   }
78 }
79
80 void requestCB(void* optParm, AsyncHTTPRequest* request, int readyState) {
81   const char* weather_template = cfg.getCharValue(F("weather_template"));
82
83   if (readyState == readyStateDone) {
84     if (request->responseHTTPcode() == 200) {
85       String weather_json = request->responseText();
86       DynamicJsonDocument* current_weather = new DynamicJsonDocument(2048);
87       DeserializationError error = deserializeJson(*current_weather, weather_json);
88       if (error) {
89         Serial.print(F("Ошибка разбора ответа: "));
90         Serial.println(error.c_str());
91         Serial.println(weather_json);
92         reportMessage(F("Ошибка обновления погоды"));
93         return;
94       }
95       weather.clear();
96       IterateJson(current_weather->as<JsonObject>(), "", weather);
97       weather_json = String();
98       delete current_weather;
99       processTemplates(weatherData,weather_template,weather,255);
100       sendWeather();
101       reportMessage(F("Погода обновлена"));
102       scroll(weatherData, !isNight());
103     }
104   }
105 }
106
107 void weatherRequest() {
108   if (!WiFi.isConnected()) { return; }
109   if (WiFi.isConnected() && weatherTicker) {
110     weatherTicker->detach();
111     int weather_min = cfg.getIntValue(F("weather_min"));
112     weather_min = weather_min ? weather_min : 60;
113     weatherTicker->attach(weather_min * 60, executeWeatherRequest);  // reschedule to requested interval
114   }
115   static bool requestOpenResult;
116   if (request.readyState() == readyStateUnsent || request.readyState() == readyStateDone) {
117     const char* weather_url = cfg.getCharValue(F("weather_url"));
118     requestOpenResult = request.open("GET", weather_url);
119
120     if (requestOpenResult) {
121       request.send();
122     } else {
123       Serial.println(F("Не удалось отправить запрос"));
124     }
125   } else {
126     Serial.println(F("Не удалось отправить запрос"));
127   }
128 }
129
130 void executeWeatherRequest() {
131   if (cfg.getBoolValue(F("enable_weather"))) {
132     request.onReadyStateChange(requestCB);
133     weatherRequest();
134   }
135 }
136
137 void setupWeatherRequest() {
138   if (weatherTicker) {
139     weatherTicker->detach();
140     delete weatherTicker;
141     weatherTicker = nullptr;
142   }
143   if (cfg.getBoolValue(F("enable_weather"))) {
144     weatherTicker = new Ticker;
145     weatherTicker->attach(30, executeWeatherRequest);  // before connection - every 30s
146   }
147 }