999f35c28f6f158f27832d9dcf17b852c62743c6
[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         return;
93       }
94       weather.clear();
95       IterateJson(current_weather->as<JsonObject>(), "", weather);
96       weather_json = String();
97       delete current_weather;
98       processTemplates(weatherData,weather_template,weather,255);
99       sendWeather();
100       scroll(weatherData, !isNight());
101     }
102   }
103 }
104
105 void weatherRequest() {
106   if (!WiFi.isConnected()) { return; }
107   if (WiFi.isConnected() && weatherTicker) {
108     weatherTicker->detach();
109     int weather_min = cfg.getIntValue(F("weather_min"));
110     weather_min = weather_min ? weather_min : 60;
111     weatherTicker->attach(weather_min * 60, executeWeatherRequest);  // reschedule to requested interval
112   }
113   static bool requestOpenResult;
114   if (request.readyState() == readyStateUnsent || request.readyState() == readyStateDone) {
115     const char* weather_url = cfg.getCharValue(F("weather_url"));
116     requestOpenResult = request.open("GET", weather_url);
117
118     if (requestOpenResult) {
119       request.send();
120     } else {
121       Serial.println(F("Не удалось отправить запрос"));
122     }
123   } else {
124     Serial.println(F("Не удалось отправить запрос"));
125   }
126 }
127
128 void executeWeatherRequest() {
129   if (cfg.getBoolValue(F("enable_weather"))) {
130     request.onReadyStateChange(requestCB);
131     weatherRequest();
132   }
133 }
134
135 void setupWeatherRequest() {
136   if (weatherTicker) {
137     weatherTicker->detach();
138     delete weatherTicker;
139     weatherTicker = nullptr;
140   }
141   if (cfg.getBoolValue(F("enable_weather"))) {
142     weatherTicker = new Ticker;
143     weatherTicker->attach(30, executeWeatherRequest);  // before connection - every 30s
144   }
145 }