/** Created on: 01.01.2021 Счётчик воды и протечки WEB морда для ESP8266 1) Установить: http://wiki.amperka.ru/%D0%BF%D1%80%D0%BE%D0%B4%D1%83%D0%BA%D1%82%D1%8B:esp8266:esptool Для 1MB флешь памяти, файловая система: http://wikihandbk.com/wiki/ESP8266:%D0%9F%D1%80%D0%BE%D1%88%D0%B8%D0%B2%D0%BA%D0%B8/Arduino/%D0%A0%D0%B0%D0%B1%D0%BE%D1%82%D0%B0_%D1%81_%D1%84%D0%B0%D0%B9%D0%BB%D0%BE%D0%B2%D0%BE%D0%B9_%D1%81%D0%B8%D1%81%D1%82%D0%B5%D0%BC%D0%BE%D0%B9_%D0%B2_%D0%B0%D0%B4%D0%B4%D0%BE%D0%BD%D0%B5_ESP8266_%D0%B4%D0%BB%D1%8F_IDE_Arduino На борту счётчика выход 2 входа для счётчика горячей и холодной (Подключено к STM8L051 геркон) 1 вход для датчика утечки (Подключено к STM8L051 200 ком резистор по моему) 1 кнопка для включения ESP8266 и перехода в настройки при длительном нажатии (10 секунд) а при коротком просто отправить данные по WIFI (Подключено к STM8L051 кнопка) 1 кнопочка для открытия кранов не смотря на утеку воды (для пожаробезопасности) (Подключено к STM8L051 кнопка) Подробнее схемотехника на: https://easyeda.com/ru Другая документация на O:\MyDocuments\projects\_Doc\Water_meter_observer */ //---------------------------------------------------------------------------------------------------- #include #include #include #include // Include the WebServer library #include #include #include //Ticker Library #include //Прмер: https://github.com/lorol/ESPAsyncWebServer/blob/master/examples/ESP_AsyncFSBrowser/ESP_AsyncFSBrowser.ino #include //Для отправки данных по MQTT см.: https://www.emqx.io/blog/esp8266-connects-to-the-public-mqtt-broker #include "Bounce2.h" #include "tools.h" //---------------------------------------------------------------------------------------------------- #define PIN_LED 02 //Пин светодиода Bounce bounce = Bounce(); //Для избавления от дребезга контактов int blink=LOW; //Для мигания при настройке int blinkMSec=0; //Для мигания при настройке ESP8266WebServer server(80); // Set web server and port number to 80 WiFiClient wifiClient; WiFiClientSecure wifiClientS; IPAddress local_IP(192,168,1,1); //Для точки доступа IPAddress gateway(192,168,1,1); //Для точки доступа IPAddress subnet(255,255,255,0); //Для точки доступа IPAddress DNS_IP(192,168,1,1); int lastPS=0; //сколько приконекченных // DNS server const byte DNS_PORT = 53; DNSServer dnsServer; //Для входа и выхода из режима настройки WIFI #define CNF_TIME 300; //Сколько секунд даётся для настройки оборудования (600=5минут) int g_ticks = CNF_TIME; //Переменная для отсчёта секунд, как дойдёт до нуля то засыпаем Ticker blinker; //Для подсчёта времени до входа в спящий режим (предполагаю просыпание по кнопке ресет) String ssid="Node6"; String pass="isecretk"; PubSubClient mqttClient; String mqtt_protocol = "tcp"; String mqtt_host = "observer.kz"; // Имя сервера MQTT uint16_t mqtt_port = 1883; // Порт для подключения к серверу MQTT String mqtt_fingerprint = ""; //Отпечаток сертификата String mqtt_user = ""; // Логи для подключения к серверу MQTT String mqtt_pass = ""; // Пароль для подключения к серверу MQTT String mqtt_topic = "home/water/main"; /* 1=on, 0=off else toggle*/ #define LED_QOS 1 //0 - Без подтверждения 1 - С подтверждением 2 - С подтверждением и без возможности двойной отправки int time5s=millis(); //Для реконекта каждые 5 секунд String uuid=""; //Уникальный ID клиента (загружается из файловой системы) String g_data; //Для накопления данных с последовательного порта //---------------------------------------------------------------------------------------------------- int g_cold = -1; //Показание холодной воды int g_hot = -1; //Показание горячей воды int g_leak = -1; //Протечка 0 нет 1 есть int g_volt = -1; //Вольт на акамуляторе int g_tmpr = -100; //Температура на микроконтроллере //---------------------------------------------------------------------------------------------------- bool configWebServer(); void setLampLight(int mode); //---------------------------------------------------------------------------------------------------- //Load settings from file system to local variables bool loadConfig() { File f = LittleFS.open("settings.txt", "r"); String line=""; do { line=readLine(f); if(line!=""){ if(BeforeFirst(line,'=')=="ssid") ssid=AfterFirst(line,'='); //Название сети else if(BeforeFirst(line,'=')=="pass") pass=AfterFirst(line,'='); //Номер сети else if(BeforeFirst(line,'=')=="mqtt_protocol") mqtt_protocol=AfterFirst(line,'='); else if(BeforeFirst(line,'=')=="mqtt_host") mqtt_host=AfterFirst(line,'='); else if(BeforeFirst(line,'=')=="mqtt_port") mqtt_port=AfterFirst(line,'=').toInt(); else if(BeforeFirst(line,'=')=="mqtt_fingerprint") mqtt_fingerprint=AfterFirst(line,'='); else if(BeforeFirst(line,'=')=="mqtt_user") mqtt_user=AfterFirst(line,'='); else if(BeforeFirst(line,'=')=="mqtt_pass") mqtt_pass=AfterFirst(line,'='); else if(BeforeFirst(line,'=')=="mqtt_topic") mqtt_topic=AfterFirst(line,'='); } } while (line!=""); f.close(); Serial.println("SSID = "+ssid); Serial.println("MQTT HOST = "+mqtt_host); return true; } //---------------------------------------------------------------------------------------------------- //Save settings from local variables to file system bool saveConfigs() { File f = LittleFS.open("settings.txt", "w"); if (!f) { Serial.println("Count file open failed on update."); } else { f.println("ssid="+ssid); f.println("pass="+pass); f.println("mqtt_protocol="+mqtt_protocol); f.println("mqtt_host="+mqtt_host); f.println("mqtt_port="+String(mqtt_port)); f.println("mqtt_fingerprint="+mqtt_fingerprint); f.println("mqtt_topic="+mqtt_topic); f.println("mqtt_user="+mqtt_user); f.println("mqtt_pass="+mqtt_pass); f.close(); } return true; } //---------------------------------------------------------------------------------------------------- //Запрос на получение данных с микроконтроллера //Для дебага написал тестовую программу клторая эиетирует STM8L по последовательному порту: O:\MyDocuments\projects\Workspace_C++Builder\NeptuneW01 bool getMode(){ Serial.println(addCRC("#0;")); //Запрашиваю для какого режима работы включили WIFI return true; } //---------------------------------------------------------------------------------------------------- // Запрашиваю данные с датчиков bool callSensorsData(){ Serial.println(addCRC("#1;")); //Запрашиваем '1'=холодная Serial.println(addCRC("#2;")); //Запрашиваем '2'=горячая Serial.println(addCRC("#3;")); //Запрашиваем '3'=протечки Serial.println(addCRC("#4;")); //Запрашиваем '4'=вольт на аккумуляторе Serial.println(addCRC("#5;")); //Запрашиваем '5'=температура на процессоре //Уровень WIFI сигнала берём на ESP8266 а не с STM8L051 return true; } //---------------------------------------------------------------------------------------------------- // Отправляю данные на STM8L для сохранения bool saveToProc(){ // return true; } //---------------------------------------------------------------------------------------------------- void handleNotFound() { server.send(404, "text/plain", "FileNotFound"); } //---------------------------------------------------------------------------------------------------- //Отправляю показания датчиков для режима настройки void handleSensors() { callSensorsData(); //Запрашиваю новые данные датчиков с микроконтроллера String json; json.reserve(500); //У меня дома около 500 символов было необходимо //Видимые сети json = "{\"networks\":["; int n = WiFi.scanNetworks(); Serial.print(n); Serial.println(" network(s) found"); for (int i = 0; i < n; i++) { json += "{\"name\":\""; json += WiFi.SSID(i); //Название сети json += "\",\"lock\":"; json += WiFi.encryptionType(i); //Шифрование json += ",\"strength\":"; json += WiFi.RSSI(i); //Уровень сигнала json += "}"; if(i!=n-1) json += ","; } json += "],"; //Количество тиков до перехода в спящий режим json += "\"ticks\":"+String(g_ticks); if(g_cold<0) json += ",\"cold\":null"; else json += ",\"cold\":"+String(g_cold); if(g_hot<0) json += ",\"hot\":null"; else json += ",\"hot\":"+String(g_hot); if(g_leak==1) json += ",\"leak\":true"; else if(g_leak==0) json += ",\"leak\":false"; else json += ",\"leak\":null"; if(g_volt<0) json += ",\"volt\":null"; else json += ",\"volt\":"+String(g_volt); if(g_tmpr<=-100) json += ",\"tmpr\":null"; else json += ",\"tmpr\":"+String(g_tmpr); // Температура json += "}"; server.send(200, "application/json", json); } //---------------------------------------------------------------------------------------------------- //Отправляем настройки сети (для режима настройки) void handleData(){ String json; json.reserve(128); json = "{"; json += "\"ssid\":\""+ssid+"\""; json += ",\"pass\":\""+pass+"\""; json += ",\"uuid\":\""+uuid+"\""; json += ",\"mqtt_protocol\":\""+mqtt_protocol+"\""; json += ",\"mqtt_host\":\""+mqtt_host+"\""; json += ",\"mqtt_port\":\""+String(mqtt_port)+"\""; json += ",\"mqtt_fingerprint\":\""+mqtt_fingerprint+"\""; json += ",\"mqtt_topic\":\""+mqtt_topic+"\""; json += ",\"mqtt_user\":\""+mqtt_user+"\""; json += ",\"mqtt_pass\":\""+mqtt_pass+"\""; json += "}"; server.send(200, "application/json", json); } //======================================================================= void handleSave(){ //Serial.println("+++Save+++"); if(server.args()>0){ for(int i=0;i0) { // Wait for the Wi-Fi to connect delay(1000); Serial.print(--i); Serial.print(' '); }*/ return true; } //---------------------------------------------------------------------------------------------------- //Отключиться от точки доступа bool disconnectFromWIFIPoint(){ WiFi.setAutoReconnect(false); WiFi.disconnect(true); return true; } //---------------------------------------------------------------------------------------------------- //Инициилизировать WIFI точку доступа для настройки выключателя а также поднять HTML сервер bool createAP(){ //WiFi.mode(WIFI_AP_STA); //WIFI_AP_STA - Режим точки доступа и клиента WIFI_AP - Режим только точки доступа WiFi.mode(WIFI_AP); //WIFI_AP - Режим только точки доступа //Настройки точки доступа if(WiFi.softAPConfig(local_IP, gateway, subnet)){ Serial.println("Ready WiFi.softAPConfig"); } Serial.print("Setting soft-AP ... "); boolean result = WiFi.softAP("NeptuneW01"); if(result == true) { Serial.println("Ready"); IPAddress IP = WiFi.softAPIP(); Serial.println(IP); //Serial.println(WiFi.localIP()); //Настройте DNS-сервер, перенаправляющий все домены на apIP dnsServer.setErrorReplyCode(DNSReplyCode::NoError); dnsServer.start(DNS_PORT, "*", DNS_IP); configWebServer(); } else { Serial.println("Failed!"); } return true; } //---------------------------------------------------------------------------------------------------- //Отключить программную точку доступа bool deleteAP(){ Serial.print("softAPdisconnect();"); dnsServer.stop(); bool result=WiFi.softAPdisconnect(true); //Гашу светодиод digitalWrite(LED_BUILTIN,HIGH); //LOW return result; } //---------------------------------------------------------------------------------------------------- //Функция для подсчёта времени до перехода в спящий режим void tick_1s() { g_ticks--; if(g_ticks==0){ //Переходим в спящий режим Serial.print("Deep sleep!"); Serial.print(addCRC("#d;!")); //Отсылаю команду на STM8 чтобы он на CHIP_PU подал низский сигнал для отключения питания } //Если в течении 2х минут не перешли в режим глубокого сна то переходим в обычный сон if(g_ticks==-600){ Serial.println("Sleep!"); Serial.print(addCRC("#d;!")); //Повторно отсылаю команду на STM8 чтобы он на CHIP_PU подал низский сигнал для отключения питания ESP.deepSleep(0); } } //---------------------------------------------------------------------------------------------------- bool configWebServer(){ Serial.println("configWebServer()"); server.on("/sensors/", HTTP_GET, handleSensors); server.on("/data/", HTTP_GET, handleData); server.on("/save/", HTTP_POST, handleSave); server.on("/", HTTP_GET, handleMain); server.onNotFound(handleNotFound); // Start server server.begin(); return true; } //---------------------------------------------------------------------------------------------------- // подключаемся к MQTT серверу bool connectToMQTT(){ Serial.println("mqtt_protocol = "+mqtt_protocol+" mqtt_host = "+mqtt_host+" mqtt_port = " + String(mqtt_port)); // Fingerprint of the broker CA // openssl x509 -in m2mqtt_srv.crt -sha1 -noout -fingerprint if(mqtt_protocol=="tcp"){ mqttClient.setClient(wifiClient); }else{ mqttClient.setClient(wifiClientS); wifiClientS.setFingerprint(mqtt_fingerprint.c_str()); } //Присваиваю настройки mqttClient.setServer(mqtt_host.c_str(),mqtt_port); if (WiFi.status() == WL_CONNECTED) { if(!mqttClient.connected()) { if (mqttClient.connect(uuid.c_str() )) { Serial.println("connected"); /*mqttClient.setCallback(callback); if(mqttClient.subscribe(String(mqtt_topic+"/set").c_str(),LED_QOS)) //Подписываемся на топик Serial.println(String("Subscribe on ")+mqtt_topic); else Serial.println("Error subscribe.");*/ } else { Serial.print("failed, status code ="); Serial.print(mqttClient.state()); } } } return true; } //---------------------------------------------------------------------------------------------------- void setup() { Serial.begin(115200); Serial.println(""); Serial.println("Init led pin"); //Настраиваем светодиод на плате pinMode(PIN_LED, OUTPUT); digitalWrite(PIN_LED, HIGH); // Выключаю светодиод //Инициализирую файловую систему if(LittleFS.begin()) Serial.println("SPIF FS ready"); else Serial.println("SPIF FS failed"); uuid=readUUID(); //Загружаю уникальный идентификатор клиента loadConfig(); //Загружаю настройки в локальные переменные blinker.attach(1, tick_1s); //Инициализирую таймер для контроля времени работы ESP8266 connectToWIFIPoint(); //Пытаюсь подключиться к настроенной точке доступа /* connectToMQTT(); //Пытаюсь опубликовать топик */ //Заранее инициализирую HTTP сервер чтобы не замарачиваться с логигой его работы... configWebServer(); //Запрашиваю актуальную информацию по датчикам c STM8 микроконтролера по USART getMode(); } //---------------------------------------------------------------------------------------------------- bool first=true; //======================================================================= void loop(){ mqttClient.loop(); dnsServer.processNextRequest(); //DNS server.handleClient(); //Обрабатываем запрос клиента (без этого не берётся IP адресс) //Как приконнектились отображаю IP адрес if(WiFi.status() == WL_CONNECTED){ if(first){ //Если приконектились или переконектились то отображаю IP адресс Serial.println(); Serial.print("Local IP address:\t"); Serial.println(WiFi.localIP()); first=false; } }else{ first=true; } //если кто приконектился то выводим это в консоль if(WiFi.softAPgetStationNum()!=lastPS) { lastPS=WiFi.softAPgetStationNum(); Serial.printf("Stations connected to soft-AP = %d\n", WiFi.softAPgetStationNum()); } /* //После запуска спрашиваем в каком режиме работать настройки или передачи показаний if(millis()-time5s>30000){ getMode(); time5s=millis(); } */ /* mqttClient.loop(); dnsServer.processNextRequest(); //DNS server.handleClient(); //Обрабатываем запрос клиента (без этого не берётся IP адресс) if(g_ticks>0){ //В режиме конфигурации каждые 500 миллисекунд меняем цвет зелёного светодиода if(millis()-blinkMSec>500){ blink=!blink; digitalWrite(LED_BUILTIN,blink); blinkMSec=millis(); } }else{ //Если в нормальном режиме функционирования //Проверяю соединение с MQTT и если не соединён то пытаюсь подключиться каждые 10 секунд if(millis()-time5s>10000){ if(!mqttClient.connected()) { connectToMQTT(); } time5s=millis(); } } */ //Блок кода для общения с энергоэфективным микроконтроллером int inByte = 0; if (Serial.available() > 0) { //если есть доступные данные то обрабатываем их //Serial.println("available = "+String(Serial.available())); // считываем байты в строку до символа с символа '#' до символа '*' for(int i=0;i-100){ data+=",\"tmpr\":"+String(g_tmpr); // Температура } data+=",\"rssi\":"+String(WiFi.RSSI()); //Уровень WIFI сигнала data+="}"; mqttClient.publish(mqtt_topic.c_str(), data.c_str(), true); //Отправляю данные в топик (С флагом RETAIN чтобы слушатели которые подключились поздно получили тек. состояние) Serial.println(data); //Как отправили данные устанавливаю время ожидания входа в сон на 5 секунд чтобы ESP8266 заснул g_ticks=5; //Очищаю данные g_cold=-1; g_hot=-1; g_leak=-1; g_volt=-1; g_tmpr=-100; } }