|
@@ -43,8 +43,27 @@
|
43
|
43
|
#include "Functionality.h"
|
44
|
44
|
#include "SimpleUpdater.h"
|
45
|
45
|
#include "DebugLog.h"
|
|
46
|
+#include "BoolField.h"
|
46
|
47
|
#include "WifiStuff.h"
|
47
|
48
|
|
|
49
|
+#ifdef TELEGRAM_TOKEN
|
|
50
|
+
|
|
51
|
+#include <WiFiClientSecure.h>
|
|
52
|
+#include <UniversalTelegramBot.h>
|
|
53
|
+
|
|
54
|
+//#define TELEGRAM_LOG_TIMINGS
|
|
55
|
+
|
|
56
|
+#if defined(ARDUINO_ARCH_ESP8266)
|
|
57
|
+X509List cert(TELEGRAM_CERTIFICATE_ROOT);
|
|
58
|
+#endif
|
|
59
|
+
|
|
60
|
+WiFiClientSecure secured_client;
|
|
61
|
+UniversalTelegramBot bot(TELEGRAM_TOKEN, secured_client);
|
|
62
|
+unsigned long last_telegram_time = 0;
|
|
63
|
+String trusted_chat_ids[] = TRUSTED_IDS;
|
|
64
|
+
|
|
65
|
+#endif // TELEGRAM_TOKEN
|
|
66
|
+
|
48
|
67
|
UPDATE_WEB_SERVER server(80);
|
49
|
68
|
WebSocketsServer socket = WebSocketsServer(81);
|
50
|
69
|
SimpleUpdater updater;
|
|
@@ -100,6 +119,239 @@ void handleGpioTest() {
|
100
|
119
|
|
101
|
120
|
#endif // ENABLE_GPIO_TEST
|
102
|
121
|
|
|
122
|
+void wifi_broadcast_state_change(const char *s) {
|
|
123
|
+#ifdef TELEGRAM_TOKEN
|
|
124
|
+ for (int n = 0; n < (sizeof(trusted_chat_ids) / sizeof(trusted_chat_ids[0])); n++) {
|
|
125
|
+ bot.sendMessage(trusted_chat_ids[n], "New state: " + String(s), "");
|
|
126
|
+ }
|
|
127
|
+#endif // TELEGRAM_TOKEN
|
|
128
|
+}
|
|
129
|
+
|
|
130
|
+#ifdef TELEGRAM_TOKEN
|
|
131
|
+enum telegram_state {
|
|
132
|
+ BOT_IDLE,
|
|
133
|
+ BOT_ASKED_FERT,
|
|
134
|
+ BOT_ASKED_PLANTS,
|
|
135
|
+ BOT_ASKED_CONFIRM
|
|
136
|
+};
|
|
137
|
+
|
|
138
|
+enum telegram_state bot_state = BOT_IDLE;
|
|
139
|
+String bot_lock = "";
|
|
140
|
+BoolField bot_plants(VALVE_COUNT - 1);
|
|
141
|
+BoolField bot_ferts(PUMP_COUNT);
|
|
142
|
+
|
|
143
|
+unsigned long telegram_update_interval() {
|
|
144
|
+ if (bot_state == BOT_IDLE) {
|
|
145
|
+ return TELEGRAM_UPDATE_INTERVAL_SLOW;
|
|
146
|
+ } else {
|
|
147
|
+ return TELEGRAM_UPDATE_INTERVAL_FAST;
|
|
148
|
+ }
|
|
149
|
+}
|
|
150
|
+
|
|
151
|
+String telegram_help() {
|
|
152
|
+ String s = "Usage:\n";
|
|
153
|
+ s += "Send /auto and follow prompts.\n";
|
|
154
|
+ s += "Send /abort to cancel menus.\n";
|
|
155
|
+ s += "Send /none to skip menus.\n";
|
|
156
|
+ s += "Send /begin to confirm menus.";
|
|
157
|
+ return s;
|
|
158
|
+}
|
|
159
|
+
|
|
160
|
+void telegram_handle_message(int message_id) {
|
|
161
|
+ if (!sm_is_idle()) {
|
|
162
|
+ debug.println("Telegram: message while machine in use");
|
|
163
|
+
|
|
164
|
+ if (bot.messages[message_id].text == "/abort") {
|
|
165
|
+ sm_bot_abort();
|
|
166
|
+ bot.sendMessage(bot.messages[message_id].chat_id, "Aborted current cycle!", "");
|
|
167
|
+ } else {
|
|
168
|
+ bot.sendMessage(bot.messages[message_id].chat_id, "Machine is already in use.\nPlease try again later.", "");
|
|
169
|
+ }
|
|
170
|
+
|
|
171
|
+ return;
|
|
172
|
+ }
|
|
173
|
+
|
|
174
|
+ if ((bot_state == BOT_IDLE) && (bot_lock == "")) {
|
|
175
|
+ bot_lock = bot.messages[message_id].chat_id;
|
|
176
|
+ debug.println("Telegram: locked to " + bot_lock);
|
|
177
|
+ }
|
|
178
|
+
|
|
179
|
+ if (bot_lock != bot.messages[message_id].chat_id) {
|
|
180
|
+ debug.println("Telegram: bot locked. abort for chat " + bot.messages[message_id].chat_id);
|
|
181
|
+ bot.sendMessage(bot.messages[message_id].chat_id, "Bot is already in use.\nPlease try again later.", "");
|
|
182
|
+ return;
|
|
183
|
+ }
|
|
184
|
+
|
|
185
|
+ if (bot_state == BOT_IDLE) {
|
|
186
|
+ if (bot.messages[message_id].text == "/auto") {
|
|
187
|
+ String s = "Please enter fertilizer numbers.\n";
|
|
188
|
+ s += "Valid numbers: 1 to " + String(PUMP_COUNT) + "\n";
|
|
189
|
+ s += "Send /none to skip.\n";
|
|
190
|
+ s += "Send /abort to cancel.";
|
|
191
|
+ bot_ferts.clear();
|
|
192
|
+ bot_state = BOT_ASKED_FERT;
|
|
193
|
+ bot.sendMessage(bot.messages[message_id].chat_id, s, "");
|
|
194
|
+ } else if (bot.messages[message_id].text == "/abort") {
|
|
195
|
+ bot_lock = "";
|
|
196
|
+ bot.sendMessage(bot.messages[message_id].chat_id, "Nothing to abort.", "");
|
|
197
|
+ } else {
|
|
198
|
+ bot_lock = "";
|
|
199
|
+ bot.sendMessage(bot.messages[message_id].chat_id, telegram_help(), "");
|
|
200
|
+ }
|
|
201
|
+ } else if (bot_state == BOT_ASKED_FERT) {
|
|
202
|
+ if (bot.messages[message_id].text == "/abort") {
|
|
203
|
+ bot_state = BOT_IDLE;
|
|
204
|
+ bot_lock = "";
|
|
205
|
+ bot.sendMessage(bot.messages[message_id].chat_id, "Aborted.", "");
|
|
206
|
+ return;
|
|
207
|
+ } else if (bot.messages[message_id].text != "/none") {
|
|
208
|
+ String buff;
|
|
209
|
+ for (int i = 0; i < bot.messages[message_id].text.length() + 1; i++) {
|
|
210
|
+ if ((i == bot.messages[message_id].text.length()) || (bot.messages[message_id].text[i] == ' ')) {
|
|
211
|
+ if (buff.length() > 0) {
|
|
212
|
+ int n = buff.toInt() - 1;
|
|
213
|
+ buff = "";
|
|
214
|
+ bot_ferts.set(n);
|
|
215
|
+ }
|
|
216
|
+ } else if ((bot.messages[message_id].text[i] >= '0') && (bot.messages[message_id].text[i] <= '9')) {
|
|
217
|
+ buff += bot.messages[message_id].text[i];
|
|
218
|
+ } else {
|
|
219
|
+ bot_state = BOT_IDLE;
|
|
220
|
+ bot_lock = "";
|
|
221
|
+ bot.sendMessage(bot.messages[message_id].chat_id, "Invalid input.\nAborted.", "");
|
|
222
|
+ return;
|
|
223
|
+ }
|
|
224
|
+ }
|
|
225
|
+ }
|
|
226
|
+
|
|
227
|
+ String s = "Please enter plant numbers.\n";
|
|
228
|
+ s += "Valid numbers: 1 to " + String(VALVE_COUNT - 1) + "\n";
|
|
229
|
+ s += "Send /abort to cancel.";
|
|
230
|
+ bot_plants.clear();
|
|
231
|
+ bot_state = BOT_ASKED_PLANTS;
|
|
232
|
+ bot.sendMessage(bot.messages[message_id].chat_id, s, "");
|
|
233
|
+ } else if (bot_state == BOT_ASKED_PLANTS) {
|
|
234
|
+ if (bot.messages[message_id].text == "/abort") {
|
|
235
|
+ bot_state = BOT_IDLE;
|
|
236
|
+ bot_lock = "";
|
|
237
|
+ bot.sendMessage(bot.messages[message_id].chat_id, "Aborted.", "");
|
|
238
|
+ return;
|
|
239
|
+ }
|
|
240
|
+
|
|
241
|
+ String buff;
|
|
242
|
+ for (int i = 0; i < bot.messages[message_id].text.length() + 1; i++) {
|
|
243
|
+ if ((i == bot.messages[message_id].text.length()) || (bot.messages[message_id].text[i] == ' ')) {
|
|
244
|
+ if (buff.length() > 0) {
|
|
245
|
+ int n = buff.toInt() - 1;
|
|
246
|
+ buff = "";
|
|
247
|
+ bot_plants.set(n);
|
|
248
|
+ }
|
|
249
|
+ } else if ((bot.messages[message_id].text[i] >= '0') && (bot.messages[message_id].text[i] <= '9')) {
|
|
250
|
+ buff += bot.messages[message_id].text[i];
|
|
251
|
+ } else {
|
|
252
|
+ bot_state = BOT_IDLE;
|
|
253
|
+ bot_lock = "";
|
|
254
|
+ bot.sendMessage(bot.messages[message_id].chat_id, "Invalid input.\nAborted.", "");
|
|
255
|
+ return;
|
|
256
|
+ }
|
|
257
|
+ }
|
|
258
|
+
|
|
259
|
+ if (bot_plants.countSet() <= 0) {
|
|
260
|
+ bot_state = BOT_IDLE;
|
|
261
|
+ bot_lock = "";
|
|
262
|
+ bot.sendMessage(bot.messages[message_id].chat_id, "No plants selected.\nAborted.", "");
|
|
263
|
+ return;
|
|
264
|
+ }
|
|
265
|
+
|
|
266
|
+#ifdef FULLAUTO_MIN_PLANT_COUNT
|
|
267
|
+ if (bot_plants.countSet() < FULLAUTO_MIN_PLANT_COUNT) {
|
|
268
|
+ bot_state = BOT_IDLE;
|
|
269
|
+ bot_lock = "";
|
|
270
|
+ bot.sendMessage(bot.messages[message_id].chat_id, "Select at least " + String(FULLAUTO_MIN_PLANT_COUNT) + " plants.\nAborted.", "");
|
|
271
|
+ return;
|
|
272
|
+ }
|
|
273
|
+#endif
|
|
274
|
+
|
|
275
|
+ String s = "Input accepted.\nFertilizers:";
|
|
276
|
+ for (int i = 0; i < PUMP_COUNT; i++) {
|
|
277
|
+ if (bot_ferts.isSet(i)) {
|
|
278
|
+ s += " " + String(i + 1);
|
|
279
|
+ }
|
|
280
|
+ }
|
|
281
|
+ s += "\nPlants:";
|
|
282
|
+ for (int i = 0; i < (VALVE_COUNT - 1); i++) {
|
|
283
|
+ if (bot_plants.isSet(i)) {
|
|
284
|
+ s += " " + String(i + 1);
|
|
285
|
+ }
|
|
286
|
+ }
|
|
287
|
+ s += "\nOk? Send /begin to start or /abort to cancel.";
|
|
288
|
+ bot_state = BOT_ASKED_CONFIRM;
|
|
289
|
+ bot.sendMessage(bot.messages[message_id].chat_id, s, "");
|
|
290
|
+ } else if (bot_state == BOT_ASKED_CONFIRM) {
|
|
291
|
+ if (bot.messages[message_id].text == "/abort") {
|
|
292
|
+ bot_state = BOT_IDLE;
|
|
293
|
+ bot_lock = "";
|
|
294
|
+ bot.sendMessage(bot.messages[message_id].chat_id, "Aborted.", "");
|
|
295
|
+ } else if (bot.messages[message_id].text == "/begin") {
|
|
296
|
+ bot_state = BOT_IDLE;
|
|
297
|
+ bot_lock = "";
|
|
298
|
+ bot.sendMessage(bot.messages[message_id].chat_id, "Auto watering cycle started.", "");
|
|
299
|
+ sm_bot_start_auto(bot_ferts, bot_plants);
|
|
300
|
+ } else {
|
|
301
|
+ bot_state = BOT_IDLE;
|
|
302
|
+ bot_lock = "";
|
|
303
|
+ bot.sendMessage(bot.messages[message_id].chat_id, "Unknown message.\nAborted.", "");
|
|
304
|
+ }
|
|
305
|
+ } else {
|
|
306
|
+ debug.println("Telegram: invalid state");
|
|
307
|
+ bot_state = BOT_IDLE;
|
|
308
|
+ bot_lock = "";
|
|
309
|
+ bot.sendMessage(bot.messages[message_id].chat_id, "Internal error.\nPlease try again.", "");
|
|
310
|
+ }
|
|
311
|
+}
|
|
312
|
+
|
|
313
|
+void telegram_handler(int message_id) {
|
|
314
|
+ debug.println("Telegram: rx " + bot.messages[message_id].chat_id + " \"" + bot.messages[message_id].text + "\"");
|
|
315
|
+
|
|
316
|
+ bool found = false;
|
|
317
|
+ for (int n = 0; n < (sizeof(trusted_chat_ids) / sizeof(trusted_chat_ids[0])); n++) {
|
|
318
|
+ if (trusted_chat_ids[n] == bot.messages[message_id].chat_id) {
|
|
319
|
+ found = true;
|
|
320
|
+ break;
|
|
321
|
+ }
|
|
322
|
+ }
|
|
323
|
+ if (!found) {
|
|
324
|
+ bot.sendMessage(bot.messages[message_id].chat_id, "Sorry, not authorized!", "");
|
|
325
|
+ return;
|
|
326
|
+ }
|
|
327
|
+
|
|
328
|
+ telegram_handle_message(message_id);
|
|
329
|
+}
|
|
330
|
+
|
|
331
|
+void telegram_poll() {
|
|
332
|
+#ifdef TELEGRAM_LOG_TIMINGS
|
|
333
|
+ unsigned long start = millis();
|
|
334
|
+#endif // TELEGRAM_LOG_TIMINGS
|
|
335
|
+
|
|
336
|
+ while (int count = bot.getUpdates(bot.last_message_received + 1)) {
|
|
337
|
+ for (int i = 0; i < count; i++) {
|
|
338
|
+ telegram_handler(i);
|
|
339
|
+ }
|
|
340
|
+ }
|
|
341
|
+
|
|
342
|
+#ifdef TELEGRAM_LOG_TIMINGS
|
|
343
|
+ unsigned long end = millis();
|
|
344
|
+ debug.println("Telegram: took " + String(end - start) + "ms");
|
|
345
|
+#endif // TELEGRAM_LOG_TIMINGS
|
|
346
|
+}
|
|
347
|
+
|
|
348
|
+void telegram_hello() {
|
|
349
|
+ for (int n = 0; n < (sizeof(trusted_chat_ids) / sizeof(trusted_chat_ids[0])); n++) {
|
|
350
|
+ bot.sendMessage(trusted_chat_ids[n], "Giess-o-mat v" FIRMWARE_VERSION " initialized.\nSend /auto to begin.", "");
|
|
351
|
+ }
|
|
352
|
+}
|
|
353
|
+#endif // TELEGRAM_TOKEN
|
|
354
|
+
|
103
|
355
|
bool wifi_write_database(int duration, const char *type, int id) {
|
104
|
356
|
bool success = false;
|
105
|
357
|
|
|
@@ -610,6 +862,18 @@ void handleRoot() {
|
610
|
862
|
#endif
|
611
|
863
|
|
612
|
864
|
message += F("<p>\n");
|
|
865
|
+#ifdef TELEGRAM_TOKEN
|
|
866
|
+ message += F("Telegram: ");
|
|
867
|
+ message += TELEGRAM_UPDATE_INTERVAL_SLOW;
|
|
868
|
+ message += F("ms / ");
|
|
869
|
+ message += TELEGRAM_UPDATE_INTERVAL_FAST;
|
|
870
|
+ message += F("ms\n");
|
|
871
|
+#else
|
|
872
|
+ message += F("Telegram bot not enabled!\n");
|
|
873
|
+#endif
|
|
874
|
+ message += F("</p>\n");
|
|
875
|
+
|
|
876
|
+ message += F("<p>\n");
|
613
|
877
|
#ifdef ENABLE_INFLUXDB_LOGGING
|
614
|
878
|
message += F("InfluxDB: ");
|
615
|
879
|
message += INFLUXDB_DATABASE;
|
|
@@ -735,6 +999,7 @@ void handleRoot() {
|
735
|
999
|
|
736
|
1000
|
void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) {
|
737
|
1001
|
if ((type != WStype_TEXT) || (length != 1)) {
|
|
1002
|
+ debug.println("Websocket: invalid type=" + String(type) + " len=" + String(length) + " data=" + String((char *)payload));
|
738
|
1003
|
return;
|
739
|
1004
|
}
|
740
|
1005
|
|
|
@@ -764,6 +1029,10 @@ void wifi_setup() {
|
764
|
1029
|
debug.print("WiFi: connecting");
|
765
|
1030
|
WiFi.begin(WIFI_SSID, WIFI_PW);
|
766
|
1031
|
|
|
1032
|
+#ifdef TELEGRAM_TOKEN
|
|
1033
|
+ secured_client.setTrustAnchors(&cert);
|
|
1034
|
+#endif // TELEGRAM_TOKEN
|
|
1035
|
+
|
767
|
1036
|
while (((ws = WiFi.status()) != WL_CONNECTED) && (connect_attempts < MAX_WIFI_CONNECT_ATTEMPTS)) {
|
768
|
1037
|
connect_attempts++;
|
769
|
1038
|
debug.print(String(" ") + String(ws));
|
|
@@ -797,6 +1066,10 @@ void wifi_setup() {
|
797
|
1066
|
WiFi.mode(WIFI_STA);
|
798
|
1067
|
WiFi.begin(WIFI_SSID, WIFI_PW);
|
799
|
1068
|
|
|
1069
|
+#ifdef TELEGRAM_TOKEN
|
|
1070
|
+ secured_client.setCACert(TELEGRAM_CERTIFICATE_ROOT);
|
|
1071
|
+#endif // TELEGRAM_TOKEN
|
|
1072
|
+
|
800
|
1073
|
while (((ws = WiFi.status()) != WL_CONNECTED) && (connect_attempts < MAX_WIFI_CONNECT_ATTEMPTS)) {
|
801
|
1074
|
connect_attempts++;
|
802
|
1075
|
debug.print(String(" ") + String(ws));
|
|
@@ -838,10 +1111,34 @@ void wifi_setup() {
|
838
|
1111
|
#ifdef ENABLE_GPIO_TEST
|
839
|
1112
|
server.on("/gpiotest", handleGpioTest);
|
840
|
1113
|
#endif // ENABLE_GPIO_TEST
|
841
|
|
-
|
|
1114
|
+
|
|
1115
|
+#ifdef TELEGRAM_TOKEN
|
|
1116
|
+ debug.print("WiFi: getting NTP time");
|
|
1117
|
+ configTime(0, 0, "pool.ntp.org");
|
|
1118
|
+ time_t now = time(nullptr);
|
|
1119
|
+ while (now < 24 * 60 * 60) {
|
|
1120
|
+ debug.print(".");
|
|
1121
|
+ delay(100);
|
|
1122
|
+ now = time(nullptr);
|
|
1123
|
+ }
|
|
1124
|
+ debug.println(" done!");
|
|
1125
|
+ debug.println("WiFi: time is " + String(now));
|
|
1126
|
+
|
|
1127
|
+ debug.println("WiFi: initializing Telegram");
|
|
1128
|
+ const String commands = F("["
|
|
1129
|
+ "{\"command\":\"auto\", \"description\":\"Start automatic watering cycle\"},"
|
|
1130
|
+ "{\"command\":\"confirm\", \"description\":\"Proceed with any menu inputs\"},"
|
|
1131
|
+ "{\"command\":\"none\", \"description\":\"Proceed without menu input\"},"
|
|
1132
|
+ "{\"command\":\"abort\", \"description\":\"Cancel any menu inputs\"}"
|
|
1133
|
+ "]");
|
|
1134
|
+ bot.setMyCommands(commands);
|
|
1135
|
+ telegram_hello();
|
|
1136
|
+#endif // TELEGRAM_TOKEN
|
|
1137
|
+
|
842
|
1138
|
server.begin();
|
843
|
1139
|
MDNS.addService("http", "tcp", 80);
|
844
|
|
-
|
|
1140
|
+ MDNS.addService("http", "tcp", 81);
|
|
1141
|
+
|
845
|
1142
|
socket.begin();
|
846
|
1143
|
socket.onEvent(webSocketEvent);
|
847
|
1144
|
|
|
@@ -880,6 +1177,13 @@ void wifi_run() {
|
880
|
1177
|
runGpioTest(gpioTestState);
|
881
|
1178
|
}
|
882
|
1179
|
#endif // ENABLE_GPIO_TEST
|
|
1180
|
+
|
|
1181
|
+#ifdef TELEGRAM_TOKEN
|
|
1182
|
+ if ((millis() - last_telegram_time) >= telegram_update_interval()) {
|
|
1183
|
+ telegram_poll();
|
|
1184
|
+ last_telegram_time = millis();
|
|
1185
|
+ }
|
|
1186
|
+#endif // TELEGRAM_TOKEN
|
883
|
1187
|
}
|
884
|
1188
|
|
885
|
1189
|
#endif // PLATFORM_ESP
|