сделан сервер на 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