From ba30af518260a0ccdf948757d4dcca60ede8084d Mon Sep 17 00:00:00 2001 From: Dmitry Borisenko <49808844+DmitryBorisenko33@users.noreply.github.com> Date: Sun, 5 Apr 2020 01:52:02 +0200 Subject: [PATCH] add udp support and some new functions --- Init.ino | 39 ++-- SSDP.ino | 49 ----- Upgrade.ino | 38 ++-- WiFi.ino | 19 +- data/config-all.json | 18 ++ data/config-my.json | 18 -- data/config.json | 18 +- data/configuration.json | 14 +- data/dev.json | 65 ++++++ data/favicon.ico | Bin 1150 -> 1150 bytes data/index.htm.gz | Bin 1227 -> 1227 bytes data/index.json | 21 +- data/js/build.chart.js.gz | Bin 12758 -> 12758 bytes data/js/function.js.gz | Bin 18228 -> 18562 bytes data/mqtt.json | 200 +++++++++--------- data/pushingbox.json | 4 +- data/setup.json | 8 +- date_excess/favicon.ico | Bin 0 -> 1150 bytes {data => date_excess}/page.htm.gz | Bin ...2-esp8266_iot-manager_modules_firmware.ino | 30 ++- mqtt.ino | 27 ++- set.h | 31 ++- udp.ino | 122 +++++++++++ 23 files changed, 464 insertions(+), 257 deletions(-) delete mode 100644 SSDP.ino create mode 100644 data/config-all.json delete mode 100644 data/config-my.json create mode 100644 data/dev.json create mode 100644 date_excess/favicon.ico rename {data => date_excess}/page.htm.gz (100%) create mode 100644 udp.ino diff --git a/Init.ino b/Init.ino index 44c68d88..ace8b18b 100644 --- a/Init.ino +++ b/Init.ino @@ -1,24 +1,33 @@ void All_init() { - server.on("/all_modules_init", HTTP_GET, [](AsyncWebServerRequest * request) { - Device_init(); - request->send(200, "text/text", "OK"); // отправляем ответ о выполнении - }); - server.on("/scenario", HTTP_GET, [](AsyncWebServerRequest * request) { - if (request->hasArg("status")) { - jsonWriteStr(configSetup, "scenario", request->getParam("status")->value()); + server.on("/init", HTTP_GET, [](AsyncWebServerRequest * request) { + String value; + if (request->hasArg("arg")) { + value = request->getParam("arg")->value(); + } + if (value == "0") { + jsonWriteStr(configSetup, "scenario", value); + saveConfig(); + Scenario_init(); + } + if (value == "1") { + jsonWriteStr(configSetup, "scenario", value); + saveConfig(); + Scenario_init(); + } + if (value == "2") { + Device_init(); + } + if (value == "3") { + clean_log_date(); + } + if (value == "4") { + Scenario_init(); } - saveConfig(); - Scenario_init(); - request->send(200, "text/text", "OK"); // отправляем ответ о выполнении - }); - server.on("/cleanlog", HTTP_GET, [](AsyncWebServerRequest * request) { - clean_log_date(); request->send(200, "text/text", "OK"); // отправляем ответ о выполнении }); prsets_init(); - Device_init(); Scenario_init(); Timer_countdown_init(); @@ -134,7 +143,7 @@ void prsets_init() { } Device_init(); Scenario_init(); - request->redirect("/page.htm?configuration"); + request->redirect("/?configuration"); }); } diff --git a/SSDP.ino b/SSDP.ino deleted file mode 100644 index 2638e15a..00000000 --- a/SSDP.ino +++ /dev/null @@ -1,49 +0,0 @@ -void SSDP_init() { - server.on("/ssdp", HTTP_GET, [](AsyncWebServerRequest * request) { - if (request->hasArg("ssdp")) { - jsonWriteStr(configSetup, "SSDP", request->getParam("ssdp")->value()); - jsonWriteStr(configJson, "SSDP", request->getParam("ssdp")->value()); - } - saveConfig(); - request->send(200, "text/text", "OK"); - }); -} - -/* - // --------------------Получаем ssdp со страницы - server.on("/ssdp", HTTP_GET, [](AsyncWebServerRequest * request) { - if (request->hasArg("ssdp")) { - jsonWriteStr(configSetup, "SSDP", request->getParam("ssdp")->value()); - jsonWriteStr(configJson, "SSDP", request->getParam("ssdp")->value()); - SSDP.setName(jsonRead(configSetup, "SSDP")); - } - saveConfig(); - request->send(200, "text/text", "OK"); - }); - - // SSDP дескриптор - server.on("/description.xml", [](AsyncWebServerRequest * request) { - //SSDP.schema(http.client()); - request->send(200, "text/text", "OK"); - }); - - - if (WiFi.status() == WL_CONNECTED) { - //Если версия 2.0.0 закаментируйте следующую строчку - SSDP.setDeviceType("upnp:rootdevice"); - SSDP.setSchemaURL("description.xml"); - SSDP.setHTTPPort(80); - SSDP.setName(jsonRead(configSetup, "SSDP")); - SSDP.setSerialNumber(chipID); - SSDP.setURL("/"); - SSDP.setModelName("tech"); - SSDP.setModelNumber(chipID + "/" + jsonRead(configSetup, "SSDP")); - - - SSDP.setModelURL("https://github.com/DmitryBorisenko33/esp32-esp8266_iot-manager_modules_firmware"); - SSDP.setManufacturer("Borisenko Dmitry"); - SSDP.setManufacturerURL("https://www.instagram.com/rriissee3"); - SSDP.begin(); - } - } -*/ diff --git a/Upgrade.ino b/Upgrade.ino index 90094ddf..daf6043e 100644 --- a/Upgrade.ino +++ b/Upgrade.ino @@ -10,29 +10,20 @@ void initUpgrade() { Serial.print("[i] Last firmware version: "); Serial.println(last_version); - server.on("/upgrade", HTTP_GET, [](AsyncWebServerRequest * request) { - - start_check_version = true; - + server.on("/check", HTTP_GET, [](AsyncWebServerRequest * request) { + upgrade_url = true; Serial.print("[i] Last firmware version: "); Serial.println(last_version); -#ifdef ESP8266 - int ChipRealSize = ESP.getFlashChipRealSize() / 1048576; -#endif -#ifdef ESP32 - int ChipRealSize = 4; -#endif String tmp = "{}"; if (WiFi.status() == WL_CONNECTED) { - if (ChipRealSize >= 4) { + if (mb_4_of_memory) { if (last_version != "") { if (last_version != "error") { if (last_version == firmware_version) { jsonWriteStr(tmp, "title", "Последняя версия прошивки уже установлена."); jsonWriteStr(tmp, "class", "pop-up"); - } else { - upgrade_flag = true; - jsonWriteStr(tmp, "title", "Идет обновление прошивки... После завершения устройство перезагрузится. Подождите одну минуту!!!"); + } else { + jsonWriteStr(tmp, "title", "Имеется новая версия прошивкиИдет обновление прошивки, после обновления страница перезагрузится автоматически...')\">Установить"); jsonWriteStr(tmp, "class", "pop-up"); } } else { @@ -53,11 +44,18 @@ void initUpgrade() { } request->send(200, "text/text", tmp); }); + + server.on("/upgrade", HTTP_GET, [](AsyncWebServerRequest * request) { + upgrade = true; + String tmp = "{}"; + //jsonWriteStr(tmp, "title", "Есть новая версия Установить?"); + request->send(200, "text/text", "ok"); + }); } -void handle_get_url() { - if (start_check_version) { - start_check_version = false; +void do_upgrade_url() { + if (upgrade_url) { + upgrade_url = false; #ifdef ESP32 last_version = getURL("http://91.204.228.124:1100/update/esp32/version.txt"); jsonWriteStr(configSetup, "last_version", last_version); @@ -118,9 +116,9 @@ void upgrade_firmware() { } } -void handle_upgrade() { - if (upgrade_flag) { - upgrade_flag = false; +void do_upgrade() { + if (upgrade) { + upgrade = false; upgrade_firmware(); } } diff --git a/WiFi.ino b/WiFi.ino index 20f9f44c..622f748c 100644 --- a/WiFi.ino +++ b/WiFi.ino @@ -1,5 +1,5 @@ void WIFI_init() { - + // --------------------Получаем ssid password со страницы server.on("/ssid", HTTP_GET, [](AsyncWebServerRequest * request) { if (request->hasArg("ssid")) { @@ -42,12 +42,12 @@ void WIFI_init() { } request->send(200, "text/text", "OK"); // отправляем ответ о выполнении }); + ROUTER_Connecting(); +} - - // Попытка подключения к точке доступа +void ROUTER_Connecting() { WiFi.mode(WIFI_STA); - // WiFi.mode(WIFI_NONE_SLEEP); byte tries = 20; String _ssid = jsonRead(configSetup, "ssid"); @@ -92,6 +92,8 @@ void WIFI_init() { Serial.print(WiFi.localIP()); Serial.println(""); jsonWriteStr(configJson, "ip", WiFi.localIP().toString()); + + //add_dev_in_list("dev.txt", chipID, WiFi.localIP().toString()); } } @@ -115,8 +117,8 @@ bool StartAPMode() { Serial.println("->try find router"); if (RouterFind(jsonRead(configSetup, "ssid"))) { ts.remove(ROUTER_SEARCHING); - WIFI_init(); - MQTT_init(); + ROUTER_Connecting(); + MQTT_Connecting(); } }, nullptr, true); } @@ -164,8 +166,9 @@ boolean RouterFind(String ssid) { return false; } } + /* -String scanWIFI() { + String scanWIFI() { uint8_t n = WiFi.scanNetworks(); DynamicJsonBuffer jsonBuffer; JsonObject& json = jsonBuffer.createObject(); @@ -184,7 +187,7 @@ String scanWIFI() { String root; json.printTo(root); return root; -} + } */ /* { diff --git a/data/config-all.json b/data/config-all.json new file mode 100644 index 00000000..0f09cde1 --- /dev/null +++ b/data/config-all.json @@ -0,0 +1,18 @@ +{ + "name": "IoTmanager", + "chipID": "", + "ssidAP": "WiFi", + "passwordAP": "", + "ssid": "your_ssid", + "password": "your_password", + "timezone": 3, + "mqttServer": "", + "mqttPort": 0, + "mqttPrefix": "/IoTmanager", + "mqttUser": "", + "mqttPass": "", + "scenario": "1", + "pushingbox_id": "", + "web_login": "admin", + "web_pass": "admin" +} \ No newline at end of file diff --git a/data/config-my.json b/data/config-my.json deleted file mode 100644 index 345fd0cc..00000000 --- a/data/config-my.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "SSDP": "IoTmanager", - "chipID": "", - "ssidAP": "WiFi", - "passwordAP": "", - "ssid": "rise", - "password": "hostel3333", - "timezone": 2, - "mqttServer": "mqtt.ioty.ru", - "mqttPort": 1883, - "mqttPrefix": "/rise", - "mqttUser": "test", - "mqttPass": "test", - "scenario": "1", - "pushingbox_id": "", - "web_login": "admin", - "web_pass": "admin" -} \ No newline at end of file diff --git a/data/config.json b/data/config.json index 6d81dee4..9ce5891e 100644 --- a/data/config.json +++ b/data/config.json @@ -1,16 +1,16 @@ { - "SSDP": "MODULES", + "name": "IoTmanager", "chipID": "", "ssidAP": "WiFi", "passwordAP": "", - "ssid": "your_ssid", - "password": "your_password", - "timezone": 3, - "mqttServer": "", - "mqttPort": 0, - "mqttPrefix": "/IoTmanager", - "mqttUser": "", - "mqttPass": "", + "ssid": "rise", + "password": "hostel3333", + "timezone": 2, + "mqttServer": "mqtt.ioty.ru", + "mqttPort": 1883, + "mqttPrefix": "/rise", + "mqttUser": "test", + "mqttPass": "test", "scenario": "1", "pushingbox_id": "", "web_login": "admin", diff --git a/data/configuration.json b/data/configuration.json index 5cd91f1e..c4a132ce 100644 --- a/data/configuration.json +++ b/data/configuration.json @@ -8,7 +8,7 @@ "content": [ { "type": "h5", - "title": "{{SSDP}}", + "title": "{{name}}", "class": "alert-warning" }, { @@ -62,7 +62,7 @@ "state": "firmware.c.txt", "style": "width:100%;height:400px", "title": "Сохранить", - "action": "/all_modules_init", + "action": "/init?arg=2", "class": "btn btn-block btn-success" }, { @@ -79,7 +79,7 @@ "type": "checkbox", "name": "scenario", "title": "Включить сценарии", - "action": "/scenario?status=[[scenario]]", + "action": "/init?arg=[[scenario]]", "state": "{{scenario}}" }, { @@ -90,8 +90,8 @@ "type": "file", "state": "firmware.s.txt", "style": "width:100%;height:400px", - "title": "Сохранить и включить", - "action": "/scenario?status=1", + "title": "Сохранить", + "action": "/init?arg=4", "class": "btn btn-block btn-success" }, { @@ -100,13 +100,13 @@ { "type": "link", "title": "Очистить логи сенсоров", - "action": "/cleanlog", + "action": "/init?arg=3", "class": "btn btn-block btn-success" }, { "type": "link", "title": "Главная", - "action": "/page.htm?index", + "action": "/", "class": "btn btn-block btn-danger btn-sm" } ] diff --git a/data/dev.json b/data/dev.json new file mode 100644 index 00000000..dc1ad578 --- /dev/null +++ b/data/dev.json @@ -0,0 +1,65 @@ +{ + "configs": [ + "/config.live.json", + "/config.setup.json" + ], + "title": "Главная", + "class": "col-sm-offset-1 col-sm-10 col-md-offset-2 col-md-8 col-lg-offset-3 col-lg-6", + "content": [ + { + "type": "h5", + "title": "{{name}}", + "class": "alert-warning" + }, + { + "type": "h3", + "title": "Список других устройств в сети:" + }, + { + "type": "hr" + }, + { + "type": "csv", + "title": [ + "html", + "html", + "html" + ], + "state": "dev.csv", + "style": "width:100%;", + "class": "nan" + }, + { + "type": "hr" + }, + { + "type": "link", + "title": "Переформировать список устройств", + "action": "udp?arg=2", + "class": "btn btn-block btn-success" + }, + { + "type": "link", + "title": "Обновить страницу", + "action": "udp?arg=3", + "class": "btn btn-block btn-success" + }, + { + "type": "hr" + }, + { + "type": "text", + "class": "alert alert-warning", + "title": "После нажатия на кнопку 'Переформировать список устройств' ждите примерно минуту, а затем обновите страницу и список появится вновь" + }, + { + "type": "hr" + }, + { + "type": "link", + "title": "Главная", + "action": "/", + "class": "btn btn-block btn-danger" + } + ] +} \ No newline at end of file diff --git a/data/favicon.ico b/data/favicon.ico index 198474d246359b14553a1d0eac6b208cd8fe6160..50d908fa6d3f6f710e4d3d0f9fb3e96f3466895c 100644 GIT binary patch literal 1150 zcmbW1Yfy|)7{^~TzWHLtjL$y0jv2QSq6~#f#7e|agTf3mXcR503wx_}tj&marGz54 zp)gY^BiC^Wt!lJL)4EK`ZoB)w`*UPujO#dO{?9q*dFKC|=ltiKBS`~9A3RtRc9^to zpd<~IBx$&Sa0$;>zZfJ*#D)y|ioUfl{G$21UOX;0e!u@0(cimd!FyIyLs@Dnw)M$W zWSOWx{+QQIZWQ^?xfEHZ;dm*z5s_q04`x?j0BPgKvU5^6+K2)!nO@P=>HF>Gt+(%B zS`fpYX+i7`3KW=(I$$E|zzOUL4n`BQ7we`LoQ~d~a(G-W%F@!wnmwMJj9}N) z(SkpLjNoZxhlZdHn?vR-3q@NUq7Ls5IUEljVVoa^IcW?9DWf>FbtKkpQ^;STLNg@@ zZFo3Fajx9xc(UhS;fmRb?ES7seNizvag(v72T+(cp2OQlaqFUi_wDUilh=s-LoqE} zj9Ik=!+Zml_!l@HxcX|4eLl`;)Er5f$w}QDO0q&J%?!o)@=0IZ+15t>s+HtKMq!DK zMIV)mdC3cISU=!b{PgyG;zY^@%8V+`<;QW#FpsO&^~gR?{~4b*+(aL-2wh}0`sjO< ztn1*K{++lZ3hwS6$}_fbsW6GEV;eY=7e&K)v(QN9O|zTEnl36W?HFQikQZa8B&man z%}%Ow-{W;F_&h$Ui%nFQr&3$7gR}b=b70L;a#XGCjkJ>)*2KPf%^XnsnFYwabXMQrEL_f5PBv&r}|(Ez1?y@ zzo)B%`?VIq-^ay6%P_`gurtI?x`1YOGuo(T@>KW9PdLG4i;a)(I{Ud_CVszy!(L1M zc@33E*HWaOOWx}3=;L%4mTNGt-AKvqMDA7<;Och%!2PyR6d#?h8hKb}<@)LERM}Qh nS(rdwxtgZir|Ejz_D}E8uPAsuE6M9Ip6+){~bRA#ZfP9 literal 1150 zcmZQzU<5(|0R|wcz>vYhz#zuJz@P!dKp~(AL>x%b3`hubNaqE~Hu_nK#=vljw{%%U zywY^%yv*t1zH!Ndo-r{XEaVjzCz;;b#v&vk0dr76xKYc^rJ?_CEVRPF2B-f2N5_T- z{{9z_tgMFV4|32-xVfs}|IKA#|8KAGgyUPw-TvF1`~Tko2^$0T8gBpdU&tvs23`N% zO|}1#!S^>e{@?tHEb{-qzCB7Z86IJ@ZY%U zGb(QW@ZW1!BL;Te)A?5^#;Xbz{_H$5a+20njlzb}2{0^bk&vkxQ#D7oC}56ioMVHG zn_&VBtHwCgvkUP_!t_%@|Nqaxa1WT18kiXv4xr%%MxZzlV~7LQ-Lq$40O diff --git a/data/index.json b/data/index.json index 05e0aba7..4ac58fd4 100644 --- a/data/index.json +++ b/data/index.json @@ -8,7 +8,7 @@ "content": [ { "type": "h5", - "title": "{{SSDP}}", + "title": "{{name}}", "class": "alert-warning" }, { @@ -37,28 +37,37 @@ { "type": "link", "title": "Конфигурация устройства", - "action": "/page.htm?configuration", + "action": "/?configuration", "class": "btn btn-block btn-primary" }, { "type": "hr" }, + { + "type": "link", + "title": "Список других устройств в сети", + "action": "/?dev", + "class": "btn btn-block btn-warning" + }, + { + "type": "hr" + }, { "type": "link", "title": "Конфигурация WIFI", - "action": "/page.htm?setup", + "action": "/?setup", "class": "btn btn-block btn-success" }, { "type": "link", "title": "Конфигурация MQTT", - "action": "/page.htm?mqtt", + "action": "/?mqtt", "class": "btn btn-block btn-success" }, { "type": "link", "title": "Конфигурация push", - "action": "/page.htm?pushingbox", + "action": "/?pushingbox", "class": "btn btn-block btn-success" }, { @@ -73,7 +82,7 @@ { "type": "button", "title": "Обновить прошивку", - "action": "/upgrade", + "action": "/check", "response": "[[my-block]]", "class": "btn btn-block btn-success" }, diff --git a/data/js/build.chart.js.gz b/data/js/build.chart.js.gz index 49d8ad0b64f6c7effc975bf8f46068f2728c994b..68e9791c9b2fbc58f9d22448ba4c0d50fbb96b3e 100644 GIT binary patch delta 16 XcmcbXd@Y$>zMF$1b$-J}_Vb1SIXnh% delta 16 XcmcbXd@Y$>zMF$%Dtq2W_Vb1SHlhXg diff --git a/data/js/function.js.gz b/data/js/function.js.gz index 571ba85a25e1d95d0b15de1357d8068ad4b0b99c..2c3d7bbfa0de78cd3a6efc97e2468046f9f52cf0 100644 GIT binary patch literal 18562 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 diff --git a/data/mqtt.json b/data/mqtt.json index e24ccac6..da2a8476 100644 --- a/data/mqtt.json +++ b/data/mqtt.json @@ -1,96 +1,106 @@ { - "configs": [ -"/config.setup.json" - ], - "class":"col-sm-offset-1 col-sm-10 col-md-offset-2 col-md-8 col-lg-offset-3 col-lg-6", - "content": [ - { - "type": "h5", - "title": "{{SSDP}}", - "class":"alert-warning" - }, - { - "type": "h4", - "title": "Server name:" - }, -{ - "type": "input", - "title": "", - "name":"1", - "state": "{{mqttServer}}" - }, -{ - "type": "h4", - "title": "Port:" - }, -{ - "type": "input", - "title": "", - "name":"2", - "state": "{{mqttPort}}" - }, -{ - "type": "h4", - "title": "Prefix:" - }, -{ - "type": "input", - "title": "", - "name":"3", - "state": "{{mqttPrefix}}" - }, -{ - "type": "h4", - "title": "User name:" - }, -{ - "type": "input", - "title": "", - "name":"4", - "state": "{{mqttUser}}" - }, -{ - "type": "h4", - "title": "Password:" - }, -{ - "type": "input", - "title": "", - "name":"5", - "state": "{{mqttPass}}" - }, -{ - "type":"h3", - "name":"my-block", -"style":"position:fixed;top:30%;left:50%;width:400px;margin-left:-200px;text-align:center;", - "class":"hidden" - }, -{ - "type": "button", - "title":"Сохранить", - "action": "mqttSave?mqttServer=[[1]]&mqttPort=[[2]]&mqttPrefix=[[3]]&mqttUser=[[4]]&mqttPass=[[5]]", - "class": "btn btn-block btn-success", - "style": "width:100%;display:inline" - }, -{ - "type": "button", - "title":"Проверить соединение с MQTT", - "action": "mqttCheck", - "response":"[[my-block]]", - "class": "btn btn-block btn-success", - "style": "width:100%;display:inline" - }, - { - "type": "link", - "title": "Перезагрузить устройство", - "action": "javascript:if(confirm(renameBlock(jsonResponse,'Перезагрузить?'))){send_request(this,'/restart?device=ok');}", - "class": "btn btn-block btn-warning" - }, - { - "type": "link", - "title": "Главная", - "action": "/page.htm?index", - "class": "btn btn-block btn-danger btn-sm" - } - ] -} + "configs": [ + "/config.setup.json" + ], + "class": "col-sm-offset-1 col-sm-10 col-md-offset-2 col-md-8 col-lg-offset-3 col-lg-6", + "content": [ + { + "type": "h5", + "title": "{{name}}", + "class": "alert-warning" + }, + { + "type": "h4", + "title": "Server name:" + }, + { + "type": "input", + "title": "", + "name": "1", + "state": "{{mqttServer}}" + }, + { + "type": "h4", + "title": "Port:" + }, + { + "type": "input", + "title": "", + "name": "2", + "state": "{{mqttPort}}" + }, + { + "type": "h4", + "title": "Prefix:" + }, + { + "type": "input", + "title": "", + "name": "3", + "state": "{{mqttPrefix}}" + }, + { + "type": "h4", + "title": "User name:" + }, + { + "type": "input", + "title": "", + "name": "4", + "state": "{{mqttUser}}" + }, + { + "type": "h4", + "title": "Password:" + }, + { + "type": "input", + "title": "", + "name": "5", + "state": "{{mqttPass}}" + }, + { + "type": "h3", + "name": "my-block", + "style": "position:fixed;top:30%;left:50%;width:400px;margin-left:-200px;text-align:center;", + "class": "hidden" + }, + { + "type": "text", + "class": "alert alert-warning", + "title": "Прежде чем нажимать на кнопку 'Отправить настройки MQTT' сохрание их, если Вы их меняли. Настройки получат и перезапишут все устройства в локальной сети" + }, + { + "type": "button", + "title": "Сохранить", + "action": "mqttSave?mqttServer=[[1]]&mqttPort=[[2]]&mqttPrefix=[[3]]&mqttUser=[[4]]&mqttPass=[[5]]", + "class": "btn btn-block btn-success" + }, + { + "type": "button", + "title": "Отправить настройки MQTT с этого устройства на все остальные", + "action": "udp?arg=1", + "class": "btn btn-block btn-success" + }, + + { + "type": "button", + "title": "Проверить соединение с MQTT", + "action": "mqttCheck", + "response": "[[my-block]]", + "class": "btn btn-block btn-success" + }, + { + "type": "link", + "title": "Перезагрузить устройство", + "action": "javascript:if(confirm(renameBlock(jsonResponse,'Перезагрузить?'))){send_request(this,'/restart?device=ok');}", + "class": "btn btn-block btn-success" + }, + { + "type": "link", + "title": "Главная", + "action": "/", + "class": "btn btn-block btn-danger btn-sm" + } + ] +} \ No newline at end of file diff --git a/data/pushingbox.json b/data/pushingbox.json index 0679489a..eac5a080 100644 --- a/data/pushingbox.json +++ b/data/pushingbox.json @@ -6,7 +6,7 @@ "content": [ { "type": "h5", - "title": "{{SSDP}}", + "title": "{{name}}", "class":"alert-warning" }, { @@ -39,7 +39,7 @@ { "type": "link", "title": "Главная", - "action": "/page.htm?index", + "action": "/", "class": "btn btn-block btn-danger btn-sm" } ] diff --git a/data/setup.json b/data/setup.json index 2b998ff4..7425c4d9 100644 --- a/data/setup.json +++ b/data/setup.json @@ -7,7 +7,7 @@ "content": [ { "type": "h5", - "title": "{{SSDP}}", + "title": "{{name}}", "class":"alert-warning" }, { @@ -26,14 +26,14 @@ { "type": "input", "title": "Имя устройства", - "name":"ssdp", - "state": "{{SSDP}}", + "name":"dev_name", + "state": "{{name}}", "pattern": "[0-9a-zA-Zа-яА-Я.\\- ]{1,20}" }, { "type": "button", "title": "Сохранить", - "action": "ssdp?ssdp=[[ssdp]]", + "action": "name?arg=[[dev_name]]", "class": "btn btn-block btn-success" }, { diff --git a/date_excess/favicon.ico b/date_excess/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..198474d246359b14553a1d0eac6b208cd8fe6160 GIT binary patch literal 1150 zcmZQzU<5(|0R|wcz>vYhz#zuJz@P!dKp~(AL>x%b3`hubNaqE~Hu_nK#=vljw{%%U zywY^%yv*t1zH!Ndo-r{XEaVjzCz;;b#v&vk0dr76xKYc^rJ?_CEVRPF2B-f2N5_T- z{{9z_tgMFV4|32-xVfs}|IKA#|8KAGgyUPw-TvF1`~Tko2^$0T8gBpdU&tvs23`N% zO|}1#!S^>e{@?tHEb{-qzCB7Z86IJ@ZY%U zGb(QW@ZW1!BL;Te)A?5^#;Xbz{_H$5a+20njlzb}2{0^bk&vkxQ#D7oC}56ioMVHG zn_&VBtHwCgvkUP_!t_%@|Nqaxa1WT18kiXv4xr%%MxZzlV~7LQ-Lq$40OgetParam("mqttPass")->value()); } saveConfig(); - start_connecting_to_mqtt = true; + mqtt_connection = true; request->send(200, "text/text", "ok"); }); @@ -49,11 +49,20 @@ void MQTT_init() { jsonWriteStr(tmp, "class", "pop-up"); request->send(200, "text/text", tmp); }); + + } -void handle_connection() { - if (start_connecting_to_mqtt) { - start_connecting_to_mqtt = false; +void do_mqtt_send_settings_to_udp() { + if (mqtt_send_settings_to_udp) { + mqtt_send_settings_to_udp = false; + send_mqtt_to_udp(); + } +} + +void do_mqtt_connection() { + if (mqtt_connection) { + mqtt_connection = false; client.disconnect(); MQTT_Connecting(); } @@ -76,14 +85,14 @@ boolean MQTT_Connecting() { //ssl//espClient.setCACert(local_root_ca1); client.setServer(mqtt_server.c_str(), jsonReadtoInt(configSetup, "mqttPort")); if (WiFi.status() == WL_CONNECTED) { - if (!client.connected()) { + if (!client.connected()) { Serial.println("[V] Connecting to MQTT server commenced"); - if (client.connect(chipID.c_str(), jsonRead(configSetup, "mqttUser").c_str(), jsonRead(configSetup, "mqttPass").c_str())) { + if (client.connect(chipID.c_str(), jsonRead(configSetup, "mqttUser").c_str(), jsonRead(configSetup, "mqttPass").c_str())) { Serial.println("[V] MQTT connected"); client.setCallback(callback); client.subscribe(jsonRead(configSetup, "mqttPrefix").c_str()); // Для приема получения HELLOW и подтверждения связи client.subscribe((jsonRead(configSetup, "mqttPrefix") + "/" + chipID + "/+/control").c_str()); // Подписываемся на топики control - client.subscribe((jsonRead(configSetup, "mqttPrefix") + "/" + chipID + "/order").c_str()); // Подписываемся на топики order + client.subscribe((jsonRead(configSetup, "mqttPrefix") + "/" + chipID + "/order").c_str()); // Подписываемся на топики order Serial.println("[V] Callback set, subscribe done"); return true; } else { @@ -99,7 +108,7 @@ boolean MQTT_Connecting() { } //=====================================================ВХОДЯЩИЕ ДАННЫЕ======================================================== -void callback(char* topic, byte* payload, unsigned int length) { +void callback(char* topic, byte * payload, unsigned int length) { Serial.print("[MQTT] "); Serial.print(topic); String topic_str = String(topic); @@ -228,7 +237,7 @@ void sendAllData() { //берет строку json и ключи превра topic.replace("\"", ""); String state = selectToMarkerLast (tmp, ":"); state.replace("\"", ""); - if (topic != ssdpS && topic != "lang" && topic != "ip" && topic.indexOf("_in") < 0) { + if (topic != "name" && topic != "lang" && topic != "ip" && topic.indexOf("_in") < 0) { sendSTATUS(topic, state); //Serial.println("-->" + topic + " " + state); } diff --git a/set.h b/set.h index bf10a2b1..76630a71 100644 --- a/set.h +++ b/set.h @@ -1,7 +1,8 @@ -String firmware_version = "2.3.2"; +String firmware_version = "2.3.1"; //----------------------------------------------------------------- +boolean mb_4_of_memory = true; String last_version; -boolean start_check_version = false; + //#define OTA_enable //#define MDNS_enable @@ -79,11 +80,14 @@ AsyncEventSource events("/events"); //--------------------------------------------------------------- #include TickerScheduler ts(30); -enum {ROUTER_SEARCHING, WIFI_MQTT_CONNECTION_CHECK, LEVEL, ANALOG_, DALLAS, DHTT, DHTH, DHTC, DHTP, DHTD, STEPPER1, STEPPER2, ANALOG_LOG, LEVEL_LOG, DALLAS_LOG, dhtT_LOG, dhtH_LOG, CMD, TIMER_COUNTDOWN, TIMERS, TIME, TIME_SYNC, STATISTICS, TEST}; +enum {ROUTER_SEARCHING, WIFI_MQTT_CONNECTION_CHECK, LEVEL, ANALOG_, DALLAS, DHTT, DHTH, DHTC, DHTP, DHTD, STEPPER1, STEPPER2, ANALOG_LOG, LEVEL_LOG, DALLAS_LOG, dhtT_LOG, dhtH_LOG, CMD, TIMER_COUNTDOWN, TIMERS, TIME, TIME_SYNC, STATISTICS, UDP, UDP_DB, TEST}; //--------------------------------------------------------------- //ssl//#include "dependencies/WiFiClientSecure/WiFiClientSecure.h" //using older WiFiClientSecure //#include "Ticker_for_TickerScheduler/Ticker/Ticker.h" //--------------------------------------------------------------- +#include +WiFiUDP Udp; +//--------------------------------------------------------------- #include WiFiClient espClient; //ssl//WiFiClientSecure espClient; @@ -145,7 +149,6 @@ const char* ntpServer = "pool.ntp.org"; const long gmtOffset_sec = 3600; const int daylightOffset_sec = 3600; -const String ssdpS = "SSDP"; String current_time; int scenario_line_status [] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; @@ -154,11 +157,25 @@ int wifi_lost_error = 0; int mqtt_lost_error = 0; String var; -boolean upgrade_flag = false; -boolean get_url_flag = false; -boolean start_connecting_to_mqtt = false; +//flags for not async actions +boolean upgrade_url = false; +boolean upgrade = false; +boolean mqtt_connection = false; +boolean udp_data_parse = false; +boolean mqtt_send_settings_to_udp = false; + +boolean udp_busy = false; + String test; boolean chart_data_in_solid_array; + + +unsigned int udp_port = 4210; +char udp_incomingPacket[255]; +//char udp_replyPacket[] = "Multicast packet 1"; +IPAddress udp_multicastIP (255, 255, 255, 255); +String received_ip; +String received_udp_line; diff --git a/udp.ino b/udp.ino new file mode 100644 index 00000000..a85052f1 --- /dev/null +++ b/udp.ino @@ -0,0 +1,122 @@ +void UDP_init() { + server.on("/udp", HTTP_GET, [](AsyncWebServerRequest * request) { + String value; + if (request->hasArg("arg")) { + value = request->getParam("arg")->value(); + } + if (value == "1") { + mqtt_send_settings_to_udp = true; + request->send(200, "text/text", "ok"); + } + if (value == "2") { + SPIFFS.remove("/dev.csv"); + request->redirect("/?dev"); + } + if (value == "3") { + request->redirect("/?dev"); + } + }); + server.on("/name", HTTP_GET, [](AsyncWebServerRequest * request) { + if (request->hasArg("arg")) { + jsonWriteStr(configSetup, "name", request->getParam("arg")->value()); + jsonWriteStr(configJson, "name", request->getParam("arg")->value()); + saveConfig(); + } + request->send(200, "text/text", "OK"); + }); + + SPIFFS.remove("/dev.csv"); + + Udp.begin(udp_port); + + ts.add(UDP, 30000, [&](void*) { + if (WiFi.status() == WL_CONNECTED) { + if (!udp_busy) { + String line_to_send = chipID + ";" + jsonRead(configSetup, "SSDP"); +#ifdef ESP8266 + Udp.beginPacketMulticast(udp_multicastIP, udp_port, WiFi.localIP()); + Udp.write(line_to_send.c_str()); +#endif +#ifdef ESP32 + Udp.beginMulticast(udp_multicastIP, udp_port); +#endif + Udp.endPacket(); + Serial.println("[UDP<=] dev info send"); + } + } + }, nullptr, false); +} + +void add_dev_in_list(String fileName, String id, String dev_name, String ip) { + File configFile = SPIFFS.open("/" + fileName, "r"); + if (!configFile) { + addFile(fileName, "device id;device name;ip adress"); + } + if (!configFile.find(id.c_str())) { + addFile(fileName, id + ";" + dev_name + "; " + ip + ""); + } +} + +void handleUdp() { + if (WiFi.status() == WL_CONNECTED) { + int packetSize = Udp.parsePacket(); + if (packetSize) { + //Serial.printf("Received %d bytes from %s, port %d\n", packetSize, Udp.remoteIP().toString().c_str(), Udp.remotePort()); + received_ip = Udp.remoteIP().toString(); + int len = Udp.read(udp_incomingPacket, 255); + if (len > 0) { + udp_incomingPacket[len] = 0; + } + received_udp_line = String(udp_incomingPacket); + udp_data_parse = true; + } + } +} + +void do_udp_data_parse() { + if (udp_data_parse) { + udp_data_parse = false; + Serial.print("[UDP=>] " + received_ip); + Serial.print(" "); + Serial.println(received_udp_line); + if (received_udp_line.indexOf("mqttServer") > 0) { + jsonWriteStr(configSetup, "mqttServer", jsonRead(received_udp_line, "mqttServer")); + jsonWriteInt(configSetup, "mqttPort", jsonReadtoInt(received_udp_line, "mqttPort")); + jsonWriteStr(configSetup, "mqttPrefix", jsonRead(received_udp_line, "mqttPrefix")); + jsonWriteStr(configSetup, "mqttUser", jsonRead(received_udp_line, "mqttUser")); + jsonWriteStr(configSetup, "mqttPass", jsonRead(received_udp_line, "mqttPass")); + saveConfig(); + Serial.println("[V] new mqtt setting received from udp and saved"); + mqtt_connection = true; + } else { + add_dev_in_list("dev.csv", selectFromMarkerToMarker(received_udp_line, ";", 0), selectFromMarkerToMarker(received_udp_line, ";", 1), received_ip); + } + } +} + +void send_mqtt_to_udp() { + if (WiFi.status() == WL_CONNECTED) { + udp_busy = true; + String mqtt_data = "{}"; + jsonWriteStr(mqtt_data, "mqttServer", jsonRead(configSetup, "mqttServer")); + jsonWriteInt(mqtt_data, "mqttPort", jsonReadtoInt(configSetup, "mqttPort")); + jsonWriteStr(mqtt_data, "mqttPrefix", jsonRead(configSetup, "mqttPrefix")); + jsonWriteStr(mqtt_data, "mqttUser", jsonRead(configSetup, "mqttUser")); + jsonWriteStr(mqtt_data, "mqttPass", jsonRead(configSetup, "mqttPass")); + Serial.println(mqtt_data); +#ifdef ESP8266 + Udp.beginPacketMulticast(udp_multicastIP, udp_port, WiFi.localIP()); + Udp.write(mqtt_data.c_str()); +#endif +#ifdef ESP32 + Udp.beginMulticast(udp_multicastIP, udp_port); + int size_of = sizeof(mqtt_data); + uint8_t msg[10] = (uint8_t)atoi(mqtt_data.c_str()); + //Udp.write(msg, sizeof(mqtt_data)); + //Udp.write(mqtt_data.c_str(), strlen(mqtt_data.c_str())); +#endif + Udp.endPacket(); + Serial.println("[UDP<=] mqtt info send"); + udp_busy = false; + } +}