forked from Akcelerometry_drgania_WMT/PI_mikrokontroler
Compare commits
5 Commits
działa_mam
...
v1.3.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 84e2abae14 | |||
| a2c46f3438 | |||
| 8436ddfaf6 | |||
| adec7d276e | |||
| 07c2614763 |
34
PREZENTACJA.md
Normal file
34
PREZENTACJA.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Prezentacja Projektu: Moduł Mikrokontrolera ESP32 (Akwizycja Danych z ADXL345) - Wersja v1.3.4.1
|
||||||
|
|
||||||
|
## 1. Technologia
|
||||||
|
W mojej części aplikacji (firmware mikrokontrolera) wykorzystane zostały następujące technologie i narzędzia:
|
||||||
|
* **Sprzęt:** Płytka rozwojowa Freenove ESP32-S3 WROOM, zewnętrzny czujnik przyspieszenia/drgań ADXL345 (komunikacja przez szynę SPI), karta SD do lokalnego przechowywania danych.
|
||||||
|
* **Środowisko i Język:** PlatformIO (C++ dla środowiska Arduino).
|
||||||
|
* **Zarządzanie czasem i zadaniami:** Wykorzystanie biblioteki `ArduinoThread` do "pseudo-wielowątkowości" i cyklicznego wywoływania zadań (np. testów WiFi, sprawdzania trybu offline, pomiarów i uploadu) w głównej pętli `loop()` mikrokontrolera.
|
||||||
|
* **Komunikacja Sieciowa i API:**
|
||||||
|
* Wi-Fi (konfiguracja sieci wczytywana bezpośrednio z pliku `wifi.txt` zapisanego na karcie SD).
|
||||||
|
* Klient HTTP (REST API) implementujący uwierzytelnianie (Basic Auth) i przesyłanie plików.
|
||||||
|
* **Formatowanie danych:** `ArduinoJson` do parsowania ustawień konfiguracyjnych oraz tworzenia ładunków dla API.
|
||||||
|
|
||||||
|
## 2. Prezentacja działania
|
||||||
|
Moduł stanowi serce układu akwizycji pomiarowej. Poniżej główne założenia jego działania:
|
||||||
|
1. **Tryb Konfiguracji:** Podczas uruchamiania, urządzenie wczytuje poświadczenia docelowej sieci Wi-Fi bezpośrednio z pliku konfiguracyjnego `wifi.txt` umieszczonego na karcie SD. Pozwala to na szybką zmianę sieci bez konieczności rekonfiguracji przez webowy interfejs.
|
||||||
|
2. **Zbieranie danych z czujnika:** W głównej pętli (poprzez `ArduinoThread`) ze zdefiniowaną częstotliwością odpytywany jest czujnik ADXL345 przez szynę SPI, a surowe dane na temat drgań są gromadzone.
|
||||||
|
3. **Zapis na nośnik nielotny:** Zebrane pakiety danych są zrzucane w zoptymalizowany sposób do binarnych plików z rozszerzeniem `.wmt` na kartę pamięci SD.
|
||||||
|
4. **Zarządzanie Uploadem:** Dodatkowy obiekt typu `Thread` wewnątrz głównej pętli regularnie monitoruje kartę SD. Jeśli znajdzie zamknięte pliki `.wmt`, tymczasowo blokuje pętlę na czas wysyłania (aby zapobiec utracie pakietów), i nawiązuje połączenie z serwerem FastAPI w celu zrzucenia zebranych logów. Jeśli wystąpi błąd (np. brak sieci), pliki pozostają bezpieczne na karcie SD i proces powtarza się w kolejnym cyklu.
|
||||||
|
|
||||||
|
## 3. Problemy
|
||||||
|
Podczas realizacji tej części systemu napotkałem i musiałem rozwiązać szereg problemów:
|
||||||
|
* **Kolidowanie czasu rzeczywistego z operacjami sieciowymi:** Wysyłanie plików na serwer oraz zapis na SD jest operacją czasochłonną. W tej wersji, opartej na `ArduinoThread`, długotrwały upload blokuje główną pętlę, co wymusza przerwanie zbierania kolejnych próbek z akcelerometru na czas przesyłu danych z karty.
|
||||||
|
* **Watchdog Timeouts (WDT):** Długie operacje sieciowe wykonywane w jednym i tym samym głównym wątku często powodowały wyzwolenie sprzętowego Watchdoga i reset mikrokontrolera. Rozwiązaniem było odpowiednie dozowanie czasu, przerywanie operacji oraz dodawanie instrukcji "karmiących" (feed) systemowego watchdoga wewnątrz pętli wysyłającej pliki.
|
||||||
|
* **Autoryzacja (401 Unauthorized) z serwerem i bezpieczeństwo API:** Skonfigurowanie płynnego logowania i autoryzacji sprzętu, tak aby backend poprawnie weryfikował zgłaszający się po WiFi mikrokontroler przed odbiorem plików pomiarowych.
|
||||||
|
* **Problemy z odczytem konfiguracji z karty SD:** Konieczność zapewnienia poprawnego i niezawodnego odczytu oraz parsowania pliku `wifi.txt` w początkowej fazie rozruchu mikrokontrolera (zanim wystartują główne wątki sieciowe).
|
||||||
|
* **Zarządzanie pamięcią konfiguracyjną (EEPROM) (starsze wydania):** W poprzednich wersjach stare ustawienia i domyślne hasła ("wmt") często zostawały w pamięci nieulotnej po przeflashowaniu mikrokontrolera. Sprawiało to ogromne trudności z logowaniem, co wymusiło napisanie mechanizmu automatycznej migracji bazy konfiguracji podczas uruchamiania.
|
||||||
|
* **Brak elastyczności (hardkodowane adresy IP) (starsze wydania):** Częstym błędem we wcześniejszym kodzie było zaszywanie na sztywno adresów produkcyjnych (np. `62.93...`), co uniemożliwiało testowanie i wymuszało wgrywanie nowego oprogramowania w przypadku zmiany serwera testowego.
|
||||||
|
* **Problemy z modułem Captive Portal (starsze wydania):** Początkowo testowaliśmy autorskie podnoszenie sieci i konfigurację przez smartfon, jednak wbudowany serwer DNS często zawieszał się w nowych środowiskach, przez co ostatecznie w wersji obecnej (v1.3.4.1) zrezygnowaliśmy z tego na rzecz pliku na karcie SD.
|
||||||
|
|
||||||
|
## 4. Do zrobienia
|
||||||
|
* **Implementacja pełnego szyfrowania (HTTPS/SSL):** Zabezpieczenie ruchu do REST API z wykorzystaniem zaufanych lub wbudowanych certyfikatów na ESP32 (obecnie wymaga to odpowiednich optymalizacji pamięci).
|
||||||
|
* **Przejście na architekturę wielowątkową (FreeRTOS):** Ponieważ `ArduinoThread` blokuje główną pętlę pomiarową na czas uploadu, najpilniejszym krokiem do zrobienia będzie wyciągnięcie UploadManagera do osobnego, niezależnego Taska przypiętego do Core 0 mikrokontrolera ESP32 (rdzenia odpowiedzialnego za obsługę Wi-Fi).
|
||||||
|
* **Testy stresowe (długodystansowe):** Uruchomienie układu w warunkach symulujących środowisko docelowe bez przerwy przez klika tygodni, by zbadać stabilność alokacji pamięci przy odczycie dużych wolumenów i upewnić się o niezawodności struktury katalogów na karcie SD.
|
||||||
|
* **Dokładna synchronizacja czasu (RTC / NTP):** Integracja dokładnego mechanizmu czasu z siecią tak, by tworzone na karcie SD pliki miały zawsze poprawny i bardzo precyzyjny stempel czasowy, niezależnie od tego czy mikrokontroler miał pełny restet z odłączeniem baterii.
|
||||||
@@ -512,7 +512,7 @@ const char *DataCapture::basenameFromPath(const char *full) { const char *slash
|
|||||||
bool DataCapture::isWmtWithDigits(const char* name, uint32_t& idxOut) const {
|
bool DataCapture::isWmtWithDigits(const char* name, uint32_t& idxOut) const {
|
||||||
size_t nlen = strlen(name), extLen = _ext.length();
|
size_t nlen = strlen(name), extLen = _ext.length();
|
||||||
if (nlen != (size_t)_digits + extLen) return false;
|
if (nlen != (size_t)_digits + extLen) return false;
|
||||||
if (strncmp(name + _digits, _ext.c_str(), extLen) != 0) return false;
|
if (strncmp(name + _digits, _ext.c_str(), extLen) != 0 && strncmp(name + _digits, ".upl", 4) != 0) return false;
|
||||||
for (uint8_t i = 0; i < _digits; ++i) if (name[i] < '0' || name[i] > '9') return false;
|
for (uint8_t i = 0; i < _digits; ++i) if (name[i] < '0' || name[i] > '9') return false;
|
||||||
char buf[16]; memcpy(buf, name, _digits); buf[_digits] = '\0';
|
char buf[16]; memcpy(buf, name, _digits); buf[_digits] = '\0';
|
||||||
idxOut = (uint32_t)strtoul(buf, nullptr, 10); return true;
|
idxOut = (uint32_t)strtoul(buf, nullptr, 10); return true;
|
||||||
@@ -536,6 +536,7 @@ uint32_t DataCapture::findHighestNumericDir() {
|
|||||||
if (!root || !root.isDirectory()) return 0;
|
if (!root || !root.isDirectory()) return 0;
|
||||||
uint32_t maxDir = 0;
|
uint32_t maxDir = 0;
|
||||||
for (File f = root.openNextFile(); f; f = root.openNextFile()) {
|
for (File f = root.openNextFile(); f; f = root.openNextFile()) {
|
||||||
|
Watchdog::feed();
|
||||||
if (f.isDirectory()) {
|
if (f.isDirectory()) {
|
||||||
const char *nm = basenameFromPath(f.name());
|
const char *nm = basenameFromPath(f.name());
|
||||||
if (isAllDigits(nm)) { uint32_t v = toUint(nm); if (v > maxDir) maxDir = v; }
|
if (isAllDigits(nm)) { uint32_t v = toUint(nm); if (v > maxDir) maxDir = v; }
|
||||||
@@ -551,7 +552,8 @@ void DataCapture::scanDirForWmt(uint32_t dirNum, uint32_t &count, uint32_t &high
|
|||||||
File dir = _fs.open(path);
|
File dir = _fs.open(path);
|
||||||
if (!dir || !dir.isDirectory()) return;
|
if (!dir || !dir.isDirectory()) return;
|
||||||
for (File f = dir.openNextFile(); f; f = dir.openNextFile()) {
|
for (File f = dir.openNextFile(); f; f = dir.openNextFile()) {
|
||||||
if (f.isDirectory()) { Watchdog::feed(); f.close(); continue; }
|
Watchdog::feed();
|
||||||
|
if (f.isDirectory()) { f.close(); continue; }
|
||||||
const char* base = basenameFromPath(f.name());
|
const char* base = basenameFromPath(f.name());
|
||||||
uint32_t idx = 0;
|
uint32_t idx = 0;
|
||||||
if (isWmtWithDigits(base, idx)) { count++; if (idx > highestIdx) highestIdx = idx; }
|
if (isWmtWithDigits(base, idx)) { count++; if (idx > highestIdx) highestIdx = idx; }
|
||||||
|
|||||||
@@ -52,11 +52,6 @@ bool UploadManager::isAlreadyUploaded(const String& filePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void UploadManager::uploadFile(const String& filePath) {
|
void UploadManager::uploadFile(const String& filePath) {
|
||||||
if (isAlreadyUploaded(filePath)) {
|
|
||||||
ESP_LOGI(TAG_UPLOAD, "File %s is already uploaded.", filePath.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WiFi.status() != WL_CONNECTED) {
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
ESP_LOGE(TAG_UPLOAD, "No WiFi. Cannot upload %s", filePath.c_str());
|
ESP_LOGE(TAG_UPLOAD, "No WiFi. Cannot upload %s", filePath.c_str());
|
||||||
appendLog(filePath, "ERROR: No WiFi");
|
appendLog(filePath, "ERROR: No WiFi");
|
||||||
@@ -66,6 +61,9 @@ void UploadManager::uploadFile(const String& filePath) {
|
|||||||
bool success = apiClient.uploadMeasurement(filePath);
|
bool success = apiClient.uploadMeasurement(filePath);
|
||||||
if (success) {
|
if (success) {
|
||||||
appendLog(filePath, "OK");
|
appendLog(filePath, "OK");
|
||||||
|
String newPath = filePath;
|
||||||
|
newPath.replace(".wmt", ".upl");
|
||||||
|
SD.rename(filePath, newPath);
|
||||||
} else {
|
} else {
|
||||||
if (WiFi.status() != WL_CONNECTED) {
|
if (WiFi.status() != WL_CONNECTED) {
|
||||||
appendLog(filePath, "ERROR: WiFi lost during upload");
|
appendLog(filePath, "ERROR: WiFi lost during upload");
|
||||||
@@ -99,11 +97,9 @@ void UploadManager::processPendingUploads() {
|
|||||||
|
|
||||||
if (childPath.endsWith(".wmt")) {
|
if (childPath.endsWith(".wmt")) {
|
||||||
if (childPath == capture_.getCurrentCapturePath()) continue;
|
if (childPath == capture_.getCurrentCapturePath()) continue;
|
||||||
if (!isAlreadyUploaded(childPath)) {
|
ESP_LOGI(TAG_UPLOAD, "Found pending file: %s", childPath.c_str());
|
||||||
ESP_LOGI(TAG_UPLOAD, "Found pending file: %s", childPath.c_str());
|
uploadFile(childPath);
|
||||||
uploadFile(childPath);
|
delay(1000);
|
||||||
delay(1000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
f.close();
|
f.close();
|
||||||
Watchdog::feed();
|
Watchdog::feed();
|
||||||
|
|||||||
Reference in New Issue
Block a user