сделан сервер на esp, терминалка имитирующая клинета и терминалка для считывания логов
сделан также клиент на esp, но не проверен
This commit is contained in:
		
						commit
						5cc802c3be
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					/.vscode/
 | 
				
			||||||
							
								
								
									
										289
									
								
								ESP_WifiTest.ino
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								ESP_WifiTest.ino
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,289 @@
 | 
				
			|||||||
 | 
					#include <WiFi.h>
 | 
				
			||||||
 | 
					#include "logs.h"
 | 
				
			||||||
 | 
					#include <Adafruit_NeoPixel.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// -------------------- НАСТРОЙКИ --------------------
 | 
				
			||||||
 | 
					char ssid[32] = "";
 | 
				
			||||||
 | 
					char password[32] = "";
 | 
				
			||||||
 | 
					char serverIP[32] = "198.168.0.1";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define NEOPIXEL_PIN 48
 | 
				
			||||||
 | 
					#define NUMPIXELS 1
 | 
				
			||||||
 | 
					#define BRIGHTNESS 40
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define SERVER  // раскомментировать для сервера
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// -------------------- ФУНКЦИИ --------------------
 | 
				
			||||||
 | 
					Adafruit_NeoPixel pixels(NUMPIXELS, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
 | 
				
			||||||
 | 
					uint8_t brightness = 0; // 0 = выкл, 255 = макс
 | 
				
			||||||
 | 
					bool greenOn = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LogModule logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void toggleGreen() {
 | 
				
			||||||
 | 
					    greenOn = !greenOn; // меняем состояние
 | 
				
			||||||
 | 
					    if (greenOn) {
 | 
				
			||||||
 | 
					        pixels.setPixelColor(0, pixels.Color(0, BRIGHTNESS, 0)); // включаем зелёный
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        pixels.setPixelColor(0, pixels.Color(0, 0, 0));   // выключаем
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    pixels.show();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void setRed() {
 | 
				
			||||||
 | 
					    pixels.setPixelColor(0, pixels.Color(BRIGHTNESS, 0, 0));
 | 
				
			||||||
 | 
					    pixels.show();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void setYellow() {
 | 
				
			||||||
 | 
					    pixels.setPixelColor(0, pixels.Color(BRIGHTNESS, BRIGHTNESS, 0));
 | 
				
			||||||
 | 
					    pixels.show();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void clearLED() {
 | 
				
			||||||
 | 
					    pixels.setPixelColor(0, pixels.Color(0, 0, 0));
 | 
				
			||||||
 | 
					    pixels.show();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					// -------------------- РЕЖИМЫ --------------------
 | 
				
			||||||
 | 
					#ifdef SERVER
 | 
				
			||||||
 | 
					WiFiServer server(1234);
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					WiFiClient client;
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// -------------------- Wi-Fi --------------------
 | 
				
			||||||
 | 
					bool wifiConnecting = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void startWiFi() {
 | 
				
			||||||
 | 
					    WiFi.disconnect(true);
 | 
				
			||||||
 | 
					    WiFi.begin(ssid, password);
 | 
				
			||||||
 | 
					    wifiConnecting = true;
 | 
				
			||||||
 | 
					    Serial.print("Connecting to WiFi");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void handleWiFi() {
 | 
				
			||||||
 | 
					    static uint32_t lastCheck = 0;
 | 
				
			||||||
 | 
					    const uint32_t interval = 500; // проверять каждые 500 мс
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (millis() - lastCheck < interval) return;
 | 
				
			||||||
 | 
					    lastCheck = millis();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (WiFi.status() == WL_CONNECTED) {
 | 
				
			||||||
 | 
					        if (wifiConnecting) {
 | 
				
			||||||
 | 
					            wifiConnecting = false;
 | 
				
			||||||
 | 
					            clearLED();
 | 
				
			||||||
 | 
					            Serial.println("\nWiFi connected");
 | 
				
			||||||
 | 
					            Serial.print("IP: "); Serial.println(WiFi.localIP());
 | 
				
			||||||
 | 
					#ifdef SERVER
 | 
				
			||||||
 | 
					            server.begin();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					#else 
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					          if (!client.connected()) {
 | 
				
			||||||
 | 
					              if (WiFi.status() != WL_CONNECTED) return;
 | 
				
			||||||
 | 
					              setYellow();
 | 
				
			||||||
 | 
					              if (client.connect(serverIP, 1234)) {
 | 
				
			||||||
 | 
					                clearLED();
 | 
				
			||||||
 | 
					                Serial.println("Connected to server");
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        setRed();
 | 
				
			||||||
 | 
					        if (!wifiConnecting) {
 | 
				
			||||||
 | 
					            Serial.println("\nWiFi disconnected. Reconnecting...");
 | 
				
			||||||
 | 
					            startWiFi();
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            Serial.print(".");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// -------------------- UART ДЛЯ НАСТРОЕК --------------------
 | 
				
			||||||
 | 
					void handleUARTNetwork() {
 | 
				
			||||||
 | 
					    static String buffer;
 | 
				
			||||||
 | 
					    while (Serial.available()) {
 | 
				
			||||||
 | 
					        char c = Serial.read();
 | 
				
			||||||
 | 
					        if (c == '\n' || c == '\r') {
 | 
				
			||||||
 | 
					            buffer.trim();
 | 
				
			||||||
 | 
					            if (buffer.length() > 0) {
 | 
				
			||||||
 | 
					                int sep = buffer.indexOf(' ');
 | 
				
			||||||
 | 
					                if (sep > 0) {
 | 
				
			||||||
 | 
					                    String cmd = buffer.substring(0, sep);
 | 
				
			||||||
 | 
					                    String val = buffer.substring(sep + 1);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    if (cmd == "SSID") {
 | 
				
			||||||
 | 
					                        val.toCharArray((char*)ssid, 32);
 | 
				
			||||||
 | 
					                        Serial.print("\nSSID set to: "); Serial.println(ssid);
 | 
				
			||||||
 | 
					                        startWiFi();
 | 
				
			||||||
 | 
					                    } 
 | 
				
			||||||
 | 
					                    else if (cmd == "PASS") {
 | 
				
			||||||
 | 
					                        val.toCharArray((char*)password, 32);
 | 
				
			||||||
 | 
					                        Serial.print("\nPassword set to: "); Serial.println(password);
 | 
				
			||||||
 | 
					                        startWiFi();
 | 
				
			||||||
 | 
					                    } 
 | 
				
			||||||
 | 
					                    else if (cmd == "IP") {
 | 
				
			||||||
 | 
					                        val.toCharArray((char*)serverIP, 16);
 | 
				
			||||||
 | 
					                        Serial.print("\nServer IP set to: "); Serial.println(serverIP);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    else {
 | 
				
			||||||
 | 
					                        logger.handleUART(buffer[0]); // передаем неизвестные команды в логгер
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    logger.handleUART(buffer[0]); // нет пробела — считаем команду неизвестной
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            buffer = "";
 | 
				
			||||||
 | 
					        } 
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            buffer += c;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void setup() {
 | 
				
			||||||
 | 
					    Serial.begin(115200);
 | 
				
			||||||
 | 
					    delay(1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    logger.begin();
 | 
				
			||||||
 | 
					    pixels.begin();
 | 
				
			||||||
 | 
					    pixels.show();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef SERVER
 | 
				
			||||||
 | 
					    Serial.println("SERVER MODE: Enter SSID and PASS via UART, e.g.:\nSSID MyWiFi\nPASS 12345678");
 | 
				
			||||||
 | 
					#else
 | 
				
			||||||
 | 
					    Serial.println("CLIENT MODE: Enter server IP via UART, e.g.:\nIP 192.168.1.100");
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void loop() {
 | 
				
			||||||
 | 
					    handleUARTNetwork();          // UART-функция для настройки сети и получения логов
 | 
				
			||||||
 | 
					    handleWiFi(); // проверка состояния Wi-Fi
 | 
				
			||||||
 | 
					    if (WiFi.status() != WL_CONNECTED) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef SERVER
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    WiFiClient clientConn = server.available();
 | 
				
			||||||
 | 
					    if (!clientConn) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Serial.println("Client connected");
 | 
				
			||||||
 | 
					    while (clientConn.connected()) {
 | 
				
			||||||
 | 
					        if (clientConn.available()) {
 | 
				
			||||||
 | 
					            String msg = clientConn.readStringUntil('\n');
 | 
				
			||||||
 | 
					            msg.trim();
 | 
				
			||||||
 | 
					            if (msg.length() == 0) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Разбор входящего сообщения
 | 
				
			||||||
 | 
					            LogEntry entry;
 | 
				
			||||||
 | 
					            entry.seq = 0;
 | 
				
			||||||
 | 
					            entry.ts = 0;
 | 
				
			||||||
 | 
					            entry.event_type = 0; // RECEIVE
 | 
				
			||||||
 | 
					            memset(entry.payload, 0, sizeof(entry.payload));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            int seqIndex = msg.indexOf("SEQ:");
 | 
				
			||||||
 | 
					            int tsIndex = msg.indexOf("TS:");
 | 
				
			||||||
 | 
					            int payloadIndex = msg.indexOf("PAYLOAD:");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (seqIndex >= 0 && tsIndex > seqIndex && payloadIndex > tsIndex) {
 | 
				
			||||||
 | 
					                String seqStr = msg.substring(seqIndex + 4, tsIndex);
 | 
				
			||||||
 | 
					                seqStr.trim();
 | 
				
			||||||
 | 
					                String tsStr = msg.substring(tsIndex + 3, payloadIndex);
 | 
				
			||||||
 | 
					                tsStr.trim();
 | 
				
			||||||
 | 
					                String payloadStr = msg.substring(payloadIndex + 8);
 | 
				
			||||||
 | 
					                payloadStr.trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                entry.seq = seqStr.toInt();
 | 
				
			||||||
 | 
					                entry.ts = strtoull(tsStr.c_str(), nullptr, 10);
 | 
				
			||||||
 | 
					                int len = min((int)payloadStr.length(), 16);
 | 
				
			||||||
 | 
					                payloadStr.toCharArray(entry.payload, len + 1);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Сохраняем лог
 | 
				
			||||||
 | 
					            logger.writeLog(entry);
 | 
				
			||||||
 | 
					            Serial.print("Received: "); Serial.println(msg);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            toggleGreen();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Создаем SEND-запись
 | 
				
			||||||
 | 
					            LogEntry sendEntry = entry;
 | 
				
			||||||
 | 
					            sendEntry.ts = millis();
 | 
				
			||||||
 | 
					            sendEntry.event_type = 1; // SEND
 | 
				
			||||||
 | 
					            logger.writeLog(sendEntry);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Echo для клиента
 | 
				
			||||||
 | 
					            String echo = "SEQ:" + String(sendEntry.seq) +
 | 
				
			||||||
 | 
					                          " TS:" + String(sendEntry.ts) +
 | 
				
			||||||
 | 
					                          " EVT:SEND PAYLOAD:" + String(sendEntry.payload) + "\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (clientConn.print(echo)) {
 | 
				
			||||||
 | 
					                Serial.print("Sent: "); Serial.println(echo);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                Serial.println("Error sending to client");
 | 
				
			||||||
 | 
					                setRed();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    clientConn.stop();
 | 
				
			||||||
 | 
					    Serial.println("Client disconnected");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#else // CLIENT
 | 
				
			||||||
 | 
					  static uint32_t lastSendMillis = 0;   // время последней отправки
 | 
				
			||||||
 | 
					  const uint32_t sendInterval = 500;    // 500 мс между отправками
 | 
				
			||||||
 | 
					  static uint16_t seqNum = 1;
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  if (!client.connected()) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // проверяем, пора ли отправлять сообщение
 | 
				
			||||||
 | 
					  if (millis() - lastSendMillis >= sendInterval) {
 | 
				
			||||||
 | 
					      lastSendMillis = millis();  // фиксируем время отправки
 | 
				
			||||||
 | 
					      String msg = "SEQ:" + String(seqNum) + " TS:" + String(millis()) + " PAYLOAD:Hard!Text^?123\n";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (client.print(msg)) {
 | 
				
			||||||
 | 
					          Serial.print("Sent: "); Serial.println(msg);
 | 
				
			||||||
 | 
					          toggleGreen();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // Сохраняем SEND-запись
 | 
				
			||||||
 | 
					          LogEntry entry;
 | 
				
			||||||
 | 
					          entry.seq = seqNum;
 | 
				
			||||||
 | 
					          entry.ts = millis();
 | 
				
			||||||
 | 
					          entry.event_type = 1; // SEND
 | 
				
			||||||
 | 
					          msg.substring(msg.indexOf("PAYLOAD:") + 8).toCharArray(entry.payload, 16);
 | 
				
			||||||
 | 
					          logger.writeLog(entry);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          seqNum++;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					          Serial.println("Error sending");
 | 
				
			||||||
 | 
					          setRed();
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Чтение ответа без блокировки
 | 
				
			||||||
 | 
					  while (client.available()) {
 | 
				
			||||||
 | 
					      String resp = client.readStringUntil('\n');
 | 
				
			||||||
 | 
					      resp.trim();
 | 
				
			||||||
 | 
					      if (resp.length() == 0) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      Serial.print("Received: "); Serial.println(resp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Сохраняем RECEIVE-запись
 | 
				
			||||||
 | 
					      LogEntry entry;
 | 
				
			||||||
 | 
					      entry.seq = resp.indexOf("SEQ:");
 | 
				
			||||||
 | 
					      entry.ts = resp.indexOf("TS:");
 | 
				
			||||||
 | 
					      entry.event_type = 0; // RECEIVE
 | 
				
			||||||
 | 
					      int payloadIndex = resp.indexOf("PAYLOAD:");
 | 
				
			||||||
 | 
					      if (payloadIndex >= 0) {
 | 
				
			||||||
 | 
					          String payloadStr = resp.substring(payloadIndex + 8);
 | 
				
			||||||
 | 
					          payloadStr.toCharArray(entry.payload, 16);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      logger.writeLog(entry);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      toggleGreen();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										131
									
								
								esp_client_emu.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								esp_client_emu.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,131 @@
 | 
				
			|||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import socket
 | 
				
			||||||
 | 
					import threading
 | 
				
			||||||
 | 
					import time
 | 
				
			||||||
 | 
					from PySide2.QtWidgets import (
 | 
				
			||||||
 | 
					    QApplication, QWidget, QVBoxLayout, QLabel, QLineEdit,
 | 
				
			||||||
 | 
					    QPushButton, QMessageBox, QSpinBox
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from PySide2.QtCore import Qt, Signal, QObject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LogSignal(QObject):
 | 
				
			||||||
 | 
					    log_msg = Signal(str)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ESPClientTerminal(QWidget):
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        super().__init__()
 | 
				
			||||||
 | 
					        self.setWindowTitle("ESP32 TCP Client")
 | 
				
			||||||
 | 
					        self.resize(400, 200)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self.layout = QVBoxLayout(self)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self.layout.addWidget(QLabel("Server IP:"))
 | 
				
			||||||
 | 
					        self.ip_input = QLineEdit("192.168.0.96")
 | 
				
			||||||
 | 
					        self.layout.addWidget(self.ip_input)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self.layout.addWidget(QLabel("Server port:"))
 | 
				
			||||||
 | 
					        self.port_input = QLineEdit("1234")
 | 
				
			||||||
 | 
					        self.layout.addWidget(self.port_input)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self.layout.addWidget(QLabel("Packet interval (ms):"))
 | 
				
			||||||
 | 
					        self.interval_input = QSpinBox()
 | 
				
			||||||
 | 
					        self.interval_input.setRange(50, 10000)
 | 
				
			||||||
 | 
					        self.interval_input.setValue(500)
 | 
				
			||||||
 | 
					        self.layout.addWidget(self.interval_input)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self.start_btn = QPushButton("Start Sending")
 | 
				
			||||||
 | 
					        self.start_btn.clicked.connect(self.start_sending)
 | 
				
			||||||
 | 
					        self.layout.addWidget(self.start_btn)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self.stop_btn = QPushButton("Stop Sending")
 | 
				
			||||||
 | 
					        self.stop_btn.clicked.connect(self.stop_sending)
 | 
				
			||||||
 | 
					        self.stop_btn.setEnabled(False)
 | 
				
			||||||
 | 
					        self.layout.addWidget(self.stop_btn)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self.status_label = QLabel("")
 | 
				
			||||||
 | 
					        self.status_label.setAlignment(Qt.AlignCenter)
 | 
				
			||||||
 | 
					        self.layout.addWidget(self.status_label)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self.running = False
 | 
				
			||||||
 | 
					        self.thread = None
 | 
				
			||||||
 | 
					        self.log_signal = LogSignal()
 | 
				
			||||||
 | 
					        self.log_signal.log_msg.connect(self.update_status)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def update_status(self, msg):
 | 
				
			||||||
 | 
					        self.status_label.setText(msg)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def start_sending(self):
 | 
				
			||||||
 | 
					        server_ip = self.ip_input.text().strip()
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            server_port = int(self.port_input.text().strip())
 | 
				
			||||||
 | 
					        except ValueError:
 | 
				
			||||||
 | 
					            QMessageBox.warning(self, "Error", "Invalid port")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        interval = self.interval_input.value() / 1000.0  # ms -> sec
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if not server_ip:
 | 
				
			||||||
 | 
					            QMessageBox.warning(self, "Error", "Enter server IP")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self.running = True
 | 
				
			||||||
 | 
					        self.start_btn.setEnabled(False)
 | 
				
			||||||
 | 
					        self.stop_btn.setEnabled(True)
 | 
				
			||||||
 | 
					        self.thread = threading.Thread(target=self.send_loop, args=(server_ip, server_port, interval), daemon=True)
 | 
				
			||||||
 | 
					        self.thread.start()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def stop_sending(self):
 | 
				
			||||||
 | 
					        self.running = False
 | 
				
			||||||
 | 
					        self.start_btn.setEnabled(True)
 | 
				
			||||||
 | 
					        self.stop_btn.setEnabled(False)
 | 
				
			||||||
 | 
					        self.status_label.setText("Stopped sending")
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					    def send_loop(self, ip, port, interval):
 | 
				
			||||||
 | 
					        seq = 0
 | 
				
			||||||
 | 
					        sock = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        while self.running:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                # Если сокета нет – пытаемся подключиться
 | 
				
			||||||
 | 
					                if sock is None:
 | 
				
			||||||
 | 
					                    self.log_signal.log_msg.emit(f"Connecting to {ip}:{port}...")
 | 
				
			||||||
 | 
					                    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
				
			||||||
 | 
					                    sock.settimeout(5)
 | 
				
			||||||
 | 
					                    sock.connect((ip, port))
 | 
				
			||||||
 | 
					                    self.log_signal.log_msg.emit(f"Connected to {ip}:{port}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Отправляем данные
 | 
				
			||||||
 | 
					                seq += 1
 | 
				
			||||||
 | 
					                ts = int(time.time() * 1000)
 | 
				
			||||||
 | 
					                payload = "PING"
 | 
				
			||||||
 | 
					                msg = f"SEQ:{seq} TS:{ts} PAYLOAD:{payload}\n"
 | 
				
			||||||
 | 
					                sock.sendall(msg.encode())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Читаем ответ
 | 
				
			||||||
 | 
					                data = sock.recv(1024).decode()
 | 
				
			||||||
 | 
					                self.log_signal.log_msg.emit(f"Sent SEQ:{seq} | Echo: {data.strip()}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                time.sleep(interval)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            except Exception as e:
 | 
				
			||||||
 | 
					                self.log_signal.log_msg.emit(f"Connection lost: {e}")
 | 
				
			||||||
 | 
					                if sock:
 | 
				
			||||||
 | 
					                    try:
 | 
				
			||||||
 | 
					                        sock.close()
 | 
				
			||||||
 | 
					                    except:
 | 
				
			||||||
 | 
					                        pass
 | 
				
			||||||
 | 
					                    sock = None
 | 
				
			||||||
 | 
					                time.sleep(2)  # ждём перед реконнектом
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if sock:
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                sock.close()
 | 
				
			||||||
 | 
					            except:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					        self.log_signal.log_msg.emit("Stopped / Disconnected")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    app = QApplication(sys.argv)
 | 
				
			||||||
 | 
					    w = ESPClientTerminal()
 | 
				
			||||||
 | 
					    w.show()
 | 
				
			||||||
 | 
					    sys.exit(app.exec_())
 | 
				
			||||||
							
								
								
									
										224
									
								
								getLogs.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								getLogs.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,224 @@
 | 
				
			|||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import csv
 | 
				
			||||||
 | 
					import serial
 | 
				
			||||||
 | 
					import serial.tools.list_ports
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import subprocess
 | 
				
			||||||
 | 
					from PySide2.QtWidgets import (
 | 
				
			||||||
 | 
					    QApplication, QWidget, QVBoxLayout, QLabel, QLineEdit,
 | 
				
			||||||
 | 
					    QPushButton, QFileDialog, QMessageBox, QComboBox, QHBoxLayout
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from PySide2.QtCore import Qt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ESPLoggerTerminal(QWidget):
 | 
				
			||||||
 | 
					    def __init__(self):
 | 
				
			||||||
 | 
					        super().__init__()
 | 
				
			||||||
 | 
					        self.setWindowTitle("ESP32 Log Downloader")
 | 
				
			||||||
 | 
					        self.resize(500, 300)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        layout = QVBoxLayout(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # --- COM-port selection ---
 | 
				
			||||||
 | 
					        port_layout = QHBoxLayout()
 | 
				
			||||||
 | 
					        port_layout.addWidget(QLabel("Serial port:"))
 | 
				
			||||||
 | 
					        self.port_combo = QComboBox()
 | 
				
			||||||
 | 
					        port_layout.addWidget(self.port_combo)
 | 
				
			||||||
 | 
					        self.refresh_btn = QPushButton("Refresh")
 | 
				
			||||||
 | 
					        self.refresh_btn.clicked.connect(self.refresh_ports)
 | 
				
			||||||
 | 
					        port_layout.addWidget(self.refresh_btn)
 | 
				
			||||||
 | 
					        layout.addLayout(port_layout)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        layout.addWidget(QLabel("Baud rate:"))
 | 
				
			||||||
 | 
					        self.baud_input = QLineEdit("115200")
 | 
				
			||||||
 | 
					        layout.addWidget(self.baud_input)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        layout.addWidget(QLabel("CSV separator:"))
 | 
				
			||||||
 | 
					        self.sep_input = QLineEdit(";")
 | 
				
			||||||
 | 
					        layout.addWidget(self.sep_input)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # --- File selection ---
 | 
				
			||||||
 | 
					        file_layout = QHBoxLayout()
 | 
				
			||||||
 | 
					        self.file_edit = QLineEdit()
 | 
				
			||||||
 | 
					        file_layout.addWidget(self.file_edit)
 | 
				
			||||||
 | 
					        self.browse_btn = QPushButton("Browse...")
 | 
				
			||||||
 | 
					        self.browse_btn.clicked.connect(self.select_file)
 | 
				
			||||||
 | 
					        file_layout.addWidget(self.browse_btn)
 | 
				
			||||||
 | 
					        layout.addLayout(file_layout)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        open_layout = QHBoxLayout()
 | 
				
			||||||
 | 
					        self.open_file_btn = QPushButton("Open File")
 | 
				
			||||||
 | 
					        self.open_file_btn.clicked.connect(self.open_file)
 | 
				
			||||||
 | 
					        open_layout.addWidget(self.open_file_btn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.open_folder_btn = QPushButton("Open Folder")
 | 
				
			||||||
 | 
					        self.open_folder_btn.clicked.connect(self.open_folder)
 | 
				
			||||||
 | 
					        open_layout.addWidget(self.open_folder_btn)
 | 
				
			||||||
 | 
					        layout.addLayout(open_layout)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # --- Buttons ---
 | 
				
			||||||
 | 
					        self.get_log_btn = QPushButton("Get Log and Save CSV")
 | 
				
			||||||
 | 
					        self.get_log_btn.clicked.connect(self.get_log)
 | 
				
			||||||
 | 
					        layout.addWidget(self.get_log_btn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.clear_log_btn = QPushButton("Clear Logs")
 | 
				
			||||||
 | 
					        self.clear_log_btn.clicked.connect(self.clear_logs)
 | 
				
			||||||
 | 
					        layout.addWidget(self.clear_log_btn)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self.status_label = QLabel("")
 | 
				
			||||||
 | 
					        self.status_label.setAlignment(Qt.AlignCenter)
 | 
				
			||||||
 | 
					        layout.addWidget(self.status_label)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        # загрузка списка портов при запуске
 | 
				
			||||||
 | 
					        self.refresh_ports()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def refresh_ports(self):
 | 
				
			||||||
 | 
					        """Обновить список доступных COM/tty"""
 | 
				
			||||||
 | 
					        self.port_combo.clear()
 | 
				
			||||||
 | 
					        ports = serial.tools.list_ports.comports()
 | 
				
			||||||
 | 
					        for p in ports:
 | 
				
			||||||
 | 
					            self.port_combo.addItem(f"{p.device} ({p.description})", p.device)
 | 
				
			||||||
 | 
					        if not ports:
 | 
				
			||||||
 | 
					            self.port_combo.addItem("No ports found", "")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def select_file(self):
 | 
				
			||||||
 | 
					        fname, _ = QFileDialog.getSaveFileName(self, "Select CSV File", "", "CSV Files (*.csv)")
 | 
				
			||||||
 | 
					        if fname:
 | 
				
			||||||
 | 
					            self.file_edit.setText(fname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def open_file(self):
 | 
				
			||||||
 | 
					        fname = self.file_edit.text().strip()
 | 
				
			||||||
 | 
					        if os.path.isfile(fname):
 | 
				
			||||||
 | 
					            os.startfile(fname)  # Windows only
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            QMessageBox.warning(self, "Warning", "File does not exist")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def open_folder(self):
 | 
				
			||||||
 | 
					        fname = self.file_edit.text().strip()
 | 
				
			||||||
 | 
					        if os.path.isfile(fname):
 | 
				
			||||||
 | 
					            subprocess.run(["explorer", f"/select,{os.path.abspath(fname)}"])
 | 
				
			||||||
 | 
					        elif os.path.isdir(fname):
 | 
				
			||||||
 | 
					            subprocess.run(["explorer", os.path.abspath(fname)])
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            QMessageBox.warning(self, "Warning", "File does not exist")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def get_log(self):
 | 
				
			||||||
 | 
					        port = self.port_combo.currentData()
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            baud = int(self.baud_input.text().strip())
 | 
				
			||||||
 | 
					        except ValueError:
 | 
				
			||||||
 | 
					            QMessageBox.warning(self, "Error", "Invalid baud rate")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        sep = self.sep_input.text() or ","
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if not port:
 | 
				
			||||||
 | 
					            QMessageBox.warning(self, "Error", "Select a serial port")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fname = self.file_edit.text().strip()
 | 
				
			||||||
 | 
					        if not fname:
 | 
				
			||||||
 | 
					            QMessageBox.warning(self, "Error", "Select a CSV file to save logs")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.status_label.setText("Connecting to ESP32...")
 | 
				
			||||||
 | 
					            QApplication.processEvents()
 | 
				
			||||||
 | 
					            ser = serial.Serial(port, baud, timeout=2)  # короче таймаут
 | 
				
			||||||
 | 
					            ser.reset_input_buffer()
 | 
				
			||||||
 | 
					            ser.reset_output_buffer()
 | 
				
			||||||
 | 
					            ser.write(b'd\n')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Ждём первую реакцию от ESP32 (например "=== LOG DUMP START ===")
 | 
				
			||||||
 | 
					            pre_line = ser.readline().decode(errors='ignore').strip()
 | 
				
			||||||
 | 
					            first_line = ser.readline().decode(errors='ignore').strip()
 | 
				
			||||||
 | 
					            if not first_line or "LOG DUMP" not in first_line:
 | 
				
			||||||
 | 
					                ser.close()
 | 
				
			||||||
 | 
					                QMessageBox.warning(self, "Error", "Selected port is not responding like ESP32")
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            self.status_label.setText("Receiving log...")
 | 
				
			||||||
 | 
					            QApplication.processEvents()
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            import time
 | 
				
			||||||
 | 
					            lines = []
 | 
				
			||||||
 | 
					            start_time = time.time()
 | 
				
			||||||
 | 
					            timeout_sec = 30
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            while True:
 | 
				
			||||||
 | 
					                if time.time() - start_time > timeout_sec:
 | 
				
			||||||
 | 
					                    QMessageBox.warning(self, "Timeout", "No complete log received (timeout).")
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                line = ser.readline().decode(errors='ignore').strip()
 | 
				
			||||||
 | 
					                if line:
 | 
				
			||||||
 | 
					                    start_time = time.time()
 | 
				
			||||||
 | 
					                if not line:
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					                if line.startswith("=== LOG DUMP END ==="):
 | 
				
			||||||
 | 
					                    break
 | 
				
			||||||
 | 
					                if line.startswith("Entry "):
 | 
				
			||||||
 | 
					                    lines.append(line)
 | 
				
			||||||
 | 
					                    self.status_label.setText(f"Received {len(lines)} entries...")
 | 
				
			||||||
 | 
					                    QApplication.processEvents()
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            QMessageBox.critical(self, "Error", f"Serial error: {e}")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            if 'ser' in locals() and ser and ser.is_open:
 | 
				
			||||||
 | 
					                ser.close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not lines:
 | 
				
			||||||
 | 
					            QMessageBox.information(self, "Info", "No log data received")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            with open(fname, "w", newline="") as f:
 | 
				
			||||||
 | 
					                writer = csv.writer(f, delimiter=sep)
 | 
				
			||||||
 | 
					                writer.writerow(["SEQ", "TS", "EVT", "PAYLOAD"])
 | 
				
			||||||
 | 
					                for line in lines:
 | 
				
			||||||
 | 
					                    parts = line.split()
 | 
				
			||||||
 | 
					                    seq = parts[2].split(":")[1]
 | 
				
			||||||
 | 
					                    ts = parts[3].split(":")[1]
 | 
				
			||||||
 | 
					                    evt = parts[4].split(":")[1]
 | 
				
			||||||
 | 
					                    payload = ""
 | 
				
			||||||
 | 
					                    if len(parts) > 5 and parts[5].startswith("PAYLOAD:"):
 | 
				
			||||||
 | 
					                        payload = line.split("PAYLOAD:", 1)[1]
 | 
				
			||||||
 | 
					                    writer.writerow([seq, ts, evt, payload])
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            QMessageBox.critical(self, "Error", f"CSV write error: {e}")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        self.status_label.setText(f"Log saved to {fname}")
 | 
				
			||||||
 | 
					        QMessageBox.information(self, "Success", f"CSV saved: {fname}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def clear_logs(self):
 | 
				
			||||||
 | 
					        """Очистка логов на ESP32"""
 | 
				
			||||||
 | 
					        port = self.port_combo.currentData()
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            baud = int(self.baud_input.text().strip())
 | 
				
			||||||
 | 
					        except ValueError:
 | 
				
			||||||
 | 
					            QMessageBox.warning(self, "Error", "Invalid baud rate")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not port:
 | 
				
			||||||
 | 
					            QMessageBox.warning(self, "Error", "Select a serial port")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            ser = serial.Serial(port, baud, timeout=3)
 | 
				
			||||||
 | 
					            ser.write(b'c\n')
 | 
				
			||||||
 | 
					            response = ser.readline().decode(errors='ignore').strip()
 | 
				
			||||||
 | 
					            ser.close()
 | 
				
			||||||
 | 
					            if "CLEARED" in response:
 | 
				
			||||||
 | 
					                self.status_label.setText("Logs cleared on ESP32")
 | 
				
			||||||
 | 
					                QMessageBox.information(self, "Success", "Logs cleared on ESP32")
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                QMessageBox.warning(self, "Warning", "No confirmation from ESP32")
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            QMessageBox.critical(self, "Error", f"Serial error: {e}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    app = QApplication(sys.argv)
 | 
				
			||||||
 | 
					    w = ESPLoggerTerminal()
 | 
				
			||||||
 | 
					    w.show()
 | 
				
			||||||
 | 
					    sys.exit(app.exec_())
 | 
				
			||||||
							
								
								
									
										371
									
								
								logs.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										371
									
								
								logs.cpp
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,371 @@
 | 
				
			|||||||
 | 
					#include "logs.h"
 | 
				
			||||||
 | 
					#include <esp_partition.h>
 | 
				
			||||||
 | 
					#include <esp_attr.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CRC8 для проверки целостности
 | 
				
			||||||
 | 
					uint8_t LogModule::calculateCRC(const LogEntry &entry) {
 | 
				
			||||||
 | 
					    uint8_t crc = 0;
 | 
				
			||||||
 | 
					    const uint8_t* data = (const uint8_t*)&entry;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Считаем CRC по всем байтам структуры КРОМЕ поля crc
 | 
				
			||||||
 | 
					    for (size_t i = 0; i < offsetof(LogEntry, crc); i++) {
 | 
				
			||||||
 | 
					        crc ^= data[i];
 | 
				
			||||||
 | 
					        for (int j = 0; j < 8; j++) {
 | 
				
			||||||
 | 
					            crc = (crc & 0x80) ? (crc << 1) ^ 0x07 : (crc << 1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Пропускаем поле crc и считаем остальные
 | 
				
			||||||
 | 
					    for (size_t i = offsetof(LogEntry, crc) + 1; i < ENTRY_SIZE; i++) {
 | 
				
			||||||
 | 
					        crc ^= data[i];
 | 
				
			||||||
 | 
					        for (int j = 0; j < 8; j++) {
 | 
				
			||||||
 | 
					            crc = (crc & 0x80) ? (crc << 1) ^ 0x07 : (crc << 1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return crc;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool LogModule::verifyEntry(const LogEntry &entry) {
 | 
				
			||||||
 | 
					    uint8_t calculated_crc = calculateCRC(entry);
 | 
				
			||||||
 | 
					    bool valid = (entry.crc == calculated_crc);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!valid) {
 | 
				
			||||||
 | 
					        Serial.printf("CRC mismatch: stored=0x%02X, calculated=0x%02X\n", 
 | 
				
			||||||
 | 
					                     entry.crc, calculated_crc);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return valid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LogModule::LogModule() : partition(nullptr), write_pos(0), total_entries(0), partition_size(0) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void LogModule::recoverLog() {
 | 
				
			||||||
 | 
					    Serial.println("Starting log recovery...");
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Ищем последнюю валидную запись
 | 
				
			||||||
 | 
					    uint32_t last_valid = findLastValidEntry();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (last_valid == UINT32_MAX) {
 | 
				
			||||||
 | 
					        // Не нашли ни одной валидной записи
 | 
				
			||||||
 | 
					        Serial.println("No valid entries found, clearing log");
 | 
				
			||||||
 | 
					        write_pos = 0;
 | 
				
			||||||
 | 
					        total_entries = 0;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        // Восстанавливаем метаданные
 | 
				
			||||||
 | 
					        write_pos = (last_valid + 1) % ((partition_size - 8) / ENTRY_SIZE);
 | 
				
			||||||
 | 
					        total_entries = last_valid + 1;
 | 
				
			||||||
 | 
					        Serial.printf("Recovered %u valid entries\n", total_entries);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    saveMetadata();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint32_t LogModule::findLastValidEntry() {
 | 
				
			||||||
 | 
					    uint32_t max_entries = (partition_size - 8) / ENTRY_SIZE;
 | 
				
			||||||
 | 
					    uint32_t last_valid = UINT32_MAX;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    for (uint32_t i = 0; i < max_entries; i++) {
 | 
				
			||||||
 | 
					        uint32_t offset = 8 + (i * ENTRY_SIZE); // ← ИСПРАВЛЕНО!
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        LogEntry entry;
 | 
				
			||||||
 | 
					        if (readRaw(offset, (uint8_t*)&entry, ENTRY_SIZE)) {
 | 
				
			||||||
 | 
					            // Проверяем не пустая ли запись
 | 
				
			||||||
 | 
					            bool is_erased = true;
 | 
				
			||||||
 | 
					            for (size_t j = 0; j < ENTRY_SIZE; j++) {
 | 
				
			||||||
 | 
					                if (((uint8_t*)&entry)[j] != 0xFF && ((uint8_t*)&entry)[j] != 0x00) {
 | 
				
			||||||
 | 
					                    is_erased = false;
 | 
				
			||||||
 | 
					                    break;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (is_erased) {
 | 
				
			||||||
 | 
					                Serial.printf("Empty entry at position %u, stopping scan\n", i);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (verifyEntry(entry)) {
 | 
				
			||||||
 | 
					                last_valid = i;
 | 
				
			||||||
 | 
					                Serial.printf("Valid entry at position %u: SEQ:%u\n", i, entry.seq);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                Serial.printf("Corrupted entry at position %u\n", i);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return last_valid;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool LogModule::validateAllEntries() {
 | 
				
			||||||
 | 
					    if (total_entries == 0) return true;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    uint32_t max_entries = (partition_size - 8) / ENTRY_SIZE;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    for (uint32_t i = 0; i < total_entries; i++) {
 | 
				
			||||||
 | 
					        uint32_t index = (write_pos - total_entries + i + max_entries) % max_entries;
 | 
				
			||||||
 | 
					        uint32_t offset = 8 + (index * ENTRY_SIZE); // ← ИСПРАВЛЕНО!
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        LogEntry entry;
 | 
				
			||||||
 | 
					        if (!readRaw(offset, (uint8_t*)&entry, ENTRY_SIZE)) {
 | 
				
			||||||
 | 
					            Serial.printf("Read failed at index %u\n", index);
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Проверяем не пустая ли запись (все 0xFF или 0x00)
 | 
				
			||||||
 | 
					        bool is_erased = true;
 | 
				
			||||||
 | 
					        for (size_t j = 0; j < ENTRY_SIZE; j++) {
 | 
				
			||||||
 | 
					            uint8_t byte = ((uint8_t*)&entry)[j];
 | 
				
			||||||
 | 
					            if (byte != 0xFF && byte != 0x00) {
 | 
				
			||||||
 | 
					                is_erased = false;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if (is_erased) {
 | 
				
			||||||
 | 
					            Serial.printf("Erased entry at index %u\n", index);
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if (!verifyEntry(entry)) {
 | 
				
			||||||
 | 
					            Serial.printf("CRC mismatch at index %u\n", index);
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool LogModule::begin() {
 | 
				
			||||||
 | 
					    partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, 
 | 
				
			||||||
 | 
					                                       ESP_PARTITION_SUBTYPE_ANY, 
 | 
				
			||||||
 | 
					                                       "log_storage");
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (!partition) {
 | 
				
			||||||
 | 
					        Serial.println("Log partition not found!");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    partition_size = partition->size;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Проверяем что партиция имеет правильный размер (2 МБ)
 | 
				
			||||||
 | 
					    if (partition_size != LOG_PARTITION_SIZE) {
 | 
				
			||||||
 | 
					        Serial.printf("ERROR: Partition size mismatch! Expected: %u bytes, Got: %u bytes\n",
 | 
				
			||||||
 | 
					                     LOG_PARTITION_SIZE, partition_size);
 | 
				
			||||||
 | 
					        Serial.println("Check partitions.csv file");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Serial.printf("Log partition: %u bytes (2 MB)\n", partition_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Инициализируем Preferences для метаданных
 | 
				
			||||||
 | 
					    if (!prefs.begin("log_storage", false)) {
 | 
				
			||||||
 | 
					        Serial.println("Failed to initialize Preferences");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Пытаемся загрузить метаданные
 | 
				
			||||||
 | 
					    if (loadMetadata()) {
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Проверяем целостность
 | 
				
			||||||
 | 
					        if (!validateAllEntries()) {
 | 
				
			||||||
 | 
					            Serial.println("Log corruption detected, reformatting...");
 | 
				
			||||||
 | 
					            esp_partition_erase_range(partition, 0, partition_size);
 | 
				
			||||||
 | 
					            write_pos = 0;
 | 
				
			||||||
 | 
					            total_entries = 0;
 | 
				
			||||||
 | 
					            saveMetadata();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        // Первый запуск или поврежденные метаданные
 | 
				
			||||||
 | 
					        Serial.println("Initializing new log storage...");
 | 
				
			||||||
 | 
					        esp_partition_erase_range(partition, 0, partition_size);
 | 
				
			||||||
 | 
					        write_pos = 0;
 | 
				
			||||||
 | 
					        total_entries = 0;
 | 
				
			||||||
 | 
					        saveMetadata();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Serial.printf("Log system ready. Max entries: %u\n", (partition_size - 8) / ENTRY_SIZE);
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool LogModule::writeRaw(uint32_t offset, const uint8_t* data, size_t size) {
 | 
				
			||||||
 | 
					    // Проверяем выравнивание
 | 
				
			||||||
 | 
					    if (size % 4 != 0 || offset % 4 != 0) {
 | 
				
			||||||
 | 
					        Serial.printf("FATAL: Alignment error: offset=0x%X, size=%u\n", offset, size);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    esp_err_t err = esp_partition_write(partition, offset, data, size);
 | 
				
			||||||
 | 
					    if (err != ESP_OK) {
 | 
				
			||||||
 | 
					        Serial.printf("Write failed: err=0x%X\n", err);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool LogModule::readRaw(uint32_t offset, uint8_t* data, size_t size) {
 | 
				
			||||||
 | 
					    if (size % 4 != 0 || offset % 4 != 0) {
 | 
				
			||||||
 | 
					#ifdef FLASH_PRINT
 | 
				
			||||||
 | 
					        Serial.printf("FATAL: Alignment error: offset=0x%X, size=%u\n", offset, size);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    esp_err_t err = esp_partition_read(partition, offset, data, size);
 | 
				
			||||||
 | 
					    return err == ESP_OK;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void LogModule::saveMetadata() {
 | 
				
			||||||
 | 
					    prefs.putUInt("write_pos", write_pos);
 | 
				
			||||||
 | 
					    prefs.putUInt("total_entries", total_entries);
 | 
				
			||||||
 | 
					#ifdef META_PRINT
 | 
				
			||||||
 | 
					    Serial.printf("Metadata saved: write_pos=%u, total_entries=%u\n", write_pos, total_entries);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool LogModule::loadMetadata() {
 | 
				
			||||||
 | 
					    write_pos = prefs.getUInt("write_pos", 0);
 | 
				
			||||||
 | 
					    total_entries = prefs.getUInt("total_entries", 0);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Валидация загруженных метаданных
 | 
				
			||||||
 | 
					    uint32_t max_entries = (partition_size - 8) / ENTRY_SIZE;
 | 
				
			||||||
 | 
					    if (write_pos >= max_entries || total_entries > max_entries) {
 | 
				
			||||||
 | 
					        Serial.printf("Invalid metadata, resetting: write_pos=%u, total_entries=%u\n", 
 | 
				
			||||||
 | 
					                     write_pos, total_entries);
 | 
				
			||||||
 | 
					        write_pos = 0;
 | 
				
			||||||
 | 
					        total_entries = 0;
 | 
				
			||||||
 | 
					        saveMetadata();
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					#ifdef META_PRINT
 | 
				
			||||||
 | 
					    Serial.printf("Metadata loaded: write_pos=%u, total_entries=%u\n", write_pos, total_entries);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool LogModule::writeLog(const LogEntry &entry) {
 | 
				
			||||||
 | 
					    if (!partition) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Подготавливаем запись с CRC
 | 
				
			||||||
 | 
					    LogEntry safe_entry = entry;
 | 
				
			||||||
 | 
					    safe_entry.crc = calculateCRC(safe_entry);
 | 
				
			||||||
 | 
					    memset(safe_entry.reserved, 0, sizeof(safe_entry.reserved)); // Обнуляем reserved
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Вычисляем позицию
 | 
				
			||||||
 | 
					    uint32_t entry_offset = 8 + (write_pos * ENTRY_SIZE); // 8 байт метаданных
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					#ifdef FLASH_PRINT
 | 
				
			||||||
 | 
					    Serial.printf("Writing to offset 0x%X: SEQ:%u Size:%u\n", 
 | 
				
			||||||
 | 
					                 entry_offset, safe_entry.seq, ENTRY_SIZE);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Записываем запись
 | 
				
			||||||
 | 
					    if (writeRaw(entry_offset, (uint8_t*)&safe_entry, ENTRY_SIZE)) {
 | 
				
			||||||
 | 
					        write_pos = (write_pos + 1) % ((partition_size - 8) / ENTRY_SIZE);
 | 
				
			||||||
 | 
					        total_entries++;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // Сохраняем метаlанные
 | 
				
			||||||
 | 
					        saveMetadata();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					#ifdef LOGGED_PRINT
 | 
				
			||||||
 | 
					    String tsStr = String(entry.ts);
 | 
				
			||||||
 | 
					    Serial.println("Logged SEQ:" + String(entry.seq) +
 | 
				
			||||||
 | 
					                " TS:" + tsStr +
 | 
				
			||||||
 | 
					                " EVT:" + (entry.event_type == 0 ? "RECV" : "SEND") +
 | 
				
			||||||
 | 
					                " PAYLOAD:" + String(entry.payload) +
 | 
				
			||||||
 | 
					                " CRC:0x" + String(safe_entry.crc, HEX));
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void LogModule::dumpLogs() {
 | 
				
			||||||
 | 
					    if (!partition) return;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // БЛОКИРУЕМ ВЫПОЛНЕНИЕ ПОКА ВСЕ НЕ ОТПРАВИМ
 | 
				
			||||||
 | 
					    Serial.println("\n=== LOG DUMP START ===");
 | 
				
			||||||
 | 
					    Serial.flush(); // Ждем отправки
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Serial.printf("Total entries: %u, Write pos: %u\n", total_entries, write_pos);
 | 
				
			||||||
 | 
					    Serial.flush();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint32_t max_entries = (partition_size - 8) / ENTRY_SIZE;
 | 
				
			||||||
 | 
					    uint32_t entries_to_read = min(total_entries, max_entries);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // ПРАВИЛЬНАЯ логика для кольцевого буфера
 | 
				
			||||||
 | 
					    uint32_t start_index = 0;
 | 
				
			||||||
 | 
					    if (total_entries > max_entries) {
 | 
				
			||||||
 | 
					        // Буфер переполнен - начинаем с позиции, где находятся самые старые данные
 | 
				
			||||||
 | 
					        start_index = write_pos;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Serial.printf("Reading %u entries, start index: %u\n", entries_to_read, start_index);
 | 
				
			||||||
 | 
					    Serial.flush();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (uint32_t i = 0; i < entries_to_read; i++) {
 | 
				
			||||||
 | 
					        uint32_t index = (start_index + i) % max_entries;
 | 
				
			||||||
 | 
					        uint32_t offset = 8 + (index * ENTRY_SIZE);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        LogEntry entry;
 | 
				
			||||||
 | 
					        if (readRaw(offset, (uint8_t*)&entry, ENTRY_SIZE)) {
 | 
				
			||||||
 | 
					            if (verifyEntry(entry)) {
 | 
				
			||||||
 | 
					                Serial.printf("Entry %u: SEQ:%u TS:%llu EVT:%s PAYLOAD:%s\n",
 | 
				
			||||||
 | 
					                             i, entry.seq, entry.ts,
 | 
				
			||||||
 | 
					                             entry.event_type == 0 ? "RECV" : "SEND",
 | 
				
			||||||
 | 
					                             entry.payload);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                Serial.printf("Entry %u: CORRUPTED - SEQ:%u CRC:0x%02X\n", 
 | 
				
			||||||
 | 
					                             i, entry.seq, entry.crc);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // ПРИНУДИТЕЛЬНО ЖДЕМ ОТПРАВКИ КАЖДОЙ ЗАПИСИ
 | 
				
			||||||
 | 
					            Serial.flush();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            Serial.printf("Read failed at index %u, offset 0x%X\n", i, offset);
 | 
				
			||||||
 | 
					            Serial.flush();
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ФИНАЛЬНОЕ ОЖИДАНИЕ
 | 
				
			||||||
 | 
					    Serial.println("=== LOG DUMP END ===");
 | 
				
			||||||
 | 
					    Serial.flush();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void LogModule::clearLogs() {
 | 
				
			||||||
 | 
					    Serial.println("\n=== LOG ERASING ===");
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Очищаем данные в партиции
 | 
				
			||||||
 | 
					    esp_partition_erase_range(partition, 0, partition_size);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Очищаем метаданные в Preferences
 | 
				
			||||||
 | 
					    prefs.clear();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Сбрасываем переменные
 | 
				
			||||||
 | 
					    write_pos = 0;
 | 
				
			||||||
 | 
					    total_entries = 0;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // Сохраняем начальные метаданные
 | 
				
			||||||
 | 
					    saveMetadata();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    Serial.println("=== LOG CLEARED ===");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void LogModule::handleUART(char cmd, char dumpCmd, char clearCmd) {
 | 
				
			||||||
 | 
					        if (cmd == dumpCmd) dumpLogs();
 | 
				
			||||||
 | 
					        else if (cmd == clearCmd) clearLogs();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										61
									
								
								logs.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								logs.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					#ifndef LOG_MODULE_H
 | 
				
			||||||
 | 
					#define LOG_MODULE_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <Arduino.h>
 | 
				
			||||||
 | 
					#include <esp_partition.h>
 | 
				
			||||||
 | 
					#include <Preferences.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define PAYLOAD_SIZE 16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Используем partition для логов
 | 
				
			||||||
 | 
					#define LOG_PARTITION_SUBTYPE 0x70
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//#define META_PRINT
 | 
				
			||||||
 | 
					//#define FLASH_PRINT
 | 
				
			||||||
 | 
					//#define LOGGED_PRINT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Размер партиции для логов - строго 2 МБ
 | 
				
			||||||
 | 
					#define LOG_PARTITION_SIZE 0x200000  // 2 MB = 2097152 bytes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct LogEntry {
 | 
				
			||||||
 | 
					    uint32_t seq;
 | 
				
			||||||
 | 
					    uint64_t ts;
 | 
				
			||||||
 | 
					    uint8_t event_type;
 | 
				
			||||||
 | 
					    uint8_t crc;
 | 
				
			||||||
 | 
					    char payload[PAYLOAD_SIZE];
 | 
				
			||||||
 | 
					    uint8_t reserved[2];
 | 
				
			||||||
 | 
					} __attribute__((packed));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define ENTRY_SIZE sizeof(LogEntry)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LogModule {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    LogModule();
 | 
				
			||||||
 | 
					    bool begin();
 | 
				
			||||||
 | 
					    bool writeLog(const LogEntry &entry);
 | 
				
			||||||
 | 
					    void dumpLogs();
 | 
				
			||||||
 | 
					    void clearLogs();
 | 
				
			||||||
 | 
					    void handleUART(char cmd, char dumpCmd = 'd', char clearCmd = 'c');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					    const esp_partition_t* partition;
 | 
				
			||||||
 | 
					    uint32_t write_pos;
 | 
				
			||||||
 | 
					    uint32_t total_entries;
 | 
				
			||||||
 | 
					    uint32_t partition_size;
 | 
				
			||||||
 | 
					    Preferences prefs;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    uint8_t calculateCRC(const LogEntry &entry);
 | 
				
			||||||
 | 
					    bool verifyEntry(const LogEntry &entry);
 | 
				
			||||||
 | 
					    void recoverLog();
 | 
				
			||||||
 | 
					    uint32_t findLastValidEntry();
 | 
				
			||||||
 | 
					    bool validateAllEntries();
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    bool readRaw(uint32_t offset, uint8_t* data, size_t size);
 | 
				
			||||||
 | 
					    bool writeRaw(uint32_t offset, const uint8_t* data, size_t size);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    void saveMetadata();
 | 
				
			||||||
 | 
					    bool loadMetadata();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										6009
									
								
								logs_stat_example.csv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6009
									
								
								logs_stat_example.csv
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										5
									
								
								partitions.csv
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								partitions.csv
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					# Name, Type, SubType, Offset, Size, Flags
 | 
				
			||||||
 | 
					nvs, data, nvs, 0x9000, 0x5000,
 | 
				
			||||||
 | 
					phy_init, data, phy, 0xe000, 0x1000,
 | 
				
			||||||
 | 
					factory, app, factory, 0x10000, 1M,
 | 
				
			||||||
 | 
					log_storage, data, 0x70, , 2M,
 | 
				
			||||||
		
		
			
  | 
		Loading…
	
		Reference in New Issue
	
	Block a user