///////////////////////////////////////////////// // // 2026-06-12 ... 2026-07-08 v.012 // ///////////////////////////////////////////////// #include #include #include #include #include #include #include #include "config.h" #include "tablo_ws2812.h" const uint32_t utcOffsetInSeconds = 10800; const uint32_t utcPeriodMseconds = 86400000; // 86400000-р/сут WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, NTP_SERVER, utcOffsetInSeconds, utcPeriodMseconds); String sensorKeys; WiFiClient espClient; HTTPClient http; uint32_t timeUpdateH = 02; // время обновления NTP uint32_t timeUpdateM = 05; // время обновления NTP String z = ""; // строка данных для отображения bool flagVis = true; bool flagSec = true; uint8_t brightVis = 0; uint32_t timeReadOld = 0; uint8_t visData[] = {5, 2, 2, 2, 2}; uint8_t displayMode = 0; unsigned long lastModeSwitch = 0; unsigned long lastHTTPRequest = 0; const unsigned long HTTP_UPDATE_INTERVAL = 60000; // Обновляем раз в минуту struct { float temp_in = 0.0; uint16_t pres = 0; uint8_t hum = 0; float temp_out = 0.0; } dataSensors; const uint8_t NUMBER_OF_ATTEMPTS = 20; ESP8266WebServer server(80); ESP8266HTTPUpdateServer httpUpdater; //=================================================== // подключение к WiFi bool setupWiFi() { z = "ABABA"; visibleWork(z, COLOR_WIFI, BRIGHT_DAY); strip.show(); digitalWrite(LED_BUILTIN, LOW); Serial.println("\n\nSetup WiFi: "); WiFi.begin(WIFI_SSID, WIFI_PASS); uint8_t count = NUMBER_OF_ATTEMPTS; while (WiFi.status() != WL_CONNECTED) { Serial.print(count); Serial.print('>'); if (!count--) return false; delay(500); } digitalWrite(LED_BUILTIN, HIGH); //----------------------------------- // индикация IP z = "DDDDD"; visibleWork(z, COLOR_WIFI, BRIGHT_DAY); strip.show(); String l = WiFi.localIP().toString(); l = l.substring(l.lastIndexOf('.') + 1, l.length()); (l.toInt() < 100) ? l = "DDE" + l : l = "DE" + l; visibleWork(l, COLOR_WIFI, BRIGHT_DAY); strip.show(); Serial.print("\nWiFi connected !\nIP: "); Serial.println(WiFi.localIP()); delay(2000); visibleWork("DDDDD", COLOR_WIFI, BRIGHT_DAY); strip.show(); //----------------------------------- // индикация силы сигнала int8_t RSSI_MAX = -50; int8_t RSSI_MIN = -100; int8_t dBm = WiFi.RSSI(); Serial.print("RSSI dBm = "); Serial.println(dBm); l = ""; (dBm <= RSSI_MIN) ? l += 0 : (dBm >= RSSI_MAX) ? l += 100 : l += 2 * (dBm + 100); Serial.print("RSSI % = "); Serial.println(l); Serial.println(); (l.toInt() < 100) ? l = "DDE" + l : l = "DE" + l; visibleWork(l, COLOR_WIFI, BRIGHT_DAY); strip.show(); delay(2000); visibleWork("DDDDD", COLOR_WIFI, BRIGHT_DAY); strip.show(); return true; } //=================================================== // синхронизация с NTP: bool setupNTP() { Serial.println("\nСинхронизируем время: "); uint8_t count = NUMBER_OF_ATTEMPTS; while (!timeClient.update()) { Serial.print(count); Serial.print('.'); if (!count--) return false; delay(500); } Serial.println(); Serial.println(timeClient.getFormattedTime()); Serial.println(); return true; } //=================================================== // Запрос POST на сервер: String http_response() { String payload; String url = "http://" + String(SERVER_HOST) + ":" + String(SERVER_PORT) + SERVER_PATH; Serial.println("Sending POST to: " + url); Serial.println("Data: " + sensorKeys); http.begin(espClient, url); http.addHeader("Content-Type", "text/plain"); http.setTimeout(5000); // Отправляем POST с нашими ключами int httpCode = http.POST(sensorKeys); // Анализируем ответ if (httpCode == HTTP_CODE_OK) { Serial.printf("Response code: %d\n", httpCode); payload = http.getString(); Serial.println("Server response: \n" + payload); http.end(); return payload; } else { Serial.printf("POST failed, error: %s\n", http.errorToString(httpCode).c_str()); http.end(); return ""; // Возвращаем пустую строку при ошибке } } //=================================================== // Парсим ответ сервера: bool parse_data_http(String data) { if (data.length() == 0) { Serial.println("Warning: Empty data received"); return false; } // Находим все разделители для первой части (до первой *) int idx1 = data.indexOf(';'); // после temp_in int idx2 = data.indexOf(';', idx1 + 1); // после pres int idx3 = data.indexOf(';', idx2 + 1); // после hum // Находим вторую звездочку (перед temp_out) int firstStar = data.indexOf('*'); int secondStar = data.indexOf('*', firstStar + 1); if (idx1 == -1 || idx2 == -1 || idx3 == -1 || secondStar == -1) { Serial.println("Warning: Invalid data format: " + data); return false; } // Парсим первую часть dataSensors.temp_in = data.substring(1, idx1).toFloat(); // Пропускаем первый * dataSensors.pres = data.substring(idx1 + 1, idx2).toInt(); dataSensors.hum = data.substring(idx2 + 1, idx3).toInt(); // Парсим вторую температуру (после второй звездочки) int startTempOut = secondStar + 1; int endTempOut = data.indexOf(';', startTempOut); if (endTempOut == -1) { endTempOut = data.indexOf('*', startTempOut); } if (endTempOut == -1) { endTempOut = data.length(); } dataSensors.temp_out = data.substring(startTempOut, endTempOut).toFloat(); Serial.printf("Parsed: T_in=%.2f, P=%d, H=%d, T_out=%.2f\n", dataSensors.temp_in, dataSensors.pres, dataSensors.hum, dataSensors.temp_out); return true; } //=================================================== // Форматируем время: void TimeMillis() { z = ""; if (timeClient.getHours() < 10) z += "D"; z += timeClient.getHours(); (flagSec) ? z += "B" : z += "D"; if (timeClient.getMinutes() < 10) z += "0"; z += timeClient.getMinutes(); if ((timeClient.getHours() >= TIME_DAY) && (timeClient.getHours() < TIME_NIGHT)) { brightVis = BRIGHT_DAY; } else { brightVis = BRIGHT_NIGHT; } } //=================================================== // Форматируем температуру: void fTemp(float t) { z = ""; // Отрицательная температура bool isNegative = (t < 0); if (isNegative) { t = -t; z += "B"; // Минус } // Целая часть int whole = (int)t; if (whole < 10 && !isNegative) { z += "D"; // Ведущий ноль для выравнивания } z += String(whole); // Дробная часть (с правильным округлением) int frac = (int)((t - whole) * 10 + 0.5); if (frac > 0) { z += "E"; // Точка z += String(frac); } // Градус z += "A"; // Дополняем до 5 символов (если нужно) while (z.length() < 5) { z = "D" + z; } } // Форматируем давление, влажность: void fOther(int o) { if (o < 10) { z = "DDDD"; } else if (o < 100) { z = "DDD"; } else { z = "DD"; } z += o; } //=================================================== // Обновляем данные отображения: void updateDisplay() { if (!flagVis) return; switch (displayMode) { case 0: TimeMillis(); visibleWork(z, COLOR_TIME, brightVis); strip.show(); flagVis = false; break; case 1: fTemp(dataSensors.temp_in); visibleWork(z, COLOR_TEMP, brightVis); strip.show(); flagVis = false; // Обновляем данные с сервера не чаще чем раз в минуту if (millis() - lastHTTPRequest >= HTTP_UPDATE_INTERVAL) { String response = http_response(); if (response.length() > 0) { if (parse_data_http(response)) { lastHTTPRequest = millis(); } } else { Serial.println("Warning: Using cached sensor data"); } } break; case 2: fOther(dataSensors.pres); visibleWork(z, COLOR_PRESS, brightVis); strip.show(); flagVis = false; break; case 3: fOther(dataSensors.hum); visibleWork(z, COLOR_HUM, brightVis); strip.show(); flagVis = false; break; case 4: fTemp(dataSensors.temp_out); visibleWork(z, COLOR_TEMP_OUT, brightVis); strip.show(); flagVis = false; break; default: break; } } //=================================================== void setup() { Serial.begin(115200); Serial.println("\n\n=== TABLO STARTING ==="); ESP.wdtEnable(5000); pinMode(LED_BUILTIN, OUTPUT); digitalWrite(LED_BUILTIN, HIGH); strip.begin(); z = "DDDDD"; visibleWork(z, COLOR_WIFI, BRIGHT_DAY); strip.show(); if (!setupWiFi()) { Serial.println("WiFi setup failed, restarting..."); ESP.restart(); } timeClient.begin(); if (!setupNTP()) { Serial.println("NTP setup failed, restarting..."); ESP.restart(); } sensorKeys = String(SENSOR_1) + "," + String(SENSOR_2); // Первый запрос к серверу String response = http_response(); if (response.length() > 0) { parse_data_http(response); } server.begin(); httpUpdater.setup(&server); Serial.println("HTTP server started"); lastModeSwitch = millis(); } //=================================================== void loop() { // Проверка WiFi if (WiFi.status() != WL_CONNECTED) { Serial.println("WiFi lost, reconnecting..."); if (!setupWiFi()) ESP.restart(); } // Синхронизация NTP каждые 6 часов static unsigned long lastNTPSync = 0; if (millis() - lastNTPSync > 21600000) { if (timeClient.update()) { lastNTPSync = millis(); Serial.println("NTP synchronized"); } } // Обновление дисплея if (flagVis) updateDisplay(); // Отображение секундного тире (только для часов) if (displayMode == 0) { if (millis() - timeReadOld >= 500) { flagSec = !flagSec; timeReadOld = millis(); flagVis = true; } } // Переключение режимов unsigned long current = millis(); if (current - lastModeSwitch >= (unsigned long)visData[displayMode] * 1000) { displayMode = (displayMode + 1) % (sizeof(visData) / sizeof(visData[0])); lastModeSwitch = current; flagVis = true; timeReadOld = current; } // Feed watchdog ESP.wdtFeed(); server.handleClient(); delay(1); // Уменьшил до 1 мс для более быстрого реагирования }