From 0985792a06b07678944b20de85a2fbfdfdb1ba7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Wojtaszek?= <180088@stud.prz.edu.pl> Date: Mon, 23 Mar 2026 12:09:05 +0100 Subject: [PATCH 1/5] =?UTF-8?q?Dodanie=20folder=C3=B3w=20include=20i=20src?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/ADXL345ArraySPI.h | 79 +++++ include/ADXL345FastSPI.h | 91 ++++++ include/ADXL345FreshSPI.h | 87 ++++++ include/ADXL345Registers.h | 27 ++ include/Config.h | 71 +++++ include/Display.h | 86 ++++++ include/Logger.h | 15 + include/Measure.h | 127 ++++++++ include/Network.h | 92 ++++++ include/Pinout.h | 52 ++++ include/Settings.h | 52 ++++ include/Tool.h | 14 + include/Uploader.h | 24 ++ include/Version.h | 9 + include/Watchdog.h | 38 +++ src/ADXL345FastSPI.cpp | 140 +++++++++ src/ADXL345FreshSPI.cpp | 242 +++++++++++++++ src/Config.cpp | 184 ++++++++++++ src/Display.cpp | 216 ++++++++++++++ src/Logger.cpp | 16 + src/Measure.cpp | 594 +++++++++++++++++++++++++++++++++++++ src/Network.cpp | 292 ++++++++++++++++++ src/Settings.cpp | 168 +++++++++++ src/Tool.cpp | 48 +++ src/Uploader.cpp | 79 +++++ src/Watchdog.cpp | 81 +++++ src/main.cpp | 433 +++++++++++++++++++++++++++ 27 files changed, 3357 insertions(+) create mode 100644 include/ADXL345ArraySPI.h create mode 100644 include/ADXL345FastSPI.h create mode 100644 include/ADXL345FreshSPI.h create mode 100644 include/ADXL345Registers.h create mode 100644 include/Config.h create mode 100644 include/Display.h create mode 100644 include/Logger.h create mode 100644 include/Measure.h create mode 100644 include/Network.h create mode 100644 include/Pinout.h create mode 100644 include/Settings.h create mode 100644 include/Tool.h create mode 100644 include/Uploader.h create mode 100644 include/Version.h create mode 100644 include/Watchdog.h create mode 100644 src/ADXL345FastSPI.cpp create mode 100644 src/ADXL345FreshSPI.cpp create mode 100644 src/Config.cpp create mode 100644 src/Display.cpp create mode 100644 src/Logger.cpp create mode 100644 src/Measure.cpp create mode 100644 src/Network.cpp create mode 100644 src/Settings.cpp create mode 100644 src/Tool.cpp create mode 100644 src/Uploader.cpp create mode 100644 src/Watchdog.cpp create mode 100644 src/main.cpp diff --git a/include/ADXL345ArraySPI.h b/include/ADXL345ArraySPI.h new file mode 100644 index 0000000..184404e --- /dev/null +++ b/include/ADXL345ArraySPI.h @@ -0,0 +1,79 @@ +#pragma once +#include +#include +#include "ADXL345FreshSPI.h" + +class ADXL345ArraySPI { +public: + static constexpr uint8_t MAX_SENSORS = 4; + + explicit ADXL345ArraySPI(const uint8_t cs_pins[MAX_SENSORS]) { + for (uint8_t i=0;i=1 nową próbkę (DATA_READY). + bool availableAll() { + for (uint8_t i=0;i +#include +#include "ADXL345FreshSPI.h" +#include + +class ADXL345FastSPI { +public: + enum Rate { RATE_100HZ, RATE_200HZ, RATE_400HZ, RATE_800HZ, RATE_1600HZ, RATE_3200HZ }; + enum Range { RANGE_2G, RANGE_4G, RANGE_8G, RANGE_16G }; + + ADXL345FastSPI(const uint8_t* csPins, uint8_t count) : count_(count > MAX_NUM ? MAX_NUM : count){ + for (uint8_t i = 0; i < count_; ++i) cs_[i] = csPins[i]; + } + + /* Pobierz konfigurację zakres akcelerometru */ + uint8_t getRange(uint8_t accel); + + /* Zwraca czy jest FUL_RES*/ + bool getFullRes(uint8_t accel); + + // Wersja podstawowa: podaj SPIClass* (np. &SPI) + bool begin(SPIClass *spi, uint32_t spiHz, Rate rate, Range range, uint8_t options = 0); + + // Wersja wygodna: deleguje do powyższej, używając globalnego SPI + inline bool begin(uint32_t spiHz, Rate rate, Range range, uint8_t options = 0) { + return begin(&SPI, spiHz, rate, range, options); + } + + // Informacje / dostępność + inline uint8_t size() const { return presentCnt_; } + inline bool isPresent(uint8_t i) const { return (i < count_) && present_[i]; } + bool availableAll(); + + // Wariant B: zdejmuje po 1 NAJSTARSZEJ próbce z FIFO każdego obecnego sensora + // Zwraca liczbę zebranych próbek (== size(), jeśli pełna ramka). + uint8_t readAlignedOnce(int16_t* x, int16_t* y, int16_t* z, uint32_t* ts_us); + + // Zgodność: pojedynczy sensor – 1 próbka + bool readNewSample(uint8_t idx, int16_t& x, int16_t& y, int16_t& z, bool& ready); + + // Zwraca ilośc podłączonych czujników po inicjalizacji - ale nie jest dynamiczna! + inline uint8_t connectedSensorsCount() const { return presentCnt_; } + + // dynamiczne odświeżenie liczby działających czujników zawsze sprawdzi na bieżąco i zwróci przy działającym urządzeniu + uint8_t refreshConnectedSensorsCount(); + + // Maska aktywnych sensorów wg bieżącego stanu (bit i = sensor i) + inline uint8_t activeMask() const { + uint8_t m = 0; + for (uint8_t i = 0; i < count_; ++i) if (present_[i]) m |= (1u << i); + return m; + } + + // Odświeża stan (ping) i zwraca zaktualizowaną maskę aktywnych sensorów + uint8_t refreshActiveMask(); + +private: + static constexpr uint8_t MAX_NUM = 4; // MAX Ilość ADXL345 + + SPIClass* spi_ = &SPI; + ADXL345FreshSPI dev_[MAX_NUM]; + uint8_t cs_[MAX_NUM]{}; + bool present_[MAX_NUM]{}; + uint8_t count_ = 0; + uint8_t presentCnt_ = 0; + + uint32_t spiHz_ = 5000000; + float odrHz_ = 3200.0f; + + static float mapRateToHz(Rate r) { + switch (r) { + case RATE_100HZ: return 100.0f; + case RATE_200HZ: return 200.0f; + case RATE_400HZ: return 400.0f; + case RATE_800HZ: return 800.0f; + case RATE_1600HZ: return 1600.0f; + case RATE_3200HZ: return 3200.0f; + default: return 3200.0f; + } + } + static ADXL345FreshSPI::Range mapRange(Range r) { + switch (r) { + case RANGE_2G: return ADXL345FreshSPI::Range::G2; + case RANGE_4G: return ADXL345FreshSPI::Range::G4; + case RANGE_8G: return ADXL345FreshSPI::Range::G8; + case RANGE_16G: return ADXL345FreshSPI::Range::G16; + default: return ADXL345FreshSPI::Range::G16; + } + } +}; diff --git a/include/ADXL345FreshSPI.h b/include/ADXL345FreshSPI.h new file mode 100644 index 0000000..9c0b34e --- /dev/null +++ b/include/ADXL345FreshSPI.h @@ -0,0 +1,87 @@ +#pragma once +#include +#include +#include "ADXL345Registers.h" +#include + +class ADXL345FreshSPI { +public: + enum class Range { G2=0, G4=1, G8=2, G16=3 }; + enum class FIFOmode { BYPASS, FIFO, STREAM, TRIGGER }; + + struct SampleI16 { + int16_t x, y, z; + uint32_t ts_us; + }; + struct SampleSI { + float ax_g, ay_g, az_g; + float ax_ms2, ay_ms2, az_ms2; + uint32_t ts_us; + }; + + ADXL345FreshSPI() = default; + + // --- Init (SPI only) --- + // Uwaga: ADXL345 wymaga SPI MODE3, zegar ≤ ~5 MHz. + bool begin(SPIClass *spi, uint8_t csPin, uint32_t clockHz = 5000000); + + // --- Konfiguracja --- + bool setODR_Hz(float odr_hz); + bool setRange(Range r, bool fullRes=true); + bool enableFIFO(FIFOmode mode, uint8_t triggerLevel=16); + bool enableDataReadyInterrupt(bool enable=true); // przy pollingu pozostaw false + + // --- Status --- + bool ping(); // DEVID == 0xE5 ? + bool available(); // DATA_READY z INT_SOURCE + + // --- Odczyty „świeże” (blokujące do timeout_ms) --- + bool readFresh(SampleI16& out, uint32_t timeout_ms = 100); + bool readFresh(SampleSI& out, uint32_t timeout_ms = 100); + + // --- Zrzut FIFO (do n elementów) --- + size_t readFIFOBurst(SampleI16* buf, size_t maxCount); + size_t readFIFOBurst(SampleSI* buf, size_t maxCount); + + // --- Parametry pomocnicze --- + void setGConstant(float g = 9.80665f) { g_ms2 = g; } + + float lsb_per_g() const { return 1.0f / scale_g_per_lsb; } + //bool write8(uint8_t reg, uint8_t val); + //bool read8(uint8_t reg, uint8_t& val); + + /* + Zwraca tryb pracy akcelerometru + */ + uint8_t getADXLRange(); + + /* Zwraca czy jest FULL_RES w akcelerometrze */ + bool getADXLFullRes(); + + void showRangeFull(String txt=""); + + // niskopoziomowe + bool write8(uint8_t reg, uint8_t val); + bool read8(uint8_t reg, uint8_t& val); + + private: + SPIClass* spi = nullptr; + uint8_t cs = 255; + uint32_t spiHz = 5000000; + + float scale_g_per_lsb = 0.0039f; // full-res ~3.9 mg/LSB + float g_ms2 = 9.80665f; + bool fullRes = true; + + // niskopoziomowe + //bool write8(uint8_t reg, uint8_t val); + //bool read8(uint8_t reg, uint8_t& val); + bool readMulti(uint8_t reg, uint8_t* dst, size_t n); + + void spiSelect(); + void spiDeselect(); + + bool configurePowerMeasure(); + uint8_t odrCodeFromHz(float hz); + void countsToSI(const SampleI16& in, SampleSI& out); +}; diff --git a/include/ADXL345Registers.h b/include/ADXL345Registers.h new file mode 100644 index 0000000..9decced --- /dev/null +++ b/include/ADXL345Registers.h @@ -0,0 +1,27 @@ +#pragma once + +#define ADXL345_REG_DEVID 0x00 +#define ADXL345_REG_BW_RATE 0x2C +#define ADXL345_REG_POWER_CTL 0x2D +#define ADXL345_REG_INT_ENABLE 0x2E +#define ADXL345_REG_INT_MAP 0x2F +#define ADXL345_REG_INT_SOURCE 0x30 +#define ADXL345_REG_DATA_FORMAT 0x31 +#define ADXL345_REG_DATAX0 0x32 +#define ADXL345_REG_DATAX1 0x33 +#define ADXL345_REG_DATAY0 0x34 +#define ADXL345_REG_DATAY1 0x35 +#define ADXL345_REG_DATAZ0 0x36 +#define ADXL345_REG_DATAZ1 0x37 +#define ADXL345_REG_FIFO_CTL 0x38 +#define ADXL345_REG_FIFO_STATUS 0x39 + +#define ADXL345_POWER_MEASURE 0x08 +#define ADXL345_DATA_READY_BIT 0x80 // INT_SOURCE[7] +#define ADXL345_DATA_FORMAT_FULL_RES 0x08 +#define ADXL345_DATA_FORMAT_RANGE_MASK 0x03 + +#define ADXL345_FIFO_BYPASS 0x00 +#define ADXL345_FIFO_FIFO 0x40 +#define ADXL345_FIFO_STREAM 0x80 +#define ADXL345_FIFO_TRIGGER 0xC0 diff --git a/include/Config.h b/include/Config.h new file mode 100644 index 0000000..e23d9d4 --- /dev/null +++ b/include/Config.h @@ -0,0 +1,71 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include +#include +#include + +#define EEPROM_SIZE 1024 +//constexpr size_t EEPROM_SIZE = sizeof(Config) + 32; // z lekkim zapasem + +extern bool isRebootRequired; +extern bool isClearLog; +extern bool connected; +extern long countConnect; +extern long countDisconnect; +extern String act_rssi_percent; +extern int8_t rssi; +extern String actDate; +extern String actTime; + +struct Config { + bool connect; // czy łączyć z Internetem? + bool measure; // true - pomiary ciągłe, false - nie rób nic (tryb konfiguracji) + char ip[16]; + char subnet[16]; + char gateway[16]; + char dns[16]; + char ssid[32]; + char ntp[50]; + char password[32]; + char hostname[32]; + char place[100]; // miejsce instalacji + bool dhcp; // czy włączyć DHCP? + char user[10]; // użytkownik konfiguracji + char pass[20]; // hasło użytkownika konfiguracji + char updateUrl[150]; // adres pliku aktualizacji + char restURL[150]; // adres Rest API + int restPort; // Port systemu Api na serwerze + char restUser[30]; // login RestAPI + char restPass[50]; // hasło RestAPi + uint8_t apiKey[32]; // Klucz API KEY + uint16_t pause; // Pomiar co sekund + uint8_t duration; // Czas pomiaru w sekundach 1-25 + char S0[12]; // nazwy czujników 1-8 + char S1[12]; + char S2[12]; + char S3[12]; + char S4[12]; + char S5[12]; + char S6[12]; + char S7[12]; +}; + +// Global config declaration +extern Config config; + +class ConfigManager { + public: + ConfigManager(); + void begin(); // EEPROM initialization + void readConfig(); // Odczyt konfiguracji z EEPROM + void saveConfig(); // Zapis konfiguracji do EEPROM + void resetToDefaults(); // Reset do ustawień domyślnych + void showConfig(); + void generateApiKey(uint8_t *buf, size_t len); + + private: + bool isEEPROMEmpty(); +}; + +#endif \ No newline at end of file diff --git a/include/Display.h b/include/Display.h new file mode 100644 index 0000000..8289bb6 --- /dev/null +++ b/include/Display.h @@ -0,0 +1,86 @@ +#ifndef DISPLAY_H +#define DISPLAY_H + +#pragma once +#include "esp_log.h" +#include +#include +#include +#include +#include +#include + +#define SCREEN_WIDTH 40 +#define SCREEN_HEIGHT 4 +#define SCREEN_ADDRESS 0x27 + +class Display { +public: + Display(RTC_DS3231 &rtc, + uint8_t address = SCREEN_ADDRESS, + uint8_t columns = SCREEN_WIDTH, + uint8_t rows = SCREEN_HEIGHT); + + // Zwraca true, jeśli urządzenie na I2C odpowiada + bool begin(TwoWire *wire = &Wire); + + /* Ekran startowy urządzenia */ + void welcomeScreen(); + + /* Czyście wiersz */ + void clearRow(uint16_t line); + + /* Wyświetla wyśrodkowany tekst w danym wierszu */ + void textCenter(uint8_t line, const char *txt); + + /* Wyświetla tekst w danym wierszu od lewej, bez centrowania */ + void text(uint8_t line, const char *text); + + /* Status - najniższy wiersz. Czyści przed zapisaniem */ + void textStatus(const char *text); + + void clear(); + + /* Ekran po uruchomieniu */ + void mainScreen(); + + /* Ekran jeśli offline miga co 1 sek */ + void displayOffline(bool measure, uint8_t count, float freeSpace, long licznik, bool refresh = false); + + /* Podsumowanie po pomiarze: ramki, sampling, etc. */ + void displaySampleRateSummary(uint32_t reccount, uint32_t captureSeconds, float khz, String filename); + + void updateNetwork(String ip, bool connected); + void updateBarWiFi(long signal, bool connected); + void textStyle(const uint8_t st, String text); + void message(String text); + + void print(String text); + void println(String text); + void setCursor(int16_t x, int16_t y); + void showAccel(float a, float b, float c); + void displayOnOffM(bool measure, String myDir, String myFile); + + // Metoda sterująca ikoną SSL (dodana do klasy) + void setSSLStatus(bool active); + + void initMeasure( + bool measure, // czy pomiar ciągły? + bool run, // czy uruchomiony? + bool runmes, + uint16_t pause, // Czas (s) pomiędzy próbkami + uint8_t duration, // Czas (s) trwania próbki + bool connect, // Czy połączenie Network + long counter, // główny licznik long + String gdate, // Data aktualna + String gtime); // Czas aktualny + +private: + LiquidCrystal_I2C *_lcd; + RTC_DS3231 &rtc_; + uint8_t _address; + uint8_t _columns; + uint8_t _rows; +}; + +#endif \ No newline at end of file diff --git a/include/Logger.h b/include/Logger.h new file mode 100644 index 0000000..3b57368 --- /dev/null +++ b/include/Logger.h @@ -0,0 +1,15 @@ +#ifndef LOGGER_H +#define LOGGER_H + +#include "esp_log.h" + +// Wszystkie tagi logowania w jednym miejscu +extern const char *TAG_MAIN; +extern const char *TAG_DISP; +extern const char *TAG_ADXL; +extern const char *TAG_CONF; + +// Funkcja inicjalizacji poziomów logowania +void init_log_levels(); + +#endif diff --git a/include/Measure.h b/include/Measure.h new file mode 100644 index 0000000..6dbdce5 --- /dev/null +++ b/include/Measure.h @@ -0,0 +1,127 @@ +#ifndef MEASURE_H +#define MEASURE_H + +#include +#include +#include +#include +#include +#include +#include +#include "ADXL345FastSPI.h" // <-- zgodnie z main.cpp +#include +#include + +// Domyślne parametry akwizycji ADXL345 (mogą być nadpisane w ADXL345FastSPI::begin) +static constexpr uint32_t SPI_HZ = 5000000; // 5 MHz (MODE3) +static constexpr float ODR_HZ = 3200.0f; // maks. ODR +// Zakres ustawiany w main.cpp przez ADXL345FastSPI::begin(..., RANGE_2G, ...) + +//extern Display display; + +struct FileInfo { + String path; // np. "/3/00000057.wmt" + uint64_t size; // bajty + bool exists; // true, jeśli ostatni plik istnieje +}; + +struct SpaceInfo { + double value; + const char *unit; // "GB" | "MB" | "UNKNOWN" +}; + +class DataCapture { + // --- Nagłówek pliku WMT (jak w oryginale) --- + struct FileHeader { + char magic[3]; // "WMT" + uint16_t version; // 1 + uint16_t headerSize; // sizeof(FileHeader) + uint32_t sampleSize; // sizeof(Sample) + uint32_t timestamp; // UNIX startu akwizycji + uint32_t reccount; // liczba rekordów Sample w pliku + } __attribute__((packed)); + +public: + // --- Rekord próbki (jak w oryginale) --- + struct Sample { + uint32_t offset; // µs od startu akwizycji (wspólny dla ramki) + uint8_t sensor_id; // 0..3 + int16_t x, y, z; // surowe ADXL345 + bool ready; // 1 = obecna + } __attribute__((packed)); + + // Konstruktor dopasowany do main.cpp – przyjmuje ADXL345FastSPI + DataCapture(ADXL345FastSPI &adxl, Display &display, RTC_DS3231 &rtc, fs::FS &storage, size_t bufferSize = 131072 /* 128 KB */); + ~DataCapture(); + + bool capture(uint32_t captureSeconds, const char *filename); + bool captureAuto(uint32_t captureSeconds, const char *baseDirectory = "/logs"); + + void printSamplingRate(uint32_t reccount, uint32_t captureSeconds, String filename); + void readHeaderAndPrint(const char *path); + void stop(); + bool isActive() const { return measurementActive_; } + + // SD utils + SpaceInfo freeSpaceMB(); + float freeSpaceFloat(bool *isGB = nullptr); + String unixToDateTime(uint32_t ts); + + // plik z najwyższym indeksem + FileInfo getLastFileInfo(); + bool deleteAllOnSD(); + + bool isExit = false; // true oznacza przerwanie pomiaru + +private: + ADXL345FastSPI &adxl_; + Display &display_; + RTC_DS3231 &rtc_; + fs::FS &_fs; + + // Katalogowanie + const String _baseDir = "/"; + const String _ext = ".wmt"; + const uint8_t _digits = 8; + const uint16_t _maxFilesPerDir = 400; + + // Bufor zapisu (PSRAM) + uint8_t *buffer_ = nullptr; + size_t bufferSize_ = 0; + size_t bufferIndex_ = 0; + bool measurementActive_ = false; + + bool flushToFile(File &f); + static uint8_t crc8(const uint8_t *data, size_t len); + void setTestingIndicator_(bool on, String myDir, String myFile); + + bool isEscape(){ + bool ispress = (GPIO.in & (1UL << BTN_OK)) == 0; + if(ispress) { + isExit = true; + measurementActive_; + display_.textStatus("Cancelling. Wait!"); + } + return ispress; + } // szybkidigitalRead : czy BTN stop??? + + + // Helpers (zachowane z Twojej wersji) +public: + bool isAllDigits(const char *s); + uint32_t toUint(const char *s); + static const char *basenameFromPath(const char* full); + bool isWmtWithDigits(const char *name, uint32_t &idxOut) const; + String makeIndexedName(uint32_t idx) const; + static String joinPath(const String &a, const String &b); + bool ensureDir(uint32_t dirNum); + uint32_t findHighestNumericDir(); + void scanDirForWmt(uint32_t dirNum, uint32_t &count, uint32_t &highestIdx); + bool recursiveDelete(const String &path); + String dirPath(uint32_t dirNum) const; + String allocateNextFilePath(); + String generateNextFilename(); + void printLastFileInfoSerial(); +}; + +#endif // MEASURE_H diff --git a/include/Network.h b/include/Network.h new file mode 100644 index 0000000..74068e2 --- /dev/null +++ b/include/Network.h @@ -0,0 +1,92 @@ +#ifndef NETWORK_H +#define NETWORK_H + +#include "esp_log.h" +#include +#include + +#ifdef ESP32 + #include + #include + #include +#elif defined(ESP8266) + #include + #include + #include +#endif + +#include // Dodano dla Captive Portal +//#include +//#include +//#include + +//#include + +// OTA +#include +#include +#include +// OTA + +#include "Display.h" // Dodano dla obsługi komunikatów na LCD + +extern Config config; // Deklaracja zewnętrznej struktury config +extern ConfigManager configManager; // Deklaracja zewnętrznego managera (to naprawi błąd) + +class WiFiManager { + public: + WiFiManager(); + void begin(); + void connectToWiFi(); + void setupAccessPoint(const char *newSSID, const char *newPassword); + void checkWiFiConnection(); + void updateLED(); + void setupMDNS(); + void ReadConnection(); + bool isConnected(); // Zwraca stan połączenia + bool convertCharToIPAddress(const char *str, IPAddress& ip); + void ledBlink(); // Z innego projektu - niepotrzebne, bo nie ma LED WiFi... + bool isWiFiOK(); // True, gdy WiFi połączone i false, gdy brak połączenia + int rssiToPercent(int rssi); // rssi na procenty + int8_t getRSSI(); + + /** + * NOWA METODA: Portal konfiguracyjny (Captive Portal) + * Uruchamiany automatycznie przy braku połączenia. + */ + void startConfigPortal(Display &display); + + /** + * Aktualizacja systemu przez internet z adresu config.updateUrl. + * @param allowInsecureTLS true => dla https wyłącz weryfikację certyfikatu. + * @param progressCb callback progress (opcjonalnie) (bytes, total). + * @return true, jeśli update zakończony sukcesem (urządzenie się zrestartuje). + */ + bool performOTAUpdate(bool allowInsecureTLS = true, std::function progressCb = nullptr); + + int8_t rssi = 0; + bool useDHCP = true; + IPAddress local_IP; + IPAddress gateway; + IPAddress subnet; + IPAddress dns; + String ssidAP = "ACCEL666"; + String passwordAP = "12345678"; + + private: + bool isAccessPoint = false; + + // Obiekty serwerów dla Portalu (ESP32/ESP8266) +#ifdef ESP32 + WebServer server{80}; +#elif defined(ESP8266) + ESP8266WebServer server{80}; +#endif + DNSServer dnsServer; + void handleRoot(); + void handleSave(); +}; + +extern WiFiManager wifi; + +#endif // WIFIMANAGER_H \ No newline at end of file diff --git a/include/Pinout.h b/include/Pinout.h new file mode 100644 index 0000000..d3fefce --- /dev/null +++ b/include/Pinout.h @@ -0,0 +1,52 @@ +#ifndef PINOUT_H +#define PINOUT_H + +#if defined(ESP32) +// SPI3 (HSPI) - SD Card nie kolidują z PSRAM +#define SD_SCK 16 // 36 //18 +#define SD_MOSI 17 // 35 //17 +#define SD_MISO 18 // 37 //16 +#define SD_CS 15 // 34 //15 ?? 34 + +// SPI2 (VSPI) - ADXL345 +#define MOSI_ADSX 11 // SDA +#define CLK_ADSX 12 // SCL +#define MISO_ADSX 13 // SDO + +//I2C C3 +#define PIN_SDA 47 // szary +#define PIN_SCL 48 // niebieski + +// Przycisk +#define BTN_UP 5 +#define BTN_OK 6 +#define BTN_DOWN 7 + +#endif + +/* +CS {5, 6, 7, 10, 14, 21} +SPI2 (VSPI) — preferowane do ADXL345 (wysoka prędkość, stabilność). ASXL345 +SPI3 (HSPI) — dowolne piny do SD (niższa prędkość, ale elastyczność). SD + +Nie używać ESP32-S3: +integrated SPI flash: 26, 27, 28, 29, 30, 31, 32, +USB: 19, 20, 43, 44 +PSRAM 35, 37 +*/ + +#if defined(ARDUINO_RASPBERRY_PI_PICO) +#define CLK_ADSX 18 +#define MOSI_ADSX 19 +#define MISO_ADSX 16 + +#define SD_MISO 12 +#define SD_CS 13 +#define SD_SCK 14 +#define SD_MOSI 15 +#define I2C_SDA 20 +#define I2C_SCL 21 +#endif + +#endif + diff --git a/include/Settings.h b/include/Settings.h new file mode 100644 index 0000000..d8711b8 --- /dev/null +++ b/include/Settings.h @@ -0,0 +1,52 @@ +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +#include +#include +#include +#include +#include +#include + +class Settings { + public: + Settings(Display &display, RTC_DS3231 &rtc); + ~Settings(); + + void setTimeRTC(); + void setConfigDevice(); + void finishConfigDevice(); + void begin(); + + bool isPressed(uint8_t btnIndex); + + // bool is1and3(); + + bool isBtnReset(); + + bool readBtnUp() { return digitalRead(BTN_UP) == LOW; } + bool readBtnOk() { return digitalRead(BTN_OK) == LOW; } + bool readBtnDown() { return digitalRead(BTN_DOWN) == LOW; } + + // Struktura na datę i czas + struct RtcDateTime { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + }; + +private: + Display &display_; + RTC_DS3231 &rtc_; + + int editField(int value, int minVal, int maxVal, const char *label); + void constrainValue(int &value, int minVal, int maxVal); + void printField(const char *label, int value); + +}; + +#endif diff --git a/include/Tool.h b/include/Tool.h new file mode 100644 index 0000000..97d39ee --- /dev/null +++ b/include/Tool.h @@ -0,0 +1,14 @@ +#ifndef TOOL_H +#define TOOL_H + +#include +#include +#include "esp_log.h" +#include "Watchdog.h" + +void scanI2C(); + +/* Funkcja przyjmuje adres jako argument i zwraca true gdy urządzenie odpowiada, w przeciwnym wypadku false */ +bool isI2CDevPresent(uint8_t address); + +#endif diff --git a/include/Uploader.h b/include/Uploader.h new file mode 100644 index 0000000..9ca0487 --- /dev/null +++ b/include/Uploader.h @@ -0,0 +1,24 @@ +#ifndef UPLOADER_H +#define UPLOADER_H + +#include +#include +#include +#include +#include +#include "Config.h" +#include "Display.h" +#include "Watchdog.h" + +class Uploader { +public: + Uploader(Display &display); + void processQueue(int maxFiles = 3); + +private: + Display &_display; + String loadCACert(const char* path); + bool sendFile(String filePath, String& caCert); +}; + +#endif \ No newline at end of file diff --git a/include/Version.h b/include/Version.h new file mode 100644 index 0000000..d4d6536 --- /dev/null +++ b/include/Version.h @@ -0,0 +1,9 @@ +#ifndef VERSION_H +#define VERSION_H + +#define VERSION "1.3.2" + +// 1: graphical 128x64, 2: LCD I2C Text 4x20 +#define LCD_TYPE 2 + +#endif \ No newline at end of file diff --git a/include/Watchdog.h b/include/Watchdog.h new file mode 100644 index 0000000..992bdb4 --- /dev/null +++ b/include/Watchdog.h @@ -0,0 +1,38 @@ +#pragma once +/** + * Watchdog — prosty interfejs do inicjalizacji i karmienia WDT z dowolnego modułu. + * + * Obsługiwane środowiska: + * - ESP32 Arduino Core / ESP-IDF (esp_task_wdt) + * - Fallback: no-op na innych platformach + * + * Użycie: + * Watchdog::init(5, true); + * Watchdog::addThisTask(); + * ... + * Watchdog::feed(); + */ + +#include + +namespace Watchdog { + +/** Inicjalizacja Task Watchdog (idempotentna). */ +bool init(int timeout_seconds = 5, bool panic_on_trigger = true); + +/** Dodaje bieżący task (wątki FreeRTOS: wołaj w ciele tego taska). */ +bool addThisTask(); + +/** Usuwa bieżący task z nadzoru WDT. */ +bool removeThisTask(); + +/** Karmi watchdog (reset licznika). */ +void feed(); + +/** Zmienia timeout (wykonuje re-init wewnętrznie, jeśli trzeba). */ +bool setTimeout(int timeout_seconds); + +/** Czy watchdog jest aktywny (zainicjalizowany)? */ +bool isActive(); + +} // namespace Watchdog diff --git a/src/ADXL345FastSPI.cpp b/src/ADXL345FastSPI.cpp new file mode 100644 index 0000000..07efeb1 --- /dev/null +++ b/src/ADXL345FastSPI.cpp @@ -0,0 +1,140 @@ +#include "ADXL345FastSPI.h" + +static const char *TAG_FRESH = "ADXLFAST"; + +bool ADXL345FastSPI::begin(SPIClass *spi, uint32_t spiHz, Rate rate, Range range, uint8_t /*options*/){ + spi_ = spi; + spiHz_ = spiHz; + odrHz_ = mapRateToHz(rate); + auto r = mapRange(range); + + //spi_->begin(); 28.01.2026 + presentCnt_ = 0; + for (uint8_t i = 0; i < count_; ++i) { + bool ok = dev_[i].begin(spi_, cs_[i], spiHz_) + && dev_[i].setRange(r, true) + && dev_[i].setODR_Hz(odrHz_); + + dev_[i].showRangeFull(String(i)); + + + if (ok) { + dev_[i].enableFIFO(ADXL345FreshSPI::FIFOmode::STREAM, 32); // wariant B + present_[i] = true; + presentCnt_++; + } else { + present_[i] = false; + } + } + return presentCnt_ > 0; +} + +bool ADXL345FastSPI::availableAll() { + for (uint8_t i = 0; i < count_; ++i) { + if (!present_[i]) continue; + // else { + // dev_[i].showRangeFull("AVAILABLE ADXL"); + // } + + if (!dev_[i].available()) return false; + } + return true; +} + +uint8_t ADXL345FastSPI::readAlignedOnce(int16_t* x, int16_t* y, int16_t* z, uint32_t* ts_us){ + // 1) Czekaj aż każdy ma ≥1 próbkę (DATA_READY => FIFO>0) + while (!availableAll()) { + // micro-spin; w razie potrzeby można dodać yield() / feed watchdog poza hot-path + } + + // 2) Zdejmij najstarszą z każdego sensora + uint8_t got = 0; + for (uint8_t i = 0; i < count_; ++i) { + if (!present_[i]) continue; + + ADXL345FreshSPI::SampleI16 one[1]; + size_t n = dev_[i].readFIFOBurst(one, 1); + if (n == 0) { + ADXL345FreshSPI::SampleI16 s; + if (!dev_[i].readFresh(s, 1)) break; + one[0] = s; + } + if (x) x[i] = one[0].x; + if (y) y[i] = one[0].y; + if (z) z[i] = one[0].z; + if (ts_us) ts_us[i] = one[0].ts_us; + got++; + } + return got; +} + +bool ADXL345FastSPI::readNewSample(uint8_t idx, int16_t& x, int16_t& y, int16_t& z, bool& ready) +{ + if (idx >= count_ || !present_[idx]) { ready = false; return false; } + + ADXL345FreshSPI::SampleI16 one[1]; + size_t n = dev_[idx].readFIFOBurst(one, 1); + if (n == 0) { + ADXL345FreshSPI::SampleI16 s; + if (!dev_[idx].readFresh(s, 1)) { ready = false; return false; } + one[0] = s; + } + x = one[0].x; y = one[0].y; z = one[0].z; + ready = true; + return true; +} + +uint8_t ADXL345FastSPI::refreshConnectedSensorsCount(){ + uint8_t cnt = 0; + for (uint8_t i = 0; i < count_; ++i) { + // ping() czyta DEVID (0xE5) wewnętrznie i zwraca true/false + if (dev_[i].ping()) { + present_[i] = true; + cnt++; + } else { + present_[i] = false; + } + } + presentCnt_ = cnt; + return cnt; +} + +/* +Zwraca maskę bitową aktywnych sensorów (np. do szybkiej diagnostyki: który CS nie odpowiada) +Uzycie: +uint8_t mask = adxl.refreshActiveMask(); +ESP_LOGI(TAG_MAIN, "Aktywne sensory (bitmask): 0x%02X, count=%u", + mask, adxl.connectedSensorsCount()); + +// Sprawdzenie konkretnego sensora: +if (mask & (1u << 3)) { + ESP_LOGI(TAG_MAIN, "Sensor #3 online"); +} else { + ESP_LOGW(TAG_MAIN, "Sensor #3 offline"); +} +*/ +uint8_t ADXL345FastSPI::refreshActiveMask(){ + uint8_t m = 0; + uint8_t cnt = 0; + + for (uint8_t i = 0; i < count_; ++i) { + if (dev_[i].ping()) { + present_[i] = true; + m |= (1u << i); + cnt++; + } else { + present_[i] = false; + } + } + presentCnt_ = cnt; + return m; +} + +// Pobiera zakres wybranego akcelerometru +uint8_t ADXL345FastSPI::getRange(uint8_t accel){ + return dev_[accel].getADXLRange(); +} + +bool ADXL345FastSPI::getFullRes(uint8_t accel){ + return dev_[accel].getADXLFullRes(); +} \ No newline at end of file diff --git a/src/ADXL345FreshSPI.cpp b/src/ADXL345FreshSPI.cpp new file mode 100644 index 0000000..347eaec --- /dev/null +++ b/src/ADXL345FreshSPI.cpp @@ -0,0 +1,242 @@ +#include "ADXL345FreshSPI.h" + +static const char *TAG_FRESH = "ADXLFRESH"; + +static inline int16_t u8pair_to_i16(uint8_t lo, uint8_t hi) { + return (int16_t)((hi << 8) | lo); +} + +bool ADXL345FreshSPI::begin(SPIClass* s, uint8_t csPin, uint32_t clockHz) { + spi = s; cs = csPin; spiHz = clockHz; + pinMode(cs, OUTPUT); digitalWrite(cs, HIGH); + //spi->begin(); // tymczas + delay(1); + + if (!ping()) return false; + + // 1. Wymuś STANDBY przed jakąkolwiek konfiguracją + write8(ADXL345_REG_POWER_CTL, 0x00); + delay(1); + + // 2. Skonfiguruj format danych (Range i Full_Res) + if (!setRange(Range::G16, true)) { // było if (!setRange(Range::G2, true)) return false; + ESP_LOGI(TAG_FRESH, "Range G16 ERROR!"); + return false; + } + // 3. Skonfiguruj ODR + if (!setODR_Hz(100.0f)) return false; // domyślnie 100 Hz + // 4. Dopiero teraz włącz pomiar + if (!configurePowerMeasure()) return false; + showRangeFull(); + return true; +} + + +bool ADXL345FreshSPI::ping() { + uint8_t id=0; if (!read8(ADXL345_REG_DEVID, id)) return false; + return id == 0xE5; +} + +bool ADXL345FreshSPI::configurePowerMeasure() { + return write8(ADXL345_REG_POWER_CTL, ADXL345_POWER_MEASURE); +} + + +bool ADXL345FreshSPI::setRange(Range r, bool fullRes_) { + fullRes = fullRes_; + // Wymuś STANDBY (MEASURE=0) – kluczowe dla zmiany RANGE/FULL_RES + write8(ADXL345_REG_POWER_CTL, 0x00); + delayMicroseconds(5); + + uint8_t fmt = 0; + if (!read8(ADXL345_REG_DATA_FORMAT, fmt)) return false; + + fmt &= ~ADXL345_DATA_FORMAT_RANGE_MASK; // bity 1:0 + fmt |= (uint8_t)r; + + if (fullRes) fmt |= ADXL345_DATA_FORMAT_FULL_RES; // bit 3 + else fmt &= ~ADXL345_DATA_FORMAT_FULL_RES; + + if (!write8(ADXL345_REG_DATA_FORMAT, fmt)) return false; + + // Kontrola: odczyt po zapisie + //uint8_t verify = 0; + //read8(ADXL345_REG_DATA_FORMAT, verify); + // tu możesz logować verify + + // Wróć do MEASURE + write8(ADXL345_REG_POWER_CTL, ADXL345_POWER_MEASURE); + scale_g_per_lsb = fullRes ? 0.0039f : (1.0f/256.0f) * (2 << (uint8_t)r); + return true; +} + + +uint8_t ADXL345FreshSPI::odrCodeFromHz(float hz) { + struct { uint8_t code; float f; } map[] = { + {0x06, 6.25f},{0x07,12.5f},{0x08,25.f},{0x09,50.f},{0x0A,100.f}, + {0x0B,200.f},{0x0C,400.f},{0x0D,800.f},{0x0E,1600.f},{0x0F,3200.f} + }; + uint8_t best=0x0A; float bestErr=1e9f; + for (auto &e: map){ float err=fabsf(e.f-hz); if (err timeout_ms) return false; + delayMicroseconds(200); + } + uint8_t buf[6]; + if (!readMulti(ADXL345_REG_DATAX0, buf, 6)) return false; + out.x = u8pair_to_i16(buf[0], buf[1]); + out.y = u8pair_to_i16(buf[2], buf[3]); + out.z = u8pair_to_i16(buf[4], buf[5]); + out.ts_us = micros(); + return true; +} + +bool ADXL345FreshSPI::readFresh(SampleSI& out, uint32_t timeout_ms) { + SampleI16 raw; + if (!readFresh(raw, timeout_ms)) return false; + countsToSI(raw, out); + return true; +} + +size_t ADXL345FreshSPI::readFIFOBurst(SampleI16* buf, size_t maxCount) { + if (!buf || maxCount==0) return 0; + uint8_t status=0; if (!read8(ADXL345_REG_FIFO_STATUS, status)) return 0; + uint8_t entries = status & 0x3F; // 0..32 + size_t n = min(entries, maxCount); + for (size_t i=0;ibeginTransaction(SPISettings(spiHz, MSBFIRST, SPI_MODE3)); + spiSelect(); + spi->transfer(reg & 0x3F); // write, single + spi->transfer(val); + spiDeselect(); + spi->endTransaction(); + return true; +} + +bool ADXL345FreshSPI::read8(uint8_t reg, uint8_t& val) { + spi->beginTransaction(SPISettings(spiHz, MSBFIRST, SPI_MODE3)); + spiSelect(); + spi->transfer(0x80 | (reg & 0x3F)); // read, single + val = spi->transfer(0x00); + spiDeselect(); + spi->endTransaction(); + return true; +} + +bool ADXL345FreshSPI::readMulti(uint8_t reg, uint8_t *dst, size_t n) { + if (n==0) return true; + spi->beginTransaction(SPISettings(spiHz, MSBFIRST, SPI_MODE3)); + spiSelect(); + spi->transfer(0xC0 | (reg & 0x3F)); // read, multi (MB=1, R/W=1) + for (size_t i=0;itransfer(0x00); + spiDeselect(); + spi->endTransaction(); + return true; +} + +void ADXL345FreshSPI::spiSelect() { digitalWrite(cs, LOW); } +void ADXL345FreshSPI::spiDeselect() { digitalWrite(cs, HIGH); } + + +uint8_t ADXL345FreshSPI::getADXLRange() { + uint8_t format = 0; + if (!read8(0x31, format)) return 255; // albo 0 + switch (format & 0x03) { + case 0: return 2; + case 1: return 4; + case 2: return 8; + case 3: return 16; + } + return 255; +} + + +bool ADXL345FreshSPI::getADXLFullRes() { + uint8_t format = 0; + if (!read8(0x31, format)) return false; + //bool ok = read8(0x31, format); + //ESP_LOGI(TAG_FRESH, "read8 ok=%d DATA_FORMAT=0x%02X", ok, format); + return (format & 0x08) != 0; +} + +void ADXL345FreshSPI::showRangeFull(String txt) { + uint8_t format = getADXLRange(); + bool full_res = getADXLFullRes(); + ESP_LOGI(TAG_FRESH, "%s ADXL345: RANGE=%dG FULL_RES=%d", txt, format, full_res); +} diff --git a/src/Config.cpp b/src/Config.cpp new file mode 100644 index 0000000..7d56d82 --- /dev/null +++ b/src/Config.cpp @@ -0,0 +1,184 @@ +#include + +Config config; + +ConfigManager::ConfigManager() {} + +// EEPROM initialize +void ConfigManager::begin() { + ESP_LOGI(TAG_CONF, "Begin config"); + EEPROM.begin(EEPROM_SIZE); + if (isEEPROMEmpty()) { + ESP_LOGI(TAG_CONF, "EEPROM Empty"); + resetToDefaults(); + generateApiKey(config.apiKey, sizeof(config.apiKey)); + saveConfig(); + readConfig(); + } else { + //Logger::getInstance().log(LOG_INFO, "READ Config"); + readConfig(); + } +} + +// Check is EEPROM is empty +bool ConfigManager::isEEPROMEmpty() { + if (EEPROM.read(0) != 251){ + ESP_LOGI(TAG_CONF, "EEPROM is new!"); + return true; + } else { + return false; + } +} + +// Read configuration from EEPROM +void ConfigManager::readConfig() { + ESP_LOGI(TAG_CONF, "Read config from EEPROM"); + EEPROM.get(1, config); +} + +// Save config to EEPROM +void ConfigManager::saveConfig() { + ESP_LOGI(TAG_CONF, "SAVE CONFIG"); + EEPROM.put(1, config); + EEPROM.write(0, 251); + if (EEPROM.commit()) { + ESP_LOGI(TAG_CONF, "Config saved"); + } else { + ESP_LOGE(TAG_CONF, "Error save config"); + } + EEPROM.end(); +} + +void ConfigManager::generateApiKey(uint8_t *buf, size_t len) { + for (size_t i = 0; i < len; i += 4) { + uint32_t r = esp_random(); // losowe 32 bity z TRNG ESP32 + size_t chunk = (len - i >= 4) ? 4 : (len - i); // ostatnia iteracja może być < 4 bajtów + memcpy(buf + i, &r, chunk); + } +} + +// Factory reset EEPROM +void ConfigManager::resetToDefaults() { + ESP_LOGI(TAG_CONF, "EEPROM RESET FACTORY"); + EEPROM.begin(EEPROM_SIZE); + for (int i = 0; i < EEPROM_SIZE; i++) { + EEPROM.write(i, 0); + } + EEPROM.write(0, 0); + strcpy(config.ssid, "politechnika"); + strcpy(config.password, ""); + strcpy(config.hostname, "WMT001"); + strcpy(config.place, "WMT Stalowa Wola"); + config.dhcp = 1; + strcpy(config.ip, "192.168.0.10"); + strcpy(config.subnet, "255.255.255.0"); + strcpy(config.gateway, "192.168.0.1"); + strcpy(config.dns, "8.8.8.8"); + strcpy(config.user, "admin"); + strcpy(config.pass, "admin"); + strcpy(config.ntp, "pl.pool.ntp.org"); + config.connect = 0; // urządzenie połączone z siecią lub 0 offline + config.measure = 1; // włącz automatyczny pomiar co x sekunt (pause) + config.duration = 5; // czas trwania pomiaru 5 sekund + config.pause = 10000; // odstęp pomiędzy pomiarami w ms + //strcpy(config.ntp, "0.pl.pool.ntp.org"); + strcpy(config.restURL, "http://62.93.60.19/accels/api1"); + config.restPort = 8000; + strcpy(config.restUser, "wmt"); + strcpy(config.restPass, "Zaq12wsx"); + strcpy(config.S0, "ACCEL1"); + strcpy(config.S1, "ACCEL2"); + strcpy(config.S2, "ACCEL3"); + strcpy(config.S3, "ACCEL4"); + strcpy(config.S4, "ACCEL5"); + strcpy(config.S5, "ACCEL6"); + strcpy(config.S6, "ACCEL7"); + strcpy(config.S7, "ACCEL8"); + EEPROM.put(1, config); + EEPROM.write(0, 251); + saveConfig(); + readConfig(); + isRebootRequired = true; + } + +void ConfigManager::showConfig(){ + ESP_LOGI(TAG_CONF, "Config size: %d bytes max %d", sizeof(config), EEPROM_SIZE); + String ii; + + if(config.connect) + ii = "online"; + else + ii = "offline"; + ESP_LOGI(TAG_CONF, "Mode: %s", ii.c_str()); + + if(config.measure) + ii = "auto"; + else + ii = "manual"; + ESP_LOGI(TAG_CONF, "MEASURE: %s", ii.c_str()); + + ii = "PLACE: " + String(config.place); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "HOSTNAME: " + String(config.hostname); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + + ii = "WIFI SSID: " + String(config.ssid); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + + ii = "WIFI PASS: " + String(config.password); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + + if(config.dhcp) + ii = "yes"; + else + ii = "no"; + ESP_LOGI(TAG_CONF, "DHCP: %s", ii.c_str()); + + ii = "IP: " + String(config.ip); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "MASK: " + String(config.subnet); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "GATEWAY: " + String(config.gateway); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "DNS: " + String(config.dns); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "USER: " + String(config.user); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "USER PASS: " + String(config.pass); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + if(config.connect) { + ii = "URL: " + String(config.restURL); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "PORT: " + String(config.restPort); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "USER: " + String(config.restUser); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "PASS: " + String(config.restPass); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + } + + for (size_t i = 0; i < 32; i++) { + if (config.apiKey[i] < 0x10) Serial.print("0"); + Serial.print(config.apiKey[i], HEX); + } + Serial.println(); + + ii = "Delay: " + String(config.pause) + "ms"; + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "S1: " + String(config.S0); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "S2: " + String(config.S1); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "S3: " + String(config.S2); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "S4: " + String(config.S3); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "S5: " + String(config.S4); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "S6: " + String(config.S5); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "S7: " + String(config.S6); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); + ii = "S8: " + String(config.S7); + ESP_LOGI(TAG_CONF, "%s", ii.c_str()); +} \ No newline at end of file diff --git a/src/Display.cpp b/src/Display.cpp new file mode 100644 index 0000000..597b154 --- /dev/null +++ b/src/Display.cpp @@ -0,0 +1,216 @@ +#include +#include "Display.h" + +static const char *DISP = "display"; + +// Flaga informująca o aktywnym trybie SSL +static bool _sslActive = false; + +Display::Display(RTC_DS3231 &rtc, uint8_t address, uint8_t columns, uint8_t rows):rtc_(rtc) { + _lcd = new LiquidCrystal_I2C(address, columns, rows); + _address = address; + _columns = columns; + _rows = rows; +} + +bool Display::begin(TwoWire *wire) { + wire->begin(); + wire->beginTransmission(_address); + if (wire->endTransmission() != 0) { + return false; // brak odpowiedzi — error + } + + // Inicjalizacja LCD + _lcd->begin(_columns, _rows); + _lcd->backlight(); + _lcd->clear(); + delay(100); + return true; +} + +void Display::setSSLStatus(bool active) { + _sslActive = active; +} + +void Display::initMeasure(bool measure, bool run, bool runmes, uint16_t pause, uint8_t duration, + bool connect, long counter, String gdate, String gtime) { + clear(); + textCenter(0, "** Measure params **"); + String conting = measure ? "yes":"no"; + String st2 = "Continous:"; + String allt = st2 + conting; + textCenter(1, allt.c_str()); + String type = "Sample:" + String(duration) + "s Pause:" + String(pause/1000)+"s"; + textCenter(2, type.c_str()); + textCenter(3, String("Time:" + gtime).c_str()); +} + +void Display::welcomeScreen() { + _lcd->clear(); + _lcd->setCursor(0, 0); + _lcd->print(" VICTUS SYSTEM "); + _lcd->setCursor(0, 1); + _lcd->print(" VIBRATION LOGGER "); + _lcd->setCursor(0, 2); + _lcd->print(" V " + String(VERSION) + " "); + _lcd->setCursor(0, 3); + _lcd->print("WMT Stalowa Wola '25"); +} + +void Display::clearRow(uint16_t line) { + _lcd->setCursor(0, line); + for (int i = 0; i < _columns; i++) { + _lcd->print(" "); + } +} + +void Display::textCenter(uint8_t line, const char *txt) { + int len = strlen(txt); + int pos = (_columns - len) / 2; + if (pos < 0) pos = 0; + _lcd->setCursor(pos, line); + _lcd->print(txt); +} + +void Display::text(uint8_t line, const char *text) { + _lcd->setCursor(0, line); + _lcd->print(text); +} + +void Display::textStatus(const char *text) { + clearRow(3); + _lcd->setCursor(0, 3); + _lcd->print(text); +} + +void Display::clear() { + _lcd->clear(); +} + +void Display::mainScreen() { + // Twoja logika mainScreen + _lcd->clear(); + textCenter(1, "MAIN SCREEN"); +} + +void Display::print(String text) { + _lcd->print(text); +} + +void Display::println(String text) { + _lcd->print(text); +} + +void Display::setCursor(int16_t x, int16_t y) { + _lcd->setCursor(x, y); +} + +void Display::updateNetwork(String ip, bool connected) { + _lcd->setCursor(0, 0); + if (connected) { + _lcd->print("IP:" + ip); + } else { + _lcd->print("IP: DISCONNECTED "); + } +} + +void Display::updateBarWiFi(long signal, bool connected) { + _lcd->setCursor(17, 0); + if (connected) { + if (signal > -60) _lcd->print("((("); + else if (signal > -80) _lcd->print(" (("); + else _lcd->print(" ("); + } else { + _lcd->print(" "); + } +} + +void Display::textStyle(const uint8_t st, String text) { + // Twoja implementacja stylów (np. bold/inwersja jeśli używasz) + _lcd->print(text); +} + +void Display::message(String text) { + clear(); + textCenter(1, text.c_str()); + delay(2000); +} + +void Display::showAccel(float a, float b, float c) { + _lcd->setCursor(0, 1); + _lcd->print("X:"); _lcd->print(a, 2); + _lcd->setCursor(0, 2); + _lcd->print("Y:"); _lcd->print(b, 2); + _lcd->setCursor(0, 3); + _lcd->print("Z:"); _lcd->print(c, 2); +} + +void Display::displayOffline(bool measure, uint8_t count, float freeSpace, long licznik, bool refresh) { + static uint8_t oadxlcnt = 255; + static bool omode = false; + static long last_tick = 0; + + if (refresh) clear(); + + DateTime now = rtc_.now(); + char buf[21]; + snprintf(buf, sizeof(buf), "%02d:%02d:%02d %02d/%02d", now.hour(), now.minute(), now.second(), now.day(), now.month()); + _lcd->setCursor(0, 0); + _lcd->print(buf); + + // Znacznik SSL "S" + if (_sslActive) { + _lcd->setCursor(19, 0); + _lcd->print("S"); + } + + _lcd->setCursor(0, 1); + _lcd->print("SD FREE: "); + _lcd->print(freeSpace, 2); + _lcd->print(" GB "); + + if ((oadxlcnt != count) || (omode != config.measure) || (refresh)){ + oadxlcnt = count; + omode = config.measure; + _lcd->setCursor(0, 2); + _lcd->print("ADXL:"); + _lcd->print(count); + _lcd->print(" MODE:"); + _lcd->print(config.measure ? "AUTO ":"MANUAL "); + } + + if (millis() - last_tick > 1000) { + last_tick = millis(); + _lcd->setCursor(0, 3); + _lcd->print("SYSTEM READY "); + _lcd->print(licznik % 2 == 0 ? "*" : " "); + } +} + +void Display::displayOnOffM(bool measure, String myDir, String myFile) { + clear(); + _lcd->setCursor(0, 0); + _lcd->print(measure ? "MEASURE STARTED" : "MEASURE STOPPED"); + _lcd->setCursor(0, 1); + _lcd->print("DIR: " + myDir); + _lcd->setCursor(0, 2); + _lcd->print(myFile); +} + +void Display::displaySampleRateSummary(uint32_t reccount, uint32_t captureSeconds, float khz, String filename){ + clear(); + _lcd->setCursor(0, 0); + if (captureSeconds == 0) { + _lcd->print("Sampling time 0!"); + delay(1000); + return; + } + float fs = (float)reccount / (float)captureSeconds; + _lcd->print("DONE: " + filename); + _lcd->setCursor(0, 1); + _lcd->print("Recs: " + String(reccount)); + _lcd->setCursor(0, 2); + _lcd->print("Freq: " + String(fs, 1) + " Hz"); + _lcd->setCursor(0, 3); + _lcd->print("Target: " + String(khz, 1) + " kHz"); +} \ No newline at end of file diff --git a/src/Logger.cpp b/src/Logger.cpp new file mode 100644 index 0000000..07b994b --- /dev/null +++ b/src/Logger.cpp @@ -0,0 +1,16 @@ +#include + +const char *TAG_MAIN = "MAIN"; +const char *TAG_DISPLAY = "DISPLAY"; +const char *TAG_WIFI = "WiFi"; +const char *TAG_ADXL = "ADXL345"; +const char *TAG_CONF = "CONFIG"; + + +void init_log_levels() { + esp_log_level_set(TAG_MAIN, ESP_LOG_INFO); + esp_log_level_set(TAG_DISPLAY, ESP_LOG_INFO); + esp_log_level_set(TAG_WIFI, ESP_LOG_INFO); + esp_log_level_set(TAG_ADXL, ESP_LOG_INFO); + esp_log_level_set(TAG_CONF, ESP_LOG_INFO); +} diff --git a/src/Measure.cpp b/src/Measure.cpp new file mode 100644 index 0000000..ff30751 --- /dev/null +++ b/src/Measure.cpp @@ -0,0 +1,594 @@ +#include + +static const char *TAG_CAPTURE = "CAPTURE"; + +// Był na sztywno przydzielony bufor 128 KB. Zmieniam tak, aby zaalokować 4 MB lub 6 MB (trzeba zostawić resztę na potrzeby systemu). +DataCapture::DataCapture(ADXL345FastSPI &adxl, Display &display, RTC_DS3231 &rtc, fs::FS &storage, size_t bufferSize) + : adxl_(adxl), display_(display), rtc_(rtc), _fs(storage){ + // Ustawiamy duży bufor dla PSRAM (np. 4 MB = 4194304 bajty) + // 4 * 1024 * 1024 / 12 bajtów = ~349 525 próbek + // Możesz to przekazać jako parametr lub wpisać na sztywno: + if (bufferSize < 4 * 1024 * 1024) bufferSize = 4 * 1024 * 1024; + bufferSize_ = bufferSize; + + #if defined(ESP32) + // ps_malloc jest kluczowy dla PSRAM. Wymuszamy alokację w zewnętrznym PSRAM + buffer_ = (uint8_t*)ps_malloc(bufferSize_); + #endif + + if (!buffer_) buffer_ = (uint8_t*)malloc(bufferSize_); + if (!buffer_) { + ESP_LOGE(TAG_CAPTURE, "No mem for buffer (%u B)", (unsigned)bufferSize_); + } else { + ESP_LOGI(TAG_CAPTURE, "Capture buffer: %u B", (unsigned)bufferSize_); + } +} + +DataCapture::~DataCapture() { + if (buffer_) { free(buffer_); buffer_ = nullptr; } +} + +void DataCapture::stop() { measurementActive_ = false; } + +// --- ZAPIS BUFORA NA SD --- +bool DataCapture::flushToFile(File& f) { + if (bufferIndex_ == 0) return true; + Watchdog::feed(); + size_t written = f.write(buffer_, bufferIndex_); + if (written != bufferIndex_) { + ESP_LOGE(TAG_CAPTURE, "SD save error (written=%u, expected=%u)", (unsigned)written, (unsigned)bufferIndex_); + display_.textStatus("SD save error"); + bufferIndex_ = 0; + return false; + } + bufferIndex_ = 0; + return true; +} + +// CRC8 (zostawione – nieużywane domyślnie) +uint8_t DataCapture::crc8(const uint8_t* data, size_t len) { + uint8_t crc = 0x00; + for (size_t i = 0; i < len; ++i) { + crc ^= data[i]; + for (uint8_t b = 0; b < 8; ++b) { + if (crc & 0x80) crc = (crc << 1) ^ 0x07; else crc <<= 1; + } + } + return crc; +} + +void DataCapture::setTestingIndicator_(bool on, String myDir, String myFile) { + display_.displayOnOffM(on, myDir, myFile); +} + +// --- automatyczna nazwa pliku --- +bool DataCapture::captureAuto(uint32_t captureSeconds, const char * /*baseDirectory*/) { + isExit = false; + Watchdog::feed(); + String path = allocateNextFilePath(); + if (path.isEmpty()) { + ESP_LOGE(TAG_CAPTURE, "Error generate file name in %s", _baseDir.c_str()); + display_.textStatus("Error file"); + return false; + } + ESP_LOGI(TAG_CAPTURE, "Autogenerate file: %s", path.c_str()); + display_.textStatus(path.c_str()); + Watchdog::feed(); + return capture(captureSeconds, path.c_str()); +} + +// --- main measure function --- +// bool DataCapture::capture(uint32_t captureSeconds, const char *filename) { +// Watchdog::feed(); +// if (!buffer_) { +// ESP_LOGE(TAG_CAPTURE, "No buffer - cancel."); +// display_.textStatus("Buffer error"); +// return false; +// } +// ESP_LOGI(TAG_CAPTURE, "Capture %u sec. -> %s", (unsigned)captureSeconds, filename); + +// File dataFile = _fs.open(filename, FILE_WRITE); +// if (!dataFile) { +// ESP_LOGE(TAG_CAPTURE, "Can't open file: %s", filename); +// char buf[100]; +// snprintf(buf, sizeof(buf), "Can't open: %s", filename); +// display_.textStatus(buf); +// //s2etTestingIndicator_(false, _baseDir, filename); +// return false; +// } + +// // Bufory RAMKI (po 1 próbce z każdego sensora) +// static const uint8_t MAXN = 7; // zgodnie z projektem +// int16_t X[MAXN]{}, Y[MAXN]{}, Z[MAXN]{}; +// uint32_t TS[MAXN]{}; + +// Watchdog::feed(); +// const uint32_t tStart_us = micros(); +// const uint32_t captureDuration_us = captureSeconds * 1000000UL; + +// // Czas startu (UTC) — tylko w nagłówku! +// const DateTime now = rtc_.now(); +// const int32_t unix_start = now.unixtime(); + +// // Nagłówek +// FileHeader hdr; +// memcpy(hdr.magic, "WMT", 3); +// hdr.version = 1; +// hdr.headerSize = sizeof(FileHeader); +// hdr.sampleSize = sizeof(Sample); +// hdr.timestamp = unix_start; +// hdr.reccount = 0; + +// if (dataFile.write(reinterpret_cast(&hdr), sizeof(hdr)) != sizeof(hdr)) { +// ESP_LOGE(TAG_CAPTURE, "Header write failed"); +// display_.textStatus("Header SD failed"); +// dataFile.close(); +// return false; +// } + +// measurementActive_ = true; +// bufferIndex_ = 0; +// setTestingIndicator_(true, _baseDir, filename); + +// uint32_t frames = 0; // liczba zebranych „ramek” +// const uint8_t presentCnt = adxl_.size(); // wykryte sensory (wg begin) + +// // --- Pętla akwizycji wyrównanych ramek --- +// while (measurementActive_) { +// if(isEscape()) break; // wyjście z pętli gdy OK przerwie +// uint32_t now_us = micros(); +// if ((now_us - tStart_us) >= captureDuration_us) break; + +// // 1) Czekamy aż KAŻDY obecny sensor ma >= 1 próbkę w FIFO (DATA_READY) +// while (!adxl_.availableAll()) { +// if(isEscape()) break; //????? sprawdź kHz pomiaru! +// // krótki spin; unikamy delay(1), aby nie zrywać 3.2 kHz +// // (opcjonalnie yield(); jeśli system wymaga) +// } + +// // 2) Zdejmij po 1 NAJSTARSZEJ próbce z każdego sensora (STREAM FIFO) +// const uint8_t got = adxl_.readAlignedOnce(X, Y, Z, TS); +// if (got != presentCnt) { +// // rzadki przypadek – niepełna ramka; pomiń +// continue; +// } + +// // 3) Wspólny timestamp ramki: minimalny z TS[i] +// uint32_t tmin = UINT32_MAX; +// for (uint8_t i = 0; i < MAXN; ++i) { +// if (!adxl_.isPresent(i)) continue; +// if (TS[i] < tmin) tmin = TS[i]; +// } +// const uint32_t frame_offset_us = tmin - tStart_us; + +// // 4) Zapis 7 rekordów Sample do bufora +// for (uint8_t i = 0; i < MAXN; ++i) { +// if (!adxl_.isPresent(i)) continue; + +// // Konstruuj rekord bezpośrednio w buforze (bez memcpy) +// if (bufferIndex_ + sizeof(Sample) > bufferSize_) { +// if (!flushToFile(dataFile)) { measurementActive_ = false; break; } +// } +// Sample *dst = reinterpret_cast(buffer_ + bufferIndex_); +// dst->offset = frame_offset_us; +// dst->sensor_id = i; +// dst->x = X[i]; dst->y = Y[i]; dst->z = Z[i]; +// dst->ready = true; +// bufferIndex_ += sizeof(Sample); +// } + +// if (!measurementActive_) break; +// frames++; +// // Watchdog rzadziej, żeby nie zwiększać jittera +// if ((frames & 0xFF) == 0) Watchdog::feed(); +// } // while +// // Statystyki +// ESP_LOGI(TAG_CAPTURE, "Frames: %u, sensors: %u", (unsigned)frames, (unsigned)presentCnt); +// printSamplingRate(frames, captureSeconds); // liczymy ramki/sek. + +// // Domknij bufor +// if (bufferIndex_ > 0) { +// if (!flushToFile(dataFile)) { +// Watchdog::feed(); +// ESP_LOGE(TAG_CAPTURE, "Finish save error."); +// display_.textStatus("Save SD error"); +// delay(1000); +// } +// } +// ESP_LOGI(TAG_CAPTURE, "Saved to SD successful"); +// display_.textStatus("Saved SD OK"); +// delay(700); +// // Uzupełnij nagłówek o liczbę rekordów (Sample) +// hdr.reccount = frames * presentCnt; +// dataFile.seek(0); +// dataFile.write(reinterpret_cast(&hdr), sizeof(hdr)); +// dataFile.flush(); +// dataFile.close(); + +// //setTestingIndicator_(false, _baseDir, filename); + +// bool ok = measurementActive_; +// measurementActive_ = false; + +// if (ok) { +// ESP_LOGI(TAG_CAPTURE, "Successful"); +// display_.textStatus("Successfull"); +// Watchdog::feed(); +// return true; +// } else { +// ESP_LOGE(TAG_CAPTURE, "Measurement aborted"); +// display_.textStatus("Aborted"); +// Watchdog::feed(); +// return false;f +// } +// } + +// --- main measure function --- V2 +bool DataCapture::capture(uint32_t captureSeconds, const char *filename) { + Watchdog::feed(); + if (!buffer_) { + ESP_LOGE(TAG_CAPTURE, "No buffer - cancel."); + display_.textStatus("Buffer error"); + return false; + } + + // Obliczamy ile danych maksymalnie może wejść do bufora + const uint8_t presentCnt = adxl_.size(); + const size_t frameSize = presentCnt * sizeof(Sample); + + ESP_LOGI(TAG_CAPTURE, "Start RAM capture: %u sec.", (unsigned)captureSeconds); + display_.textStatus("Sampling..."); + + static const uint8_t MAXN = 7; + int16_t X[MAXN]{}, Y[MAXN]{}, Z[MAXN]{}; + uint32_t TS[MAXN]{}; + + measurementActive_ = true; + bufferIndex_ = 0; + uint32_t frames = 0; + + const uint32_t tStart_us = micros(); + const uint32_t captureDuration_us = captureSeconds * 1000000UL; + const DateTime now_start = rtc_.now(); // Czas dla nagłówka + + // --- KRYTYCZNA PĘTLA POMIAROWA (ZERO SD) --- + while (measurementActive_) { + if(isEscape()) break; + uint32_t now_us = micros(); + if ((now_us - tStart_us) >= captureDuration_us) break; + + // Sprawdzenie czy mamy miejsce w PSRAM na kolejną pełną ramkę + if (bufferIndex_ + frameSize > bufferSize_) { + ESP_LOGW(TAG_CAPTURE, "PSRAM Buffer Full! Stopping."); + break; + } + + // Czekamy na dane z sensora + while (!adxl_.availableAll()) { + if(isEscape()) break; + } + + const uint8_t got = adxl_.readAlignedOnce(X, Y, Z, TS); + if (got != presentCnt) continue; + + // Wspólny timestamp ramki + uint32_t tmin = UINT32_MAX; + for (uint8_t i = 0; i < MAXN; ++i) { + if (!adxl_.isPresent(i)) continue; + if (TS[i] < tmin) tmin = TS[i]; + } + const uint32_t frame_offset_us = tmin - tStart_us; + + // Zapis do PSRAM + for (uint8_t i = 0; i < MAXN; ++i) { + if (!adxl_.isPresent(i)) continue; + Sample *dst = reinterpret_cast(buffer_ + bufferIndex_); + dst->offset = frame_offset_us; + dst->sensor_id = i; + dst->x = X[i]; dst->y = Y[i]; dst->z = Z[i]; + dst->ready = true; + bufferIndex_ += sizeof(Sample); + } + frames++; + if ((frames & 0x3FF) == 0) Watchdog::feed(); // Rzadziej, by nie siać jittera + } // --- KONIEC PĘTLI POMIAROWEJ --- + + measurementActive_ = false; + ESP_LOGI(TAG_CAPTURE, "Capture finished. Samples in RAM: %u. Writing to SD...", (unsigned)frames); + display_.textStatus("Saving to SD..."); + + // Dopiero teraz otwieramy plik na SD + File dataFile = _fs.open(filename, FILE_WRITE); + if (!dataFile) { + ESP_LOGE(TAG_CAPTURE, "Can't open SD file!"); + display_.textStatus("SD Open Error"); + return false; + } + + // Przygotowanie nagłówka + FileHeader hdr; + memcpy(hdr.magic, "WMT", 3); + hdr.version = 1; + hdr.headerSize = sizeof(FileHeader); + hdr.sampleSize = sizeof(Sample); + hdr.timestamp = now_start.unixtime(); + hdr.reccount = frames * presentCnt; + + // Zapis nagłówka + dataFile.write(reinterpret_cast(&hdr), sizeof(hdr)); + + // Zapis całego bufora PSRAM jednym ciągiem + size_t written = dataFile.write(buffer_, bufferIndex_); + + if (written == bufferIndex_) { + ESP_LOGI(TAG_CAPTURE, "SD Save Successful: %u bytes", (unsigned)written); + //display_.textStatus("Save OK"); + display_.textStatus(basenameFromPath(filename)); + delay(2000); // Tutaj zrob to inaczej + } else { + ESP_LOGE(TAG_CAPTURE, "SD Write Error!"); + display_.textStatus("SD Write Err"); + } + + dataFile.close(); + printSamplingRate(frames, captureSeconds, filename); + return (written == bufferIndex_); +} + + +// --- Narzędzia SD i pierdoły --- +String DataCapture::unixToDateTime(uint32_t ts) { + DateTime dt(ts); + char buf[25]; + snprintf(buf, sizeof(buf), "%04u-%02u-%02u %02u:%02u:%02u", + dt.year(), dt.month(), dt.day(), dt.hour(), dt.minute(), dt.second()); + return String(buf); +} + +void DataCapture::readHeaderAndPrint(const char *path) { + Watchdog::feed(); + File f = SD.open(path, FILE_READ); + if (!f) { ESP_LOGE(TAG_CAPTURE,"Error open file %s", path); return; } + + FileHeader hdr; + size_t rd = f.read((uint8_t*)&hdr, sizeof(hdr)); + f.close(); + if (rd != sizeof(hdr)) { ESP_LOGE(TAG_CAPTURE,"Header error"); return; } + + if (memcmp(hdr.magic, "WMT", 3) != 0) { ESP_LOGE(TAG_CAPTURE,"File signature error"); return; } + + ESP_LOGI(TAG_CAPTURE,"===== WMT file header ====="); + ESP_LOGI(TAG_CAPTURE,"Signature: %.3s", hdr.magic); + ESP_LOGI(TAG_CAPTURE,"Version: %u", hdr.version); + ESP_LOGI(TAG_CAPTURE,"Header size:%u B", hdr.headerSize); + ESP_LOGI(TAG_CAPTURE,"Sample size:%u B", hdr.sampleSize); + ESP_LOGI(TAG_CAPTURE,"Timestamp: %d", hdr.timestamp); + ESP_LOGI(TAG_CAPTURE,"Reccount: %u", hdr.reccount); + ESP_LOGI(TAG_CAPTURE,"==========================="); +} + +void DataCapture::printSamplingRate(uint32_t reccount, uint32_t captureSeconds, String filename) { + Watchdog::feed(); + if (captureSeconds == 0) { ESP_LOGW(TAG_CAPTURE, "Sampling time = 0!"); return; } + float fs = (float)reccount / (float)captureSeconds; // Hz (ramki/s) + float fs_kHz = fs / 1000.0f; + ESP_LOGI(TAG_CAPTURE,"Frames: %u", (unsigned)reccount); + ESP_LOGI(TAG_CAPTURE,"Time: %u s", (unsigned)captureSeconds); + ESP_LOGI(TAG_CAPTURE,"Rate: %.3f kHz (frames)", fs_kHz); + display_.displaySampleRateSummary(reccount, captureSeconds, fs_kHz, filename); + display_.textStatus("Summary"); + delay(2000); +} + +// --- Zarządzanie katalogami/plikiem --- +String DataCapture::allocateNextFilePath() { + Watchdog::feed(); + uint32_t highestDir = findHighestNumericDir(); + if (highestDir == 0) { + highestDir = 1; + if (!ensureDir(highestDir)) return String(); + } + uint32_t count = 0, highestIdx = 0; + scanDirForWmt(highestDir, count, highestIdx); + + uint32_t targetDir = highestDir; + uint32_t nextIdx = 0; + + if (count > _maxFilesPerDir) { + targetDir = highestDir + 1; + if (!ensureDir(targetDir)) return String(); + nextIdx = 1; + } else { + nextIdx = (highestIdx == 0) ? 1 : (highestIdx + 1); + } + return joinPath(dirPath(targetDir), makeIndexedName(nextIdx)); +} + +String DataCapture::generateNextFilename() { + Watchdog::feed(); + uint32_t highestDir = findHighestNumericDir(); + if (highestDir == 0) return String(); + + uint32_t count = 0, highestIdx = 0; + scanDirForWmt(highestDir, count, highestIdx); + ESP_LOGI(TAG_CAPTURE, "highestDir %u, count %u, highestIdx %u", (unsigned)highestDir, (unsigned)count, (unsigned)highestIdx); + if (highestIdx == 0) return String(); + return joinPath(dirPath(highestDir), makeIndexedName(highestIdx)); +} + +FileInfo DataCapture::getLastFileInfo() { + FileInfo info{String(), 0u, false}; + Watchdog::feed(); + String lastPath = generateNextFilename(); + if (lastPath.isEmpty()) return info; + + if (_fs.exists(lastPath)) { + File f = _fs.open(lastPath, FILE_READ); + if (f) { + info.exists = true; + info.path = lastPath; + info.size = f.size(); + f.close(); + } + } + return info; +} + +bool DataCapture::deleteAllOnSD() { + Watchdog::feed(); + File root = _fs.open(_baseDir); + if (!root || !root.isDirectory()) return false; + + for (File e = root.openNextFile(); e; e = root.openNextFile()) { + Watchdog::feed(); + String child = String(e.name()); + e.close(); + if (!recursiveDelete(child)) return false; + } + root.close(); + return true; +} + +SpaceInfo DataCapture::freeSpaceMB() { + Watchdog::feed(); + SpaceInfo si{0.0, "UNKNOWN"}; +#if defined(ESP32) + uint64_t total = SD.totalBytes(); + uint64_t used = SD.usedBytes(); + if (total >= used) { + uint64_t freeB = total - used; + const double oneGB = 1024.0 * 1024.0 * 1024.0; + const double oneMB = 1024.0 * 1024.0; + if (freeB >= (uint64_t)oneGB) { + si.value = (double)freeB / oneGB; si.unit = "GB"; + } else { + si.value = (double)freeB / oneMB; si.unit = "MB"; + } + } +#endif + return si; +} + +float DataCapture::freeSpaceFloat(bool* isGB) { + Watchdog::feed(); + SpaceInfo si = freeSpaceMB(); + if (!si.unit || strcmp(si.unit, "UNKNOWN") == 0) { + if (isGB) *isGB = false; + return -1.0f; + } + if (isGB) *isGB = (strcmp(si.unit, "GB") == 0); + return static_cast(si.value); +} + +// --- helpery pomocnicze --- + +bool DataCapture::isAllDigits(const char *s) { + if (!s || !*s) return false; + while (*s) { if (*s < '0' || *s > '9') return false; ++s; } + return true; +} + +uint32_t DataCapture::toUint(const char *s) { + uint32_t v = 0; while (*s) { v = v*10u + uint32_t(*s - '0'); ++s; } return v; +} + +String DataCapture::dirPath(uint32_t dirNum) const { return joinPath(_baseDir, String(dirNum)); } + +const char *DataCapture::basenameFromPath(const char *full) { const char *slash = strrchr(full, '/'); return slash ? slash + 1 : full; } + +bool DataCapture::isWmtWithDigits(const char* name, uint32_t& idxOut) const { + size_t nlen = strlen(name), extLen = _ext.length(); + if (nlen != (size_t)_digits + extLen) return false; + if (strncmp(name + _digits, _ext.c_str(), extLen) != 0) 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'; + idxOut = (uint32_t)strtoul(buf, nullptr, 10); return true; +} + +String DataCapture::makeIndexedName(uint32_t idx) const { + char fmt[8]; snprintf(fmt, sizeof(fmt), "%%0%ulu", (unsigned long)_digits); + char num[24]; snprintf(num, sizeof(num), fmt, (unsigned long)idx); + return String(num) + _ext; +} + +String DataCapture::joinPath(const String &a, const String &b) { if (a.endsWith("/")) return a + b; return a + "/" + b; } +bool DataCapture::ensureDir(uint32_t dirNum) { + String path = dirPath(dirNum); + if (_fs.exists(path)) { File f = _fs.open(path); bool ok = f && f.isDirectory(); if (f) f.close(); return ok; } + return _fs.mkdir(path); +} + +uint32_t DataCapture::findHighestNumericDir() { + File root = _fs.open(_baseDir); + if (!root || !root.isDirectory()) return 0; + uint32_t maxDir = 0; + for (File f = root.openNextFile(); f; f = root.openNextFile()) { + if (f.isDirectory()) { + const char *nm = basenameFromPath(f.name()); + if (isAllDigits(nm)) { uint32_t v = toUint(nm); if (v > maxDir) maxDir = v; } + } + } + return maxDir; +} + +void DataCapture::scanDirForWmt(uint32_t dirNum, uint32_t &count, uint32_t &highestIdx) { + Watchdog::feed(); + count = 0; highestIdx = 0; + String path = dirPath(dirNum); + File dir = _fs.open(path); + if (!dir || !dir.isDirectory()) return; + for (File f = dir.openNextFile(); f; f = dir.openNextFile()) { + if (f.isDirectory()) { Watchdog::feed(); f.close(); continue; } + const char* base = basenameFromPath(f.name()); + uint32_t idx = 0; + if (isWmtWithDigits(base, idx)) { count++; if (idx > highestIdx) highestIdx = idx; } + f.close(); + } +} + +bool DataCapture::recursiveDelete(const String &path) { + File e = _fs.open(path); + if (!e) return false; + if (!e.isDirectory()) { e.close(); return _fs.remove(path); } + // katalog + for (File c = e.openNextFile(); c; c = e.openNextFile()) { + String child = String(c.name()); c.close(); + if (!recursiveDelete(child)) { e.close(); return false; } + } + e.close(); + if (path == _baseDir) return true; // nie usuwamy katalogu bazowego + return _fs.rmdir(path); +} + +void DataCapture::printLastFileInfoSerial() { + Watchdog::feed(); + const FileInfo info = getLastFileInfo(); + if (!info.exists) { + ESP_LOGE(TAG_CAPTURE, "No .wmt file exists. Last file not exists"); + return; + } + + const double kb = static_cast(info.size) / 1024.0; + const double mb = kb / 1024.0; + +#if defined(ESP32) + // ESP32 wspiera Serial.printf z %llu + ESP_LOGI(TAG_CAPTURE, "Last file: %s", info.path.c_str()); + ESP_LOGI(TAG_CAPTURE, "Size: %llu B (%.2f KB, %.2f MB)", static_cast(info.size), kb, mb); + char buf[100]; + snprintf(buf, sizeof(buf), "Size:%.2f KB", kb); + display_.textStatus(buf); +#else + // Wariant zachowawczy dla platform bez %llu; pokazujemy rozmiar w MB/KB. + ESP_LOGI(TAG_CAPTURE, "Last file info: ")); + ESP_LOGI(TAG_CAPTURE, info.path); + ESP_LOGI(TAG_CAPTURE, "Size: ")); + ESP_LOGI(TAG_CAPTURE, mb); + //ESP_LOGI(TAG_CAPTURE, " MB")); + //Serial.print(F(" (")); + //Serial.print(kb, 2); + //Serial.println(F(" KB)")); +#endif + +} + diff --git a/src/Network.cpp b/src/Network.cpp new file mode 100644 index 0000000..01d06e4 --- /dev/null +++ b/src/Network.cpp @@ -0,0 +1,292 @@ +#include +#include "Watchdog.h" // Dodano dla feed() w portalu + +static const char *WIFI = "wifi"; + +WiFiManager::WiFiManager() {} + +void WiFiManager::begin() { + ESP_LOGI(WIFI, "Start network"); + isAccessPoint = config.connect; + if (!isAccessPoint) { + setupAccessPoint(ssidAP.c_str(), passwordAP.c_str()); + } else { + ESP_LOGI(WIFI, "WiFi client"); + ReadConnection(); + connectToWiFi(); + } + getRSSI(); + setupMDNS(); +} + +// NOWA FUNKCJA: Portal konfiguracyjny (Captive Portal) +void WiFiManager::startConfigPortal(Display &display) { + WiFi.mode(WIFI_AP); + WiFi.softAP(ssidAP.c_str(), passwordAP.c_str()); + + dnsServer.start(53, "*", WiFi.softAPIP()); + + server.on("/", [this]() { this->handleRoot(); }); + server.on("/save", [this]() { this->handleSave(); }); + server.onNotFound([this]() { this->handleRoot(); }); + server.begin(); + + display.clear(); + display.textCenter(0, "WIFI SETUP MODE"); + display.textCenter(1, ssidAP.c_str()); + display.textCenter(2, "IP: 192.168.4.1"); + + ESP_LOGI(WIFI, "Portal started: %s", ssidAP.c_str()); + + while (true) { + dnsServer.processNextRequest(); + server.handleClient(); + Watchdog::feed(); // Użycie Twojego Watchdoga z Watchdog.h + delay(10); + // Pętla trwa do momentu restartu w handleSave() + } +} + +void WiFiManager::handleRoot() { + String html = ""; + html += ""; + html += "

VICTUS WMT

Konfiguracja WiFi:

"; + html += "
"; + html += "SSID:

"; + html += "Hasło:

"; + html += "
"; + server.send(200, "text/html", html); +} + +void WiFiManager::handleSave() { + String newSsid = server.arg("s"); + String newPass = server.arg("p"); + + if (newSsid.length() > 0) { + strncpy(config.ssid, newSsid.c_str(), sizeof(config.ssid)); + strncpy(config.password, newPass.c_str(), sizeof(config.password)); + configManager.saveConfig(); + + server.send(200, "text/html", "Zapisano dane. Restartuje..."); + ESP_LOGI(WIFI, "WiFi saved. Rebooting..."); + delay(2000); + ESP.restart(); + } +} + +// --- TWOJE ORYGINALNE FUNKCJE (ZACHOWANE W 100%) --- + +// Konwersja char na IPAddress +bool WiFiManager::convertCharToIPAddress(const char *str, IPAddress& ip) { + uint8_t octets[4]; + int parsed = sscanf(str, "%hhu.%hhu.%hhu.%hhu", &octets[0], &octets[1], &octets[2], &octets[3]); + if (parsed == 4) { + ip = IPAddress(octets[0], octets[1], octets[2], octets[3]); + return true; + } + return false; +} + +void WiFiManager::ReadConnection() { + ESP_LOGE(WIFI, "Network config error"); + isAccessPoint = config.connect; + useDHCP = config.dhcp; + convertCharToIPAddress(config.ip, local_IP); + convertCharToIPAddress(config.gateway, gateway); + convertCharToIPAddress(config.subnet, subnet); + convertCharToIPAddress(config.dns, dns); +} + +void WiFiManager::connectToWiFi() { + ESP_LOGI(WIFI, "WiFi STA mode"); + WiFi.mode(WIFI_STA); + WiFi.begin(config.ssid, config.password); + + if (!useDHCP) { + if (!WiFi.config(local_IP, gateway, subnet, dns)) { + ESP_LOGE(WIFI, "Network static IP failed"); + } + } + + ESP_LOGI(WIFI, "WiFi connecting"); + int retries = 0; + while (WiFi.status() != WL_CONNECTED && retries < 20) { + delay(500); + retries++; + updateLED(); + } + + if (WiFi.status() == WL_CONNECTED) { + ESP_LOGI(WIFI, "SSID: %s", config.ssid); + String ipString = "IP: " + WiFi.localIP().toString(); + ESP_LOGI(WIFI, "%s", ipString.c_str()); + String gatewayInfo = "GATEWAY: " + WiFi.gatewayIP().toString(); + ESP_LOGI(WIFI, "%s", gatewayInfo.c_str()); + String dnsInfo = "DNS: " + WiFi.dnsIP().toString(); + ESP_LOGI(WIFI, "%s", dnsInfo.c_str()); + } else { + String infoWiFi = "WIFI CONNECTION ERROR: " + String(config.ssid); + ESP_LOGI(WIFI, "%s", infoWiFi.c_str()); + } + updateLED(); +} + +void WiFiManager::setupAccessPoint(const char *newSSID, const char *newPassword) { + ESP_LOGI(WIFI, "Start AP mode"); + // Wyłączenie zapisywania konfiguracji do flash + //WiFi.persistent(false); + // Usunięcie zapisanej konfiguracji trybu stacji + //WiFi.disconnect(true); + //delay(1000); + //WiFi.eraseAP(); + //WiFi.enableAP(false); + //WiFi.enableAP(true); + WiFi.mode(WIFI_AP); + WiFi.softAPsetHostname(config.hostname); + WiFi.softAP(newSSID, newPassword); + delay(1000); + String ssi = "AP SSID: " + String(WiFi.softAPSSID()); + ESP_LOGI(WIFI, "%s", ssi.c_str()); + String sspass = "AP PASS: " + String(newPassword); + ESP_LOGI(WIFI, "%s", sspass.c_str()); + IPAddress IP = WiFi.softAPIP(); + String ipString = "AP IP: " + IP.toString(); + ESP_LOGI(WIFI, "%s", ipString.c_str()); + setupMDNS(); + getRSSI(); +} + +bool WiFiManager::isWiFiOK(){ + if (WiFi.status() != WL_CONNECTED) + return false; + else + return true; +} + +void WiFiManager::checkWiFiConnection() { + if (!isAccessPoint) { + getRSSI(); + if (WiFi.status() != WL_CONNECTED) { + String infoWiFi = "WiFi reconnecting: " + String(config.ssid); + ESP_LOGI(WIFI, "%s", infoWiFi.c_str()); + WiFi.reconnect(); + int retries = 0; + while (WiFi.status() != WL_CONNECTED && retries < 20) { + delay(500); + retries++; + updateLED(); + } + if (WiFi.status() == WL_CONNECTED) { + //String infoWiFi = "WiFi connected: " + String(config.ssid); + //ESP_LOGI(WIFI, "%s", infoWiFi.c_str()); + //String ipString = "IP: " + WiFi.localIP().toString(); + //ESP_LOGI(WIFI, "%s", ipString.c_str()); + getRSSI(); + setupMDNS(); + } else { + String infoWiFi = "WiFi reconnect error: " + String(config.ssid); + ESP_LOGI(WIFI, "%s", infoWiFi.c_str()); + getRSSI(); + } + } + updateLED(); + } +} + +int WiFiManager::rssiToPercent(int rssi) { + if (rssi <= -100) { + return 0; + } else + if (rssi >= -50) { + return 100; + } + // Dla wartości pomiędzy -100 a -50 dBm stosujemy prostą liniową skalę + else { + return 2 * (rssi + 100); // Przykładowa liniowa zależność + } +} + +void WiFiManager::updateLED() { + getRSSI(); + if (WiFi.status() == WL_CONNECTED) { + int8_t rssi = WiFi.RSSI(); + if (rssi > -70) { + ; + //digitalWrite(LED_PIN, HIGH); // Silny sygnał + } else { + String signalInfo = "WIFI WEAK SIGNAL: " + String(rssi) + " " + rssiToPercent(rssi) + "%"; + ESP_LOGW(WIFI, "%s", signalInfo.c_str()); + } + } +} + +int8_t WiFiManager::getRSSI() { + if (WiFi.status() == WL_CONNECTED) { + rssi = WiFi.RSSI(); + return rssi; + } + return 0; +} + +void WiFiManager::setupMDNS() { + ESP_LOGI(WIFI, "mDNS start"); + if (!MDNS.begin(config.hostname)) { + ESP_LOGE(WIFI, "mDNS error"); + } else { + String mdnsstr = "MDNS: http://" + String(config.hostname) + ".local"; + ESP_LOGI(WIFI, "%s", mdnsstr.c_str()); + } +} + +bool WiFiManager::performOTAUpdate(bool allowInsecureTLS, std::function progressCb){ + // 1) Pobierz i sprawdź URL + String url = String(config.updateUrl); // z Config.h – globalny 'config' + url.trim(); + if (url.isEmpty()) { + ESP_LOGE(WIFI, "[OTA] Pusty config.updateUrl – przerwano."); + return false; + } + ESP_LOGI(WIFI, "[OTA] URL: %s", url.c_str()); + + // 2) Wymagamy aktywnego Wi-Fi w trybie klienta + if (WiFi.status() != WL_CONNECTED) { + ESP_LOGE(WIFI, "[OTA] Brak połączenia Wi-Fi – przerwano."); + return false; + } + + // 3) Callback postępu (opcjonalny) + if (progressCb) { + httpUpdate.onProgress([&](int cur, int total){ progressCb(cur, total); }); + } + + // 4) Konfiguracja klienta i wywołanie aktualizacji + httpUpdate.rebootOnUpdate(true); // po sukcesie – reboot + t_httpUpdate_return ret; + + if (url.startsWith("https://")) { + WiFiClientSecure client; + if (allowInsecureTLS) { + client.setInsecure(); // UWAGA: testy/dev; w produkcji lepiej setCACert(...) + } + client.setTimeout(15000); + ret = httpUpdate.update(client, url); // HTTPS + } else { + WiFiClient client; + client.setTimeout(15000); + ret = httpUpdate.update(client, url); // HTTP + } + + // 5) Obsługa rezultatów + switch (ret) { + case HTTP_UPDATE_OK: + ESP_LOGI(WIFI, "[OTA] Sukces - restart nastąpi za chwilę."); + return true; // reboot i tak zaraz nastąpi + case HTTP_UPDATE_NO_UPDATES: + ESP_LOGW(WIFI, "[OTA] Brak nowej wersji (304/Not Modified)."); + return false; + case HTTP_UPDATE_FAILED: + default: + ESP_LOGE(WIFI, "[OTA] Błąd (%d): %s", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); + return false; + } +} \ No newline at end of file diff --git a/src/Settings.cpp b/src/Settings.cpp new file mode 100644 index 0000000..c698f3c --- /dev/null +++ b/src/Settings.cpp @@ -0,0 +1,168 @@ +#include + +// Lokalny tag logów +static const char *TAG_SETTINGS = "SETTINGS"; + +Settings::Settings(Display &display, RTC_DS3231 &rtc): display_(display), rtc_(rtc){} + +Settings::~Settings() {} + +void Settings::begin() { + pinMode(BTN_UP, INPUT_PULLUP); + pinMode(BTN_OK, INPUT_PULLUP); + pinMode(BTN_DOWN, INPUT_PULLUP); +} + +// Zwraca true, jeśli przycisk jest wciśnięty +bool Settings::isPressed(uint8_t btnIndex) { + //ESP_LOGI(TAG_SETTINGS, "BTN check"); + switch (btnIndex) { + case 1: return digitalRead(BTN_UP) == LOW; + case 2: return digitalRead(BTN_OK) == LOW; + case 3: return digitalRead(BTN_DOWN) == LOW; + default: return false; + } +} + +// Kombinacja 1 i 3 jednocześnie +bool Settings::isBtnReset() { + ESP_LOGI(TAG_SETTINGS, "BTN reset check"); + return (digitalRead(BTN_UP) == LOW && digitalRead(BTN_DOWN) == LOW); +} + + +/* + Ustawienie opcji +*/ + void Settings::setConfigDevice() { + ESP_LOGI(TAG_SETTINGS, "Set config device"); + display_.clear(); + delay(300); + + config.duration = editField(config.duration, 1, 20, "Test duration"); + uint16_t dur = config.pause/1000; + dur = editField(dur, 1, 20, "Pause between"); + config.pause = dur * 1000; + config.measure = editField(config.measure, 0, 1, "Autorun test"); + ESP_LOGI(TAG_SETTINGS, "Config finish"); +} + +void Settings::finishConfigDevice(){ + display_.clear(); + display_.textCenter(1, "CONFIG UPDATED"); + display_.textCenter(2, "RESTARTING"); + delay(1000); + display_.clear(); + ESP.restart(); +} + + +/* -------------------------------------------------------------------- + * Ustawianie czasu RTC: + * Kolejność: rok -> miesiąc -> dzień -> godzina -> minuta -> sekunda + * ------------------------------------------------------------------*/ +void Settings::setTimeRTC() { + DateTime now = rtc_.now(); + RtcDateTime t; + t.year = now.year(); + t.month = now.month(); + t.day = now.day(); + t.hour = now.hour(); + t.minute = now.minute(); + t.second = now.second(); + + ESP_LOGI(TAG_SETTINGS, + "Start RTC edit: %04u-%02u-%02u %02u:%02u:%02u", + t.year, t.month, t.day, t.hour, t.minute, t.second); + + display_.clear(); + delay(300); + + // Kolejne pola + t.year = editField(t.year, 2024, 2099, "YEAR"); + t.month = editField(t.month, 1, 12, "MONTH"); + // uproszczenie: 1–31 bez sprawdzania długości miesiąca + t.day = editField(t.day, 1, 31, "DAY"); + t.hour = editField(t.hour, 0, 23, "HOUR"); + t.minute = editField(t.minute, 0, 59, "MINUTE"); + t.second = editField(t.second, 0, 59, "SECOND"); + + // Zapis do RTC + rtc_.adjust(DateTime(t.year, t.month, t.day, t.hour, t.minute, t.second)); + + ESP_LOGI(TAG_SETTINGS, + "RTC set to: %04u-%02u-%02u %02u:%02u:%02u", + t.year, t.month, t.day, t.hour, t.minute, t.second); + + display_.clear(); + display_.textCenter(1, "RTC UPDATED"); + display_.textCenter(2, "RESTARTING"); + //display_.textCenter(2, "OK TO CONTINUE"); + delay(1000); + display_.clear(); + ESP.restart(); +} + +// Edycja jednej wartości (rok/miesiąc/dzień/godz/min/sek) +int Settings::editField(int value, int minVal, int maxVal, const char *label) { + bool lastUp = false; + bool lastDown = false; + bool lastOk = false; + + constrainValue(value, minVal, maxVal); + printField(label, value); + + while (true) { + bool up = readBtnUp(); + bool ok = readBtnOk(); + bool down = readBtnDown(); + + // Zmiana przy puszczeniu/wciśnięciu (zbocze narastające) + if (up && !lastUp) { + value++; + if (value > maxVal) value = minVal; + printField(label, value); + } + + if (down && !lastDown) { + value--; + if (value < minVal) value = maxVal; + printField(label, value); + } + + if (ok && !lastOk) { + ESP_LOGI(TAG_SETTINGS, "[OK] %s = %d", label, value); + // mały debounce na wyjście z pola + delay(200); + return value; + } + + lastUp = up; + lastDown = down; + lastOk = ok; + + delay(80); // prosty debounce + ograniczenie odpytywania + } +} + +void Settings::constrainValue(int &value, int minVal, int maxVal) { + if (value < minVal) value = minVal; + if (value > maxVal) value = maxVal; +} + +// Wyświetlanie aktualnie edytowanego pola na LCD +void Settings::printField(const char *label, int value) { + char buf[21]; + display_.clear(); + // Linia 0: nazwa pola + snprintf(buf, sizeof(buf), "%s:", label); + display_.text(0, buf); + + // Linia 1: wartość + snprintf(buf, sizeof(buf), "%d", value); + display_.text(1, buf); + + // Linia 3: podpowiedź + display_.textStatus("UP/DOWN, OK=Next"); + ESP_LOGI(TAG_SETTINGS, "%s = %d", label, value); +} \ No newline at end of file diff --git a/src/Tool.cpp b/src/Tool.cpp new file mode 100644 index 0000000..868dfe7 --- /dev/null +++ b/src/Tool.cpp @@ -0,0 +1,48 @@ +#include + +static const char *TOOL = "tool"; + +bool isI2CDevPresent(uint8_t address) { + Wire.beginTransmission(address); + uint8_t error = Wire.endTransmission(); + + if (error == 0) { + ESP_LOGI(TOOL, "I2C response from 0x%02X", address); + return true; + } else { + if (error == 4) { + ESP_LOGW(TOOL, "I2C unknown error at 0x%02X", address); + } else { + ESP_LOGI(TOOL, "No I2C device at 0x%02X", address); + } + return false; + } +} + +void scanI2C() { + byte error, address; + int nDevices = 0; + ESP_LOGI(TOOL, "I2C start scan"); + for (address = 1; address < 127; address++) { + Wire.beginTransmission(address); + error = Wire.endTransmission(); + + if (error == 0) { + char buf[32]; + snprintf(buf, sizeof(buf), "I2C device: 0x%02X", address); + ESP_LOGI(TOOL, "%s", buf); + nDevices++; + Watchdog::feed(); + } + else if (error == 4) { + ESP_LOGW(TOOL, "I2C error at address: 0x%02X", address); + } + } + + if (nDevices == 0) { + ESP_LOGE(TOOL, "I2C no devices found"); + } else { + ESP_LOGI(TOOL, "I2C scan finished. Found %d device(s)", nDevices); + } + Watchdog::feed(); +} diff --git a/src/Uploader.cpp b/src/Uploader.cpp new file mode 100644 index 0000000..2f7fa4d --- /dev/null +++ b/src/Uploader.cpp @@ -0,0 +1,79 @@ +#include "Uploader.h" + +Uploader::Uploader(Display &display) : _display(display) {} + +void Uploader::processQueue(int maxFiles) { + if (WiFi.status() != WL_CONNECTED) return; + + String caCert = loadCACert("/cert.pem"); + if (caCert == "") { + ESP_LOGE("UPLOADER", "Brak cert.pem na SD!"); + return; + } + + int sentCount = 0; + File root = SD.open("/"); + + // Przeszukiwanie folderów numerycznych stworzonych przez Measure.cpp + while (File folder = root.openNextFile()) { + if (sentCount >= maxFiles) break; + if (folder.isDirectory()) { + File dir = SD.open(folder.path()); + while (File file = dir.openNextFile()) { + if (sentCount >= maxFiles) break; + + String fileName = file.name(); + if (fileName.endsWith(".wmt")) { + _display.textStatus("SSL UPLOADING..."); + if (sendFile(String(file.path()), caCert)) { + String oldPath = String(file.path()); + String newPath = oldPath; + newPath.replace(".wmt", ".sent"); + file.close(); + SD.rename(oldPath.c_str(), newPath.c_str()); + sentCount++; + } + } + file.close(); + } + dir.close(); + } + folder.close(); + } + root.close(); +} + +bool Uploader::sendFile(String filePath, String& caCert) { + File f = SD.open(filePath, FILE_READ); + if (!f) return false; + + WiFiClientSecure client; + client.setCACert(caCert.c_str()); + HTTPClient http; + bool success = false; + + if (http.begin(client, "https://api.pwojtaszek.codes/upload")) { + http.addHeader("Content-Type", "application/octet-stream"); + http.addHeader("X-File-Name", filePath); + http.addHeader("X-Device-ID", config.hostname); + + int code = http.sendRequest("POST", &f, f.size()); + if (code == 200) { + ESP_LOGI("UPLOADER", "Upload OK: %s", filePath.c_str()); + success = true; + } else { + ESP_LOGE("UPLOADER", "Error: %d", code); + } + http.end(); + } + f.close(); + return success; +} + +String Uploader::loadCACert(const char* path) { + File f = SD.open(path); + if (!f) return ""; + String cert = f.readString(); + f.close(); + return cert; +} \ No newline at end of file diff --git a/src/Watchdog.cpp b/src/Watchdog.cpp new file mode 100644 index 0000000..5b7c5c6 --- /dev/null +++ b/src/Watchdog.cpp @@ -0,0 +1,81 @@ +#include "Watchdog.h" + +// Wykrywanie środowiska +#if defined(ESP_PLATFORM) || defined(ESP32) + #include + #define WDOG_HAS_ESP 1 +#else + #define WDOG_HAS_ESP 0 +#endif + +namespace Watchdog { + +static bool s_initialized = false; + +bool init(int timeout_seconds, bool panic_on_trigger) { +#if WDOG_HAS_ESP + if (s_initialized) return true; + esp_err_t err = esp_task_wdt_init(timeout_seconds, panic_on_trigger); + if (err == ESP_OK || err == ESP_ERR_INVALID_STATE) { + // ESP_ERR_INVALID_STATE: już zainicjalizowany — traktujemy jako OK + s_initialized = true; + return true; + } + return false; +#else + (void)timeout_seconds; (void)panic_on_trigger; + s_initialized = true; // no-op, aby nie blokować wywołań w kodzie + return true; +#endif +} + +bool addThisTask() { +#if WDOG_HAS_ESP + if (!s_initialized) return false; + esp_err_t err = esp_task_wdt_add(nullptr); // nullptr = bieżący task + return (err == ESP_OK || err == ESP_ERR_INVALID_STATE); +#else + return true; // no-op +#endif +} + +bool removeThisTask() { +#if WDOG_HAS_ESP + if (!s_initialized) return false; + esp_err_t err = esp_task_wdt_delete(nullptr); // bieżący task + return (err == ESP_OK || err == ESP_ERR_INVALID_STATE); +#else + return true; // no-op +#endif +} + +void feed() { +#if WDOG_HAS_ESP + esp_task_wdt_reset(); +#else + // no-op +#endif +} + +bool setTimeout(int timeout_seconds) { +#if WDOG_HAS_ESP + if (!s_initialized) { + // jeśli ktoś nie zainicjalizował — zrób to teraz + return init(timeout_seconds, true); + } + // W ESP-IDF/Arduino brak prostego API na „live update” — re-init: + esp_err_t err = esp_task_wdt_deinit(); + (void)err; // nie każdy port raportuje OK/INVALID_STATE spójnie + s_initialized = false; + return init(timeout_seconds, true); +#else + (void)timeout_seconds; + return true; // no-op +#endif +} + +bool isActive() { + return s_initialized; +} + +} // namespace Watchdog diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..396c169 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,433 @@ +#include +#include "Watchdog.h" +#include +#include +#include +#include +#include +#include +#include +#include "ADXL345FastSPI.h" +#include "RTClib.h" +#include +#include +#include +#include +#include +#include +#include // Dodano moduł wysyłki + +#define WDT_TIMEOUT 60 // Czas watchdoga do restartu +#define MAX_ADXL345_SENSORS 4 // Maksymalna ilość podłączanych sensorów + +SPIClass SPI_ADXL(FSPI); // SPI2 (VSPI) +SPIClass SPI_SD(HSPI); // SPI3 (HSPI) + +float x, y, z = 0; // Dane odczytane z akcelerometru +String name; + +bool isRebootRequired = false; +bool isAccelExists = false; // Czy istnieje jakiś podłączony do SPI czujnik +bool testingNow = false; // Czy trwa test (gdy tak, trzeba wszystko inne wyłączyć) +bool runMeasure = false; // czy włączyć pomiar? + +// Buttony: 5, 6, 7 +// piny SPI CS dla ASXL345 +const uint8_t csPins[MAX_ADXL345_SENSORS] = {9, 10, 14, 21}; + +long licznik = 0; + +ConfigManager configManager; +RTC_DS3231 rtc; + +ADXL345FastSPI adxl(csPins, MAX_ADXL345_SENSORS); +Display display(rtc, 0x27, 20, 4); +Settings settings(display, rtc); // obsługa przycisków +WiFiManager wifi; + +DataCapture capture(adxl, display, rtc, SD, 8192); // NEW !!! MEASURE!!! + +// Inicjalizacja Uploadera +Uploader uploader(display); + +Thread wifiTestThread = Thread(); // Cykliczny test WiFi +Thread offlineThread = Thread(); // Jeśli offline +Thread measureThread = Thread(); // Pomiar i zapis na SD +Thread uploadThread = Thread(); // Wątek wysyłki SSL + +//////// PROTOTYPY ///////////////// +void setup(); +void loop(); +void reboot(); +void resetBtnClick(); +void checkWiFi(); +void showOfflineScreen(); +void measure(); +void runUploader(); +void toogleMode(); +void settingsDevice(); +void showError(String err); + +/* ******************* SETUP() ************************* */ +void setup() { + Serial.begin(115200); + delay(500); + // przy USB-CDC warto poczekać chwilę na enumerację (z timeoutem): + unsigned long t0 = millis(); + while (!Serial && millis()-t0 < 2000) { delay(10); } + settings.begin(); + esp_log_level_set("*", ESP_LOG_INFO); // _ERROR, _WARN, _INFO, _DEBUG, _VERBOSE + delay(500); // było 1000 + + ESP_LOGI(TAG_MAIN, "----------------------"); + ESP_LOGI(TAG_MAIN, "WMT Stalowa Wola A.Chmielowiec & L.Klich"); + ESP_LOGI(TAG_MAIN, "Rejestrator parametrow"); + ESP_LOGI(TAG_MAIN, "Firmware: %s", VERSION); + ESP_LOGI(TAG_MAIN, "----------------------"); + ESP_LOGI(TAG_MAIN, "ESP32 model: %s Rev %d", ESP.getChipModel(), ESP.getChipRevision()); + ESP_LOGI(TAG_MAIN, "Chip cores: %d", ESP.getChipCores()); + + // Inicjalizacja Watchdoga na 5 sek, panic_on_trigger = true + if (Watchdog::init(25, true)) { + ESP_LOGI(TAG_MAIN, "Watchdog init ok."); + } else { + ESP_LOGE(TAG_MAIN, "Watchdog init error."); + } + + Wire.begin(PIN_SDA, PIN_SCL, 100000); + scanI2C(); // Skanowanie magistrali I2C na UART (RTC-0x68, LCD-0x27) + + configManager.begin(); // konfiguracja EEPROM urządzenia + + // Test LCD I2C + if (!isI2CDevPresent(0x27)) { + ESP_LOGE(TAG_MAIN, "LCD 0x27 wire error!"); + } + ESP_LOGI(TAG_MAIN, "Display init"); + if (!display.begin()) { + ESP_LOGE(TAG_MAIN, "Display init failed"); + while (true) delay(1000); + } + ESP_LOGI(TAG_MAIN, "Display OK"); + display.welcomeScreen(); + delay(1000); + + // Przycisk reset w przypadku factory reset + if(settings.isBtnReset()) resetBtnClick(); + + // MCU Info + ESP_LOGI(TAG_MAIN, "MCU info"); + display.textStatus(ESP.getChipModel()); + delay(500); + char buf[20]; + sprintf(buf, "Freq: %d MHz", ESP.getCpuFreqMHz()); + display.textStatus(buf); + delay(500); + + // Test PSRAM + display.textStatus("PSRAM:"); + ESP_LOGI(TAG_MAIN, "PSRAM init"); + if (!psramFound()) { + ESP_LOGE(TAG_MAIN, "PSRAM not found"); + display.print("FAILED"); + while (true) delay(1000); + } + display.print("OK"); + delay(500); + + // Test RTC + ESP_LOGI(TAG_MAIN, "RTC test"); + if (!isI2CDevPresent(0x68)) { + ESP_LOGE(TAG_MAIN, "RTC 0x68 wire error!"); + display.textStatus("RTC wire error!"); + while (true) delay(1000); + } + display.textStatus("RTC:"); + ESP_LOGI(TAG_MAIN, "RTC init"); + if (!rtc.begin()) { + display.print("FAILED"); + ESP_LOGE(TAG_MAIN, "Can't find RTC"); + while (true) delay(1000); + } else { + display.print("OK"); + ESP_LOGI(TAG_MAIN, "RTC OK"); + if (rtc.lostPower()) { + ESP_LOGE(TAG_MAIN, "RTC power lost! Set clock"); + display.textStatus("RTC: SET TIME"); + delay(1000); + settings.setTimeRTC(); + } + } + delay(500); + + // Karta SD + ESP_LOGI(TAG_MAIN, "SD Card init"); + ESP_LOGI(TAG_MAIN, "SPI2 SD SCK: %d, MISO: %d, MOSI: %d, CS: %d", SD_SCK, SD_MISO, SD_MOSI, SD_CS); + SPI_SD.begin(SD_SCK, SD_MISO, SD_MOSI); + display.textStatus("SD CARD:"); + while(!SD.begin(SD_CS, SPI_SD, 4000000)) { + ESP_LOGE(TAG_MAIN, "SD mount failed"); + display.print("FAILED"); + delay(4000); + display.textStatus("SD CARD:"); + delay(500); + } + ESP_LOGI(TAG_MAIN, "SD Card OK"); + display.print("OK"); + + ESP_LOGI(TAG_MAIN, "ADXL345 SPI3 SCK: %d, MISO: %d, MOSI: %d", CLK_ADSX, MISO_ADSX, MOSI_ADSX); + SPI_ADXL.begin(CLK_ADSX, MISO_ADSX, MOSI_ADSX); + + // Inicjalizacja ADXL345 + ESP_LOGI(TAG_MAIN, "ADXL345 init"); + display.textStatus("ADXL345: "); + if(!adxl.begin(&SPI_ADXL, 5000000, ADXL345FastSPI::RATE_3200HZ, ADXL345FastSPI::RANGE_16G, 1)){ + ESP_LOGE(TAG_MAIN, "ADXL345 Error"); + display.print("FAILED"); + isAccelExists = false; + uint8_t counter = 0; + while(counter<5){ counter++; delay(1000); } + display.clear(); + display.textCenter(0, "PLEASE CONNECT"); + display.textCenter(1, "SENSOR MODULE"); + display.textCenter(2, "ANY KEY RESTART"); + display.textCenter(3, "GURU MEDITATION"); + while(true){ + if(digitalRead(BTN_UP) == LOW) reboot(); + if(digitalRead(BTN_OK) == LOW) reboot(); + if(digitalRead(BTN_DOWN) == LOW) reboot(); + } + } else { + display.print(String(adxl.size())); + ESP_LOGI(TAG_MAIN, "ADXL345 OK: %d COUNT", adxl.size()); // liczba wykrytych + isAccelExists = true; + } + + DateTime now = rtc.now(); + ESP_LOGI(TAG_MAIN, "RTC TIME: %d:%d:%d %d:%d:%d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second()); + delay(1000); + + if(config.connect){ + display.textStatus("WiFi Connect..."); + wifi.begin(); + + int retry_count = 0; + while (WiFi.status() != WL_CONNECTED && retry_count < 60) { + delay(500); + retry_count++; + Watchdog::feed(); + if(retry_count % 10 == 0) display.print("."); + } + + if (WiFi.status() != WL_CONNECTED) { + ESP_LOGW(TAG_MAIN, "WiFi Failed. Starting Configuration Portal..."); + wifi.startConfigPortal(display); + } else { + ESP_LOGI(TAG_MAIN, "WiFi OK"); + wifiTestThread.onRun(checkWiFi); + wifiTestThread.setInterval(5000); + + uploadThread.onRun(runUploader); + uploadThread.setInterval(30000); + } + } else { + offlineThread.onRun(showOfflineScreen); + offlineThread.setInterval(1000); + } + + measureThread.onRun(measure); + measureThread.setInterval(config.pause); + + if (Watchdog::addThisTask()) { + ESP_LOGI(TAG_MAIN, "Added main task to Watchdog"); + } + + ESP_LOGI(TAG_MAIN, "System ready"); + display.clear(); +} + +void runUploader() { + if (!testingNow) { + uploader.processQueue(3); + } +} + +void resetBtnClick(){ + uint8_t counter = 10; + ESP_LOGI(TAG_MAIN, "RESET BTN to 10 sec."); + while(settings.isBtnReset()){ + Serial.print(counter); Serial.print(","); + counter--; + delay(1000); + display.clear(); + display.textCenter(0,"WARNING!!!"); + display.textCenter(1, "Factory reset"); + display.textCenter(2, "keep holding for"); + display.textStatus(String(counter).c_str()); + if (counter <= 1) { + display.clear(); + display.textCenter(0, "RESET CONFIG"); + display.textCenter(1, "release key"); + ESP_LOGI(TAG_MAIN, "RESET CONFIG"); + while(settings.isBtnReset()){;} + configManager.resetToDefaults(); + display.textStatus("RESTARTING!"); + delay(500); + isRebootRequired = true; + } else { + isRebootRequired = true; + } + } +} + +void showError(String err){ + Watchdog::feed(); + display.clear(); + display.println(err); + ESP_LOGE(TAG_MAIN, "%s", err.c_str()); + delay(3000); +} + +void checkWiFi(){ + Watchdog::feed(); + bool isConnected = WiFi.isConnected(); + String ip = WiFi.localIP().toString(); + wifi.checkWiFiConnection(); + display.updateBarWiFi(WiFi.RSSI(), isConnected); + display.updateNetwork(ip, isConnected); +} + +void showOfflineScreen(){ + if((!config.connect) && (!testingNow)) { + bool isGB; + Watchdog::feed(); + float freeSpace = capture.freeSpaceFloat(&isGB); + display.displayOffline(testingNow, adxl.connectedSensorsCount(), capture.freeSpaceFloat(), licznik, false); + } +} + +void reboot() { + display.clear(); + display.textCenter(1, "SYSTEM"); + display.textCenter(2, "RESTARTING"); + #if defined(ARDUINO_RASPBERRY_PI_PICO) + watchdog_enable(1, 1); + while (true); + #endif + #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) + ESP_LOGI(TAG_MAIN, "RESTART"); + ESP.restart(); + #endif + #if defined(ARDUINO_ARCH_STM32) + NVIC_SystemReset(); + #endif +} + +void measure(){ + Watchdog::feed(); + testingNow = true; + offlineThread.enabled = false; + DateTime now = rtc.now(); + ESP_LOGI(TAG_MAIN, "RTC TIME: %d:%d:%d %d:%d:%d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second()); + char bdate[15]; char btime[15]; + snprintf(bdate, sizeof(bdate), "%04d-%02d-%02d", now.year(), now.month(), now.day()); + snprintf(btime, sizeof(btime), "%02d:%02d:%02d", now.hour(), now.minute(), now.second()); + display.initMeasure(config.measure, testingNow, runMeasure, config.pause, config.duration, config.connect, licznik, bdate, btime); + ESP_LOGI(TAG_MAIN, "MEASURE RUNNING"); + + capture.captureAuto(config.duration, "/"); + testingNow = false; + Watchdog::feed(); + if(!capture.isExit){ + capture.printLastFileInfoSerial(); + capture.readHeaderAndPrint(capture.generateNextFilename().c_str()); + ESP_LOGI(TAG_MAIN, "MEASURE FINISH"); + } else { + ESP_LOGI(TAG_MAIN, "MEASURE INTERRUPT"); + } + runMeasure = false; + ESP_LOGI(TAG_MAIN, "DISPLAY Offline"); + display.displayOffline(testingNow, adxl.connectedSensorsCount(), capture.freeSpaceFloat(), licznik, true); + offlineThread.enabled = true; +} + +void toogleMode(){ + config.measure = !config.measure; + configManager.saveConfig(); + display.textStatus("Mode changed"); + delay(500); + display.textStatus(""); +} + +void settingsDevice(){ + settings.setConfigDevice(); + configManager.saveConfig(); + settings.finishConfigDevice(); +} + +///////////// LOOP //////////////////////////////////////////// +void loop() { + if (settings.isPressed(2)){ + Watchdog::feed(); + if(runMeasure) { + capture.isExit = true; + runMeasure = false; + display.textStatus("Stopped"); + delay(3000); + } else runMeasure = true; + if(runMeasure) ESP_LOGI(TAG_MAIN, "BTN MEASURE: START"); else ESP_LOGI(TAG_MAIN, "BTN MEASURE: STOP"); + + Watchdog::feed(); + delay(300); + if (runMeasure) measureThread.run(); +} + + if(settings.isPressed(3)) settingsDevice(); // DOWN + if(settings.isPressed(1)) toogleMode(); // UP + + if(testingNow) { + if((runMeasure) && (settings.isPressed(2))){ + runMeasure = false; + display.textStatus("STOPPING. WAIT"); + } + return; + } + + if(wifiTestThread.shouldRun() && (config.connect)) + wifiTestThread.run(); + + if(uploadThread.shouldRun() && (config.connect)) + uploadThread.run(); + + if(offlineThread.shouldRun() && (!config.connect)){ + offlineThread.run(); + } + + if(!isAccelExists) { + ESP_LOGE(TAG_MAIN, "ADXL module error:halt"); + display.clear(); + delay(500); + display.textCenter(0, "SENSORS"); + display.textCenter(1, "NOT EXISTS"); + display.textCenter(2, "SYSTEM STOPPED"); + Watchdog::feed(); + delay(2000); + } else { + if(measureThread.shouldRun() && (config.measure) && (!runMeasure)){ + Watchdog::feed(); + ESP_LOGI(TAG_MAIN, "Measure thread run"); + measureThread.run(); + } + } + + if (isRebootRequired) { + ESP_LOGI(TAG_MAIN, "Reboot required"); + Watchdog::feed(); + delay(1000); + reboot(); + } + + Watchdog::feed(); + delay(20); + licznik ++; +} \ No newline at end of file From 66475edad4179b9219117c8776b79686ca7101c5 Mon Sep 17 00:00:00 2001 From: Victus Date: Sun, 10 May 2026 20:46:23 +0200 Subject: [PATCH 2/5] Dodanie Captive Portal z logarytmami --- include/ADXL345FreshSPI.h | 2 +- include/APIClient.h | 19 +++ include/Display.h | 19 ++- include/Measure.h | 8 +- include/Network.h | 34 ++--- include/Settings.h | 2 +- include/UploadManager.h | 31 ++++ include/Version.h | 2 +- src/ADXL345FastSPI.cpp | 9 +- src/APIClient.cpp | 124 +++++++++++++++ src/Config.cpp | 16 +- src/Display.cpp | 313 +++++++++++++++++++++++--------------- src/Measure.cpp | 14 +- src/Network.cpp | 239 ++++++++++++++++++----------- src/Settings.cpp | 6 +- src/UploadManager.cpp | 117 ++++++++++++++ src/main.cpp | 137 ++++++++++------- 17 files changed, 776 insertions(+), 316 deletions(-) create mode 100644 include/APIClient.h create mode 100644 include/UploadManager.h create mode 100644 src/APIClient.cpp create mode 100644 src/UploadManager.cpp diff --git a/include/ADXL345FreshSPI.h b/include/ADXL345FreshSPI.h index 9c0b34e..2cdad53 100644 --- a/include/ADXL345FreshSPI.h +++ b/include/ADXL345FreshSPI.h @@ -21,7 +21,7 @@ public: ADXL345FreshSPI() = default; - // --- Init (SPI only) --- + // --- Init (SPI) --- // Uwaga: ADXL345 wymaga SPI MODE3, zegar ≤ ~5 MHz. bool begin(SPIClass *spi, uint8_t csPin, uint32_t clockHz = 5000000); diff --git a/include/APIClient.h b/include/APIClient.h new file mode 100644 index 0000000..a0a2c2b --- /dev/null +++ b/include/APIClient.h @@ -0,0 +1,19 @@ +#ifndef APICLIENT_H +#define APICLIENT_H + +#include +#include +#include +#include +#include +#include "Config.h" +#include "Watchdog.h" + +class APIClient { +public: + APIClient(); + + bool uploadMeasurement(const String& filePath); +}; + +#endif diff --git a/include/Display.h b/include/Display.h index 8289bb6..62fb6ad 100644 --- a/include/Display.h +++ b/include/Display.h @@ -60,10 +60,6 @@ public: void setCursor(int16_t x, int16_t y); void showAccel(float a, float b, float c); void displayOnOffM(bool measure, String myDir, String myFile); - - // Metoda sterująca ikoną SSL (dodana do klasy) - void setSSLStatus(bool active); - void initMeasure( bool measure, // czy pomiar ciągły? bool run, // czy uruchomiony? @@ -78,9 +74,20 @@ public: private: LiquidCrystal_I2C *_lcd; RTC_DS3231 &rtc_; - uint8_t _address; uint8_t _columns; uint8_t _rows; + uint8_t _address; + + // Poprzednie + uint16_t oyear; + uint8_t omonth; + uint8_t oday; + uint8_t ohour; + uint8_t omin; + uint8_t osec; + float ospace; + uint8_t oadxlcnt; + bool omode; }; -#endif \ No newline at end of file +#endif diff --git a/include/Measure.h b/include/Measure.h index 6dbdce5..78490d6 100644 --- a/include/Measure.h +++ b/include/Measure.h @@ -17,8 +17,6 @@ static constexpr uint32_t SPI_HZ = 5000000; // 5 MHz (MODE3) static constexpr float ODR_HZ = 3200.0f; // maks. ODR // Zakres ustawiany w main.cpp przez ADXL345FastSPI::begin(..., RANGE_2G, ...) -//extern Display display; - struct FileInfo { String path; // np. "/3/00000057.wmt" uint64_t size; // bajty @@ -31,7 +29,7 @@ struct SpaceInfo { }; class DataCapture { - // --- Nagłówek pliku WMT (jak w oryginale) --- + // --- Nagłówek pliku WMT --- struct FileHeader { char magic[3]; // "WMT" uint16_t version; // 1 @@ -42,7 +40,7 @@ class DataCapture { } __attribute__((packed)); public: - // --- Rekord próbki (jak w oryginale) --- + // --- Rekord próbki --- struct Sample { uint32_t offset; // µs od startu akwizycji (wspólny dla ramki) uint8_t sensor_id; // 0..3 @@ -105,8 +103,6 @@ private: return ispress; } // szybkidigitalRead : czy BTN stop??? - - // Helpers (zachowane z Twojej wersji) public: bool isAllDigits(const char *s); uint32_t toUint(const char *s); diff --git a/include/Network.h b/include/Network.h index 74068e2..736b17e 100644 --- a/include/Network.h +++ b/include/Network.h @@ -9,13 +9,12 @@ #include #include #include + #include #elif defined(ESP8266) #include #include - #include + #include #endif - -#include // Dodano dla Captive Portal //#include //#include //#include @@ -28,11 +27,6 @@ #include // OTA -#include "Display.h" // Dodano dla obsługi komunikatów na LCD - -extern Config config; // Deklaracja zewnętrznej struktury config -extern ConfigManager configManager; // Deklaracja zewnętrznego managera (to naprawi błąd) - class WiFiManager { public: WiFiManager(); @@ -50,11 +44,11 @@ class WiFiManager { int rssiToPercent(int rssi); // rssi na procenty int8_t getRSSI(); - /** - * NOWA METODA: Portal konfiguracyjny (Captive Portal) - * Uruchamiany automatycznie przy braku połączenia. - */ - void startConfigPortal(Display &display); + void handleClient(); + void startCaptivePortal(); + void handleRoot(); + void handleSave(); + void handleNotFound(); /** * Aktualizacja systemu przez internet z adresu config.updateUrl. @@ -75,18 +69,10 @@ class WiFiManager { private: bool isAccessPoint = false; - - // Obiekty serwerów dla Portalu (ESP32/ESP8266) -#ifdef ESP32 + bool captivePortalActive = false; WebServer server{80}; -#elif defined(ESP8266) - ESP8266WebServer server{80}; -#endif DNSServer dnsServer; - void handleRoot(); - void handleSave(); + int expectedCaptchaAnswer = 0; }; -extern WiFiManager wifi; - -#endif // WIFIMANAGER_H \ No newline at end of file +#endif // WIFIMANAGER_H diff --git a/include/Settings.h b/include/Settings.h index d8711b8..0653c68 100644 --- a/include/Settings.h +++ b/include/Settings.h @@ -24,7 +24,7 @@ class Settings { // bool is1and3(); bool isBtnReset(); - + bool isSetClock(); bool readBtnUp() { return digitalRead(BTN_UP) == LOW; } bool readBtnOk() { return digitalRead(BTN_OK) == LOW; } bool readBtnDown() { return digitalRead(BTN_DOWN) == LOW; } diff --git a/include/UploadManager.h b/include/UploadManager.h new file mode 100644 index 0000000..db40a5b --- /dev/null +++ b/include/UploadManager.h @@ -0,0 +1,31 @@ +#ifndef UPLOADMANAGER_H +#define UPLOADMANAGER_H + +#include +#include +#include +#include "APIClient.h" +#include "RTClib.h" +#include "Measure.h" + +class UploadManager { +public: + UploadManager(APIClient& client, RTC_DS3231& rtc, DataCapture& capture); + + // Call this to upload a specific file immediately + void uploadFile(const String& filePath); + + // Call this in the background when WiFi is connected + void processPendingUploads(); + +private: + APIClient& apiClient; + RTC_DS3231& rtc_; + DataCapture& capture_; + + bool isAlreadyUploaded(const String& filePath); + void appendLog(const String& filePath, const String& status); + String getCurrentTimestamp(); +}; + +#endif diff --git a/include/Version.h b/include/Version.h index d4d6536..d06b09f 100644 --- a/include/Version.h +++ b/include/Version.h @@ -1,7 +1,7 @@ #ifndef VERSION_H #define VERSION_H -#define VERSION "1.3.2" +#define VERSION "1.3.4.1" // 1: graphical 128x64, 2: LCD I2C Text 4x20 #define LCD_TYPE 2 diff --git a/src/ADXL345FastSPI.cpp b/src/ADXL345FastSPI.cpp index 07efeb1..5cd48d1 100644 --- a/src/ADXL345FastSPI.cpp +++ b/src/ADXL345FastSPI.cpp @@ -1,4 +1,5 @@ #include "ADXL345FastSPI.h" +#include "Watchdog.h" static const char *TAG_FRESH = "ADXLFAST"; @@ -43,8 +44,14 @@ bool ADXL345FastSPI::availableAll() { uint8_t ADXL345FastSPI::readAlignedOnce(int16_t* x, int16_t* y, int16_t* z, uint32_t* ts_us){ // 1) Czekaj aż każdy ma ≥1 próbkę (DATA_READY => FIFO>0) + uint32_t start_wait = millis(); while (!availableAll()) { - // micro-spin; w razie potrzeby można dodać yield() / feed watchdog poza hot-path + if (millis() - start_wait > 100) { + ESP_LOGE(TAG_FRESH, "Timeout waiting for sensors data!"); + return 0; + } + Watchdog::feed(); + yield(); } // 2) Zdejmij najstarszą z każdego sensora diff --git a/src/APIClient.cpp b/src/APIClient.cpp new file mode 100644 index 0000000..a87d042 --- /dev/null +++ b/src/APIClient.cpp @@ -0,0 +1,124 @@ +#include "APIClient.h" +#include +#include + +static const char *TAG_API = "API"; + +APIClient::APIClient() {} + +bool APIClient::uploadMeasurement(const String &filePath) { + if (WiFi.status() != WL_CONNECTED) { + ESP_LOGE(TAG_API, "No WiFi connection."); + return false; + } + + File file = SD.open(filePath, FILE_READ); + if (!file) { + ESP_LOGE(TAG_API, "Failed to open file %s", filePath.c_str()); + return false; + } + + // Wyciągnij samą nazwę pliku (bez ścieżki) + String filename = filePath; + int slashIndex = filename.lastIndexOf('/'); + if (slashIndex >= 0) { + filename = filename.substring(slashIndex + 1); + } + + // multipart/form-data boundary + String boundary = "----ESP32WMTBoundary"; + String head = "--" + boundary + "\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"" + + filename + "\"\r\n" + + "Content-Type: application/octet-stream\r\n\r\n"; + String tail = "\r\n--" + boundary + "--\r\n"; + + size_t fileSize = file.size(); + size_t totalLength = head.length() + fileSize + tail.length(); + + // Połączenie z hostem + WiFiClient client; + String host = String(config.restURL); + int port = config.restPort; + + // Usuń prefiks protokołu z hosta + host.replace("http://", ""); + host.replace("https://", ""); + + ESP_LOGI(TAG_API, "Connecting to %s:%d for upload", host.c_str(), port); + if (!client.connect(host.c_str(), port)) { + ESP_LOGE(TAG_API, "Connection failed to host %s:%d", host.c_str(), port); + file.close(); + return false; + } + + ESP_LOGI(TAG_API, "Uploading %s (%d bytes)", filePath.c_str(), fileSize); + + // --- HTTP Request --- + // Endpoint wg dokumentacji API IoT + client.println("POST /api/v1/measurements/measurements/upload HTTP/1.1"); + client.println("Host: " + host + ":" + String(port)); + + // Basic Auth: base64(serial:password) + String auth = String(config.restUser) + ":" + String(config.restPass); + String authBase64 = base64::encode(auth); + client.println("Authorization: Basic " + authBase64); + + client.println("Content-Length: " + String(totalLength)); + client.println("Content-Type: multipart/form-data; boundary=" + boundary); + client.println("Connection: close"); + client.println(); + + // Multipart body: head + file data + tail + client.print(head); + + uint8_t buffer[2048]; + while (file.available()) { + size_t len = file.read(buffer, sizeof(buffer)); + client.write(buffer, len); + Watchdog::feed(); + } + + client.print(tail); + file.close(); + + // --- Parsowanie odpowiedzi --- + int httpCode = 0; + String responseLine; + unsigned long timeout = millis(); + while (client.connected() && millis() - timeout < 15000) { + if (client.available()) { + responseLine = client.readStringUntil('\n'); + responseLine.trim(); + if (responseLine.startsWith("HTTP/1.1 ")) { + httpCode = responseLine.substring(9, 12).toInt(); + } + if (responseLine.length() == 0) + break; + } + Watchdog::feed(); + } + + String responseBody = ""; + while (client.available()) { + responseBody += client.readString(); + } + client.stop(); + + // --- Obsługa kodów odpowiedzi wg dokumentacji API --- + if (httpCode == 201) { + ESP_LOGI(TAG_API, "Upload successful: 201 Created"); + return true; + } else if (httpCode == 401) { + ESP_LOGE(TAG_API, "Unauthorized 401: Wrong serial or password!"); + } else if (httpCode == 403) { + ESP_LOGE(TAG_API, "Forbidden 403: Device not authorized or user tried upload."); + } else if (httpCode == 400) { + ESP_LOGE(TAG_API, "Bad Request 400: %s", responseBody.c_str()); + } else { + ESP_LOGE(TAG_API, "Upload failed with code %d. Response: %s", httpCode, + responseBody.c_str()); + } + + return false; +} diff --git a/src/Config.cpp b/src/Config.cpp index 7d56d82..f17182b 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -22,7 +22,7 @@ void ConfigManager::begin() { // Check is EEPROM is empty bool ConfigManager::isEEPROMEmpty() { - if (EEPROM.read(0) != 251){ + if (EEPROM.read(0) != 253){ ESP_LOGI(TAG_CONF, "EEPROM is new!"); return true; } else { @@ -40,7 +40,7 @@ void ConfigManager::readConfig() { void ConfigManager::saveConfig() { ESP_LOGI(TAG_CONF, "SAVE CONFIG"); EEPROM.put(1, config); - EEPROM.write(0, 251); + EEPROM.write(0, 253); if (EEPROM.commit()) { ESP_LOGI(TAG_CONF, "Config saved"); } else { @@ -77,15 +77,15 @@ void ConfigManager::resetToDefaults() { strcpy(config.user, "admin"); strcpy(config.pass, "admin"); strcpy(config.ntp, "pl.pool.ntp.org"); - config.connect = 0; // urządzenie połączone z siecią lub 0 offline + config.connect = 1; // urządzenie połączone z siecią lub 0 offline config.measure = 1; // włącz automatyczny pomiar co x sekunt (pause) config.duration = 5; // czas trwania pomiaru 5 sekund config.pause = 10000; // odstęp pomiędzy pomiarami w ms //strcpy(config.ntp, "0.pl.pool.ntp.org"); - strcpy(config.restURL, "http://62.93.60.19/accels/api1"); - config.restPort = 8000; - strcpy(config.restUser, "wmt"); - strcpy(config.restPass, "Zaq12wsx"); + strcpy(config.restURL, "http://62.93.60.19"); + config.restPort = 5004; + strcpy(config.restUser, "SN001234ABCD56789012"); + strcpy(config.restPass, "device001"); strcpy(config.S0, "ACCEL1"); strcpy(config.S1, "ACCEL2"); strcpy(config.S2, "ACCEL3"); @@ -95,7 +95,7 @@ void ConfigManager::resetToDefaults() { strcpy(config.S6, "ACCEL7"); strcpy(config.S7, "ACCEL8"); EEPROM.put(1, config); - EEPROM.write(0, 251); + EEPROM.write(0, 253); saveConfig(); readConfig(); isRebootRequired = true; diff --git a/src/Display.cpp b/src/Display.cpp index 597b154..c6ce2de 100644 --- a/src/Display.cpp +++ b/src/Display.cpp @@ -3,9 +3,6 @@ static const char *DISP = "display"; -// Flaga informująca o aktywnym trybie SSL -static bool _sslActive = false; - Display::Display(RTC_DS3231 &rtc, uint8_t address, uint8_t columns, uint8_t rows):rtc_(rtc) { _lcd = new LiquidCrystal_I2C(address, columns, rows); _address = address; @@ -28,9 +25,6 @@ bool Display::begin(TwoWire *wire) { return true; } -void Display::setSSLStatus(bool active) { - _sslActive = active; -} void Display::initMeasure(bool measure, bool run, bool runmes, uint16_t pause, uint8_t duration, bool connect, long counter, String gdate, String gtime) { @@ -43,54 +37,97 @@ void Display::initMeasure(bool measure, bool run, bool runmes, uint16_t pause, u String type = "Sample:" + String(duration) + "s Pause:" + String(pause/1000)+"s"; textCenter(2, type.c_str()); textCenter(3, String("Time:" + gtime).c_str()); -} - -void Display::welcomeScreen() { - _lcd->clear(); - _lcd->setCursor(0, 0); - _lcd->print(" VICTUS SYSTEM "); - _lcd->setCursor(0, 1); - _lcd->print(" VIBRATION LOGGER "); - _lcd->setCursor(0, 2); - _lcd->print(" V " + String(VERSION) + " "); - _lcd->setCursor(0, 3); - _lcd->print("WMT Stalowa Wola '25"); -} - -void Display::clearRow(uint16_t line) { - _lcd->setCursor(0, line); - for (int i = 0; i < _columns; i++) { - _lcd->print(" "); - } -} - -void Display::textCenter(uint8_t line, const char *txt) { - int len = strlen(txt); - int pos = (_columns - len) / 2; - if (pos < 0) pos = 0; - _lcd->setCursor(pos, line); - _lcd->print(txt); -} - -void Display::text(uint8_t line, const char *text) { - _lcd->setCursor(0, line); - _lcd->print(text); -} - -void Display::textStatus(const char *text) { - clearRow(3); - _lcd->setCursor(0, 3); - _lcd->print(text); + textStatus("initialisation"); } void Display::clear() { - _lcd->clear(); + _lcd->clear(); } -void Display::mainScreen() { - // Twoja logika mainScreen - _lcd->clear(); - textCenter(1, "MAIN SCREEN"); +void Display::textCenter(uint8_t line, const char *txt){ + if (line >= _rows) { + ESP_LOGE(DISP, "Line >= max rows!"); + return; + } + _lcd->setCursor(0, line); + int len = strlen(txt); + if (len < _columns) { + int pad = (_columns - len) / 2; + for (int i = 0; i < pad; i++) { + _lcd->print(" "); + } + } + _lcd->print(txt); +} + +void Display::text(uint8_t line, const char *text) { + if (line >= _rows) { + ESP_LOGE(DISP, "Line >= max rows!"); + return; + } + _lcd->setCursor(0, line); + _lcd->print(text); +} + +void Display::welcomeScreen() { + ESP_LOGI(DISP, "Info screen"); + clear(); + textCenter(0, "** Vibra Dude **"); + textCenter(1, "WMT Stalowa Wola"); + String firm = String(VERSION); + firm.trim(); + textCenter(2, firm.c_str()); + //_lcd->print(VERSION); +} + +void Display::mainScreen(){ + ESP_LOGI(DISP, "Main screen"); + clear(); + textCenter(0, "** Vibra Dude **"); + textCenter(1, "WMT Stalowa Wola"); + //String firm = String(VERSION).trim(); + //textCenter(2, firm.c_str()); + //_lcd->print(VERSION); + ESP_LOGI(DISP, "Finish main screen"); +} + +void Display::clearRow(uint16_t line){ + _lcd->setCursor(0, line); + _lcd->print(" "); + _lcd->setCursor(0, line); +} + +void Display::textStatus(const char *text){ + clearRow(3); + _lcd->setCursor(0, 3); + _lcd->print(text); +} + + +void Display::updateNetwork(String ip, bool connected) { + // przykładowa prosta implementacja: + // clear(); + // text(0, connected ? "NET: OK" : "NET: OFF"); + // _lcd->setCursor(0, 1); + // _lcd->print(ip); +} + +void Display::updateBarWiFi(long signal, bool connected) { + // tu możesz wykorzystać np. init_bargraph / draw_horizontal_graph + // (void)signal; + // (void)connected; +} + +void Display::textStyle(const uint8_t st, String text) { + // miejsce na różne style tekstu + //(void)st; +// clear(); + //text(0, text.c_str()); +} + +void Display::message(String text) { + // clear(); + //text(0, text.c_str()); } void Display::print(String text) { @@ -99,77 +136,96 @@ void Display::print(String text) { void Display::println(String text) { _lcd->print(text); + _lcd->print("\n"); } void Display::setCursor(int16_t x, int16_t y) { - _lcd->setCursor(x, y); -} - -void Display::updateNetwork(String ip, bool connected) { - _lcd->setCursor(0, 0); - if (connected) { - _lcd->print("IP:" + ip); - } else { - _lcd->print("IP: DISCONNECTED "); - } -} - -void Display::updateBarWiFi(long signal, bool connected) { - _lcd->setCursor(17, 0); - if (connected) { - if (signal > -60) _lcd->print("((("); - else if (signal > -80) _lcd->print(" (("); - else _lcd->print(" ("); - } else { - _lcd->print(" "); - } -} - -void Display::textStyle(const uint8_t st, String text) { - // Twoja implementacja stylów (np. bold/inwersja jeśli używasz) - _lcd->print(text); -} - -void Display::message(String text) { - clear(); - textCenter(1, text.c_str()); - delay(2000); + _lcd->setCursor((uint8_t)x, (uint8_t)y); } void Display::showAccel(float a, float b, float c) { - _lcd->setCursor(0, 1); - _lcd->print("X:"); _lcd->print(a, 2); - _lcd->setCursor(0, 2); - _lcd->print("Y:"); _lcd->print(b, 2); - _lcd->setCursor(0, 3); - _lcd->print("Z:"); _lcd->print(c, 2); + clear(); + // _lcd->setCursor(0, 0); + // _lcd->print("Ax: "); + // _lcd->print(a, 2); + // _lcd->setCursor(0, 1); + // _lcd->print("Ay: "); + // _lcd->print(b, 2); + // _lcd->setCursor(0, 2); + // _lcd->print("Az: "); + // _lcd->print(c, 2); } -void Display::displayOffline(bool measure, uint8_t count, float freeSpace, long licznik, bool refresh) { - static uint8_t oadxlcnt = 255; - static bool omode = false; - static long last_tick = 0; - - if (refresh) clear(); - - DateTime now = rtc_.now(); - char buf[21]; - snprintf(buf, sizeof(buf), "%02d:%02d:%02d %02d/%02d", now.hour(), now.minute(), now.second(), now.day(), now.month()); - _lcd->setCursor(0, 0); - _lcd->print(buf); - - // Znacznik SSL "S" - if (_sslActive) { - _lcd->setCursor(19, 0); - _lcd->print("S"); +void Display::displayOffline(bool measure, uint8_t count, float freeSpace, long licznik, bool refresh){ + if(refresh){ + oyear, omonth, oday, ohour, omin, osec, ospace, oadxlcnt = 100; + _lcd->clear(); + _lcd->setCursor(0, 0); } - _lcd->setCursor(0, 1); - _lcd->print("SD FREE: "); - _lcd->print(freeSpace, 2); - _lcd->print(" GB "); + DateTime now = rtc_.now(); + uint16_t year = now.year(); + uint8_t month = now.month(); + uint8_t day = now.day(); + uint8_t hour = now.hour(); + uint8_t min = now.minute(); + uint8_t sec = now.second(); - if ((oadxlcnt != count) || (omode != config.measure) || (refresh)){ + // DEBUG + //ESP_LOGI(DISP, "RTC: %d:%d:%d %d:%d:%d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second()); + //ESP_LOGI(DISP, "RTC: %d:%d:%d %d:%d:%d", oday, omonth, oyear, ohour, omin, osec); + + if ((hour != ohour) || (refresh)) { + ohour = hour; + _lcd->setCursor(0, 0); + if (hour < 10) _lcd->print('0'); + _lcd->print(hour); + _lcd->setCursor(2, 0); + _lcd->print(':'); + } + + if ((min != omin) || (refresh)) { + omin = min; + _lcd->setCursor(3, 0); + if (min < 10) _lcd->print('0'); + _lcd->print(min); + _lcd->print(':'); + } + + if ((sec != osec) || (refresh)) { + _lcd->setCursor(6, 0); + osec = sec; + if (sec < 10) _lcd->print('0'); + _lcd->print(sec); + } + + if ((day != oday || month != omonth || year != oyear) || (refresh)){ + _lcd->setCursor(10, 0); + if (day < 10) _lcd->print('0'); + _lcd->print(day); + _lcd->print('.'); + if (month < 10) _lcd->print('0'); + _lcd->print(month); + _lcd->print('.'); + _lcd->print(year); + oday = day; + omonth = month; + oyear = year; + if(!config.measure) + textStatus("OK: Start MEASURE"); + else + textStatus("Wait for NEXT"); + } + + if((freeSpace != ospace) || (refresh)){ + ospace = freeSpace; + _lcd->setCursor(0, 1); + _lcd->print("FREE:"); + _lcd->print(freeSpace); + _lcd->print("GB"); + } + + if((count != oadxlcnt) || (omode != config.measure) || (refresh)){ oadxlcnt = count; omode = config.measure; _lcd->setCursor(0, 2); @@ -178,25 +234,24 @@ void Display::displayOffline(bool measure, uint8_t count, float freeSpace, long _lcd->print(" MODE:"); _lcd->print(config.measure ? "AUTO ":"MANUAL "); } - - if (millis() - last_tick > 1000) { - last_tick = millis(); - _lcd->setCursor(0, 3); - _lcd->print("SYSTEM READY "); - _lcd->print(licznik % 2 == 0 ? "*" : " "); - } } +// Podczas pomiaru void Display::displayOnOffM(bool measure, String myDir, String myFile) { clear(); _lcd->setCursor(0, 0); _lcd->print(measure ? "MEASURE STARTED" : "MEASURE STOPPED"); _lcd->setCursor(0, 1); _lcd->print("DIR: " + myDir); + //_lcd->print(myDir); + //_lcd->setCursor(0, 2); + //_lcd->print("FILE:"); + //_lcd->setCursor(0, 3); _lcd->setCursor(0, 2); _lcd->print(myFile); } +/* Podsumowanie po pomiarze: ramki, sampling, etc. */ void Display::displaySampleRateSummary(uint32_t reccount, uint32_t captureSeconds, float khz, String filename){ clear(); _lcd->setCursor(0, 0); @@ -205,12 +260,20 @@ void Display::displaySampleRateSummary(uint32_t reccount, uint32_t captureSecond delay(1000); return; } - float fs = (float)reccount / (float)captureSeconds; - _lcd->print("DONE: " + filename); - _lcd->setCursor(0, 1); - _lcd->print("Recs: " + String(reccount)); - _lcd->setCursor(0, 2); - _lcd->print("Freq: " + String(fs, 1) + " Hz"); - _lcd->setCursor(0, 3); - _lcd->print("Target: " + String(khz, 1) + " kHz"); -} \ No newline at end of file + float fs = (float)reccount / (float)captureSeconds; // Hz (ramki/s) + float fs_kHz = fs / 1000.0f; + _lcd->setCursor(0,0); + _lcd->print("Frames:"); + _lcd->print((unsigned)reccount); + _lcd->setCursor(0,1); + _lcd->print("Time:"); + _lcd->print((unsigned)captureSeconds); + _lcd->print("s."); + _lcd->setCursor(0,2); + _lcd->print("Rate:"); + _lcd->print(fs_kHz); + _lcd->print("kHz"); + + _lcd->setCursor(0,3); + _lcd->print(filename); +} diff --git a/src/Measure.cpp b/src/Measure.cpp index ff30751..42ef797 100644 --- a/src/Measure.cpp +++ b/src/Measure.cpp @@ -264,12 +264,22 @@ bool DataCapture::capture(uint32_t captureSeconds, const char *filename) { } // Czekamy na dane z sensora + uint32_t start_wait = millis(); while (!adxl_.availableAll()) { - if(isEscape()) break; + if (isEscape()) break; + if (millis() - start_wait > 500) { + ESP_LOGE(TAG_CAPTURE, "Sensor data timeout - aborting capture."); + measurementActive_ = false; + break; + } + Watchdog::feed(); + yield(); } + if (!measurementActive_) break; + const uint8_t got = adxl_.readAlignedOnce(X, Y, Z, TS); - if (got != presentCnt) continue; + if (got == 0 || got != presentCnt) continue; // Wspólny timestamp ramki uint32_t tmin = UINT32_MAX; diff --git a/src/Network.cpp b/src/Network.cpp index 01d06e4..9e07e3a 100644 --- a/src/Network.cpp +++ b/src/Network.cpp @@ -1,7 +1,8 @@ #include -#include "Watchdog.h" // Dodano dla feed() w portalu +#include static const char *WIFI = "wifi"; +extern ConfigManager configManager; WiFiManager::WiFiManager() {} @@ -19,62 +20,6 @@ void WiFiManager::begin() { setupMDNS(); } -// NOWA FUNKCJA: Portal konfiguracyjny (Captive Portal) -void WiFiManager::startConfigPortal(Display &display) { - WiFi.mode(WIFI_AP); - WiFi.softAP(ssidAP.c_str(), passwordAP.c_str()); - - dnsServer.start(53, "*", WiFi.softAPIP()); - - server.on("/", [this]() { this->handleRoot(); }); - server.on("/save", [this]() { this->handleSave(); }); - server.onNotFound([this]() { this->handleRoot(); }); - server.begin(); - - display.clear(); - display.textCenter(0, "WIFI SETUP MODE"); - display.textCenter(1, ssidAP.c_str()); - display.textCenter(2, "IP: 192.168.4.1"); - - ESP_LOGI(WIFI, "Portal started: %s", ssidAP.c_str()); - - while (true) { - dnsServer.processNextRequest(); - server.handleClient(); - Watchdog::feed(); // Użycie Twojego Watchdoga z Watchdog.h - delay(10); - // Pętla trwa do momentu restartu w handleSave() - } -} - -void WiFiManager::handleRoot() { - String html = ""; - html += ""; - html += "

VICTUS WMT

Konfiguracja WiFi:

"; - html += "
"; - html += "SSID:

"; - html += "Hasło:

"; - html += "
"; - server.send(200, "text/html", html); -} - -void WiFiManager::handleSave() { - String newSsid = server.arg("s"); - String newPass = server.arg("p"); - - if (newSsid.length() > 0) { - strncpy(config.ssid, newSsid.c_str(), sizeof(config.ssid)); - strncpy(config.password, newPass.c_str(), sizeof(config.password)); - configManager.saveConfig(); - - server.send(200, "text/html", "Zapisano dane. Restartuje..."); - ESP_LOGI(WIFI, "WiFi saved. Rebooting..."); - delay(2000); - ESP.restart(); - } -} - -// --- TWOJE ORYGINALNE FUNKCJE (ZACHOWANE W 100%) --- // Konwersja char na IPAddress bool WiFiManager::convertCharToIPAddress(const char *str, IPAddress& ip) { @@ -108,7 +53,7 @@ void WiFiManager::connectToWiFi() { } } - ESP_LOGI(WIFI, "WiFi connecting"); + ESP_LOGI(WIFI, "WiFi connecting to last SSID: %s", config.ssid); int retries = 0; while (WiFi.status() != WL_CONNECTED && retries < 20) { delay(500); @@ -116,6 +61,44 @@ void WiFiManager::connectToWiFi() { updateLED(); } + if (WiFi.status() != WL_CONNECTED) { + ESP_LOGI(WIFI, "Failed. Reading wifi.txt from SD card..."); + File f = SD.open("/wifi.txt"); + if (f) { + while (f.available()) { + String line = f.readStringUntil('\n'); + line.trim(); + if (line.isEmpty()) continue; + int sep = line.indexOf(';'); + if (sep > 0) { + String s = line.substring(0, sep); + String p = line.substring(sep + 1); + s.trim(); + p.trim(); + ESP_LOGI(WIFI, "Trying SSID from list: '%s'", s.c_str()); + WiFi.disconnect(); + WiFi.begin(s.c_str(), p.c_str()); + int tr = 0; + while (WiFi.status() != WL_CONNECTED && tr < 20) { + delay(500); + tr++; + updateLED(); + } + if (WiFi.status() == WL_CONNECTED) { + ESP_LOGI(WIFI, "Connected to %s. Saving to config.", s.c_str()); + strncpy(config.ssid, s.c_str(), sizeof(config.ssid)-1); + strncpy(config.password, p.c_str(), sizeof(config.password)-1); + configManager.saveConfig(); + break; + } + } + } + f.close(); + } else { + ESP_LOGW(WIFI, "wifi.txt not found on SD card."); + } + } + if (WiFi.status() == WL_CONNECTED) { ESP_LOGI(WIFI, "SSID: %s", config.ssid); String ipString = "IP: " + WiFi.localIP().toString(); @@ -125,7 +108,7 @@ void WiFiManager::connectToWiFi() { String dnsInfo = "DNS: " + WiFi.dnsIP().toString(); ESP_LOGI(WIFI, "%s", dnsInfo.c_str()); } else { - String infoWiFi = "WIFI CONNECTION ERROR: " + String(config.ssid); + String infoWiFi = "WIFI CONNECTION ERROR: ALL FAILED"; ESP_LOGI(WIFI, "%s", infoWiFi.c_str()); } updateLED(); @@ -165,31 +148,31 @@ bool WiFiManager::isWiFiOK(){ void WiFiManager::checkWiFiConnection() { if (!isAccessPoint) { - getRSSI(); - if (WiFi.status() != WL_CONNECTED) { - String infoWiFi = "WiFi reconnecting: " + String(config.ssid); - ESP_LOGI(WIFI, "%s", infoWiFi.c_str()); - WiFi.reconnect(); - int retries = 0; - while (WiFi.status() != WL_CONNECTED && retries < 20) { - delay(500); - retries++; - updateLED(); - } - if (WiFi.status() == WL_CONNECTED) { - //String infoWiFi = "WiFi connected: " + String(config.ssid); - //ESP_LOGI(WIFI, "%s", infoWiFi.c_str()); - //String ipString = "IP: " + WiFi.localIP().toString(); - //ESP_LOGI(WIFI, "%s", ipString.c_str()); - getRSSI(); - setupMDNS(); - } else { - String infoWiFi = "WiFi reconnect error: " + String(config.ssid); - ESP_LOGI(WIFI, "%s", infoWiFi.c_str()); + getRSSI(); + if (WiFi.status() != WL_CONNECTED) { + String infoWiFi = "WiFi reconnecting: " + String(config.ssid); + ESP_LOGI(WIFI, "%s", infoWiFi.c_str()); + WiFi.reconnect(); + int retries = 0; + while (WiFi.status() != WL_CONNECTED && retries < 20) { + delay(500); + retries++; + updateLED(); + } + if (WiFi.status() == WL_CONNECTED) { + //String infoWiFi = "WiFi connected: " + String(config.ssid); + //ESP_LOGI(WIFI, "%s", infoWiFi.c_str()); + //String ipString = "IP: " + WiFi.localIP().toString(); + //ESP_LOGI(WIFI, "%s", ipString.c_str()); getRSSI(); - } - } - updateLED(); + setupMDNS(); + } else { + String infoWiFi = "WiFi reconnect error: " + String(config.ssid); + ESP_LOGI(WIFI, "%s", infoWiFi.c_str()); + getRSSI(); + } + } + updateLED(); } } @@ -260,7 +243,7 @@ bool WiFiManager::performOTAUpdate(bool allowInsecureTLS, std::function"; + html += ""; + html += "

WiFi Configuration

"; + html += "
"; + html += "
"; + html += ""; + html += ""; + html += ""; + html += ""; + html += ""; + html += ""; + html += ""; + html += "
"; + + server.send(200, "text/html", html); +} + +void WiFiManager::handleSave() { + String ssid = server.arg("ssid"); + String pass = server.arg("password"); + String captchaStr = server.arg("captcha"); + + if (captchaStr.toInt() != expectedCaptchaAnswer) { + server.send(400, "text/plain", "Incorrect Captcha. Please go back and try again."); + return; + } + + if (ssid.length() > 0) { + File f = SD.open("/wifi.txt", FILE_APPEND); + if (f) { + f.println(ssid + ";" + pass); + f.close(); + server.send(200, "text/plain", "Credentials saved successfully! The device will now restart and try to connect."); + delay(2000); + ESP.restart(); + } else { + server.send(500, "text/plain", "Failed to open wifi.txt on SD card for appending."); + } + } else { + server.send(400, "text/plain", "SSID cannot be empty."); + } +} + +void WiFiManager::handleNotFound() { + server.sendHeader("Location", String("http://") + WiFi.softAPIP().toString(), true); + server.send(302, "text/plain", ""); } \ No newline at end of file diff --git a/src/Settings.cpp b/src/Settings.cpp index c698f3c..82fc781 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -30,6 +30,10 @@ bool Settings::isBtnReset() { return (digitalRead(BTN_UP) == LOW && digitalRead(BTN_DOWN) == LOW); } +bool Settings::isSetClock() { + ESP_LOGI(TAG_SETTINGS, "BTN set clock"); + return (digitalRead(BTN_OK) == LOW); +} /* Ustawienie opcji @@ -141,7 +145,7 @@ int Settings::editField(int value, int minVal, int maxVal, const char *label) { lastDown = down; lastOk = ok; - delay(80); // prosty debounce + ograniczenie odpytywania + delay(80); //debouncing } } diff --git a/src/UploadManager.cpp b/src/UploadManager.cpp new file mode 100644 index 0000000..4a74d3b --- /dev/null +++ b/src/UploadManager.cpp @@ -0,0 +1,117 @@ +#include "UploadManager.h" +#include "Watchdog.h" + +static const char* TAG_UPLOAD = "UPLOAD"; +static const char* LOG_FILE = "/uploaded.csv"; + +UploadManager::UploadManager(APIClient& client, RTC_DS3231& rtc, DataCapture& capture) + : apiClient(client), rtc_(rtc), capture_(capture) {} + +String UploadManager::getCurrentTimestamp() { + DateTime now = rtc_.now(); + char buf[25]; + snprintf(buf, sizeof(buf), "%04u-%02u-%02u %02u:%02u:%02u", + now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second()); + return String(buf); +} + +void UploadManager::appendLog(const String& filePath, const String& status) { + File f = SD.open(LOG_FILE, FILE_APPEND); + if (f) { + f.printf("%s,%s,%s\n", getCurrentTimestamp().c_str(), filePath.c_str(), status.c_str()); + f.close(); + } +} + +bool UploadManager::isAlreadyUploaded(const String& filePath) { + File f = SD.open(LOG_FILE, FILE_READ); + if (!f) return false; + + bool found = false; + while (f.available()) { + String line = f.readStringUntil('\n'); + line.trim(); + if (line.length() == 0) continue; + + int firstComma = line.indexOf(','); + if (firstComma < 0) continue; + int secondComma = line.indexOf(',', firstComma + 1); + if (secondComma < 0) continue; + + String logPath = line.substring(firstComma + 1, secondComma); + String logStatus = line.substring(secondComma + 1); + + if (logPath == filePath) { + if (logStatus == "OK") found = true; + else found = false; // A retry might be needed if last status wasn't OK + } + Watchdog::feed(); + } + f.close(); + return found; +} + +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) { + ESP_LOGE(TAG_UPLOAD, "No WiFi. Cannot upload %s", filePath.c_str()); + appendLog(filePath, "ERROR: No WiFi"); + return; + } + + bool success = apiClient.uploadMeasurement(filePath); + if (success) { + appendLog(filePath, "OK"); + } else { + if (WiFi.status() != WL_CONNECTED) { + appendLog(filePath, "ERROR: WiFi lost during upload"); + } else { + appendLog(filePath, "ERROR: Upload Failed"); + } + } +} + +void UploadManager::processPendingUploads() { + if (WiFi.status() != WL_CONNECTED) return; + + ESP_LOGI(TAG_UPLOAD, "Checking for pending uploads..."); + uint32_t highestDir = capture_.findHighestNumericDir(); + if (highestDir == 0) return; + + for (uint32_t d = 1; d <= highestDir; d++) { + String path = capture_.dirPath(d); + File dir = SD.open(path); + if (!dir || !dir.isDirectory()) { + if(dir) dir.close(); + continue; + } + + for (File f = dir.openNextFile(); f; f = dir.openNextFile()) { + if (f.isDirectory()) { f.close(); continue; } + String childPath = String(f.name()); + if (!childPath.startsWith("/")) { + childPath = path + "/" + childPath; + } + + if (childPath.endsWith(".wmt")) { + if (!isAlreadyUploaded(childPath)) { + ESP_LOGI(TAG_UPLOAD, "Found pending file: %s", childPath.c_str()); + uploadFile(childPath); + delay(1000); + } + } + f.close(); + Watchdog::feed(); + if (WiFi.status() != WL_CONNECTED) { + dir.close(); + return; + } + } + dir.close(); + } + ESP_LOGI(TAG_UPLOAD, "Pending uploads check complete."); +} diff --git a/src/main.cpp b/src/main.cpp index 396c169..5d90aa2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,7 +15,8 @@ #include #include #include -#include // Dodano moduł wysyłki +#include "APIClient.h" +#include "UploadManager.h" #define WDT_TIMEOUT 60 // Czas watchdoga do restartu #define MAX_ADXL345_SENSORS 4 // Maksymalna ilość podłączanych sensorów @@ -23,7 +24,7 @@ SPIClass SPI_ADXL(FSPI); // SPI2 (VSPI) SPIClass SPI_SD(HSPI); // SPI3 (HSPI) -float x, y, z = 0; // Dane odczytane z akcelerometru +float x, y, z = 0; // Dane odczytane z akcelerometru String name; bool isRebootRequired = false; @@ -46,14 +47,13 @@ Settings settings(display, rtc); // obsługa przycisków WiFiManager wifi; DataCapture capture(adxl, display, rtc, SD, 8192); // NEW !!! MEASURE!!! - -// Inicjalizacja Uploadera -Uploader uploader(display); +APIClient apiClient; +UploadManager uploadManager(apiClient, rtc, capture); Thread wifiTestThread = Thread(); // Cykliczny test WiFi Thread offlineThread = Thread(); // Jeśli offline Thread measureThread = Thread(); // Pomiar i zapis na SD -Thread uploadThread = Thread(); // Wątek wysyłki SSL +Thread uploadThread = Thread(); // Background upload //////// PROTOTYPY ///////////////// void setup(); @@ -63,10 +63,7 @@ void resetBtnClick(); void checkWiFi(); void showOfflineScreen(); void measure(); -void runUploader(); -void toogleMode(); -void settingsDevice(); -void showError(String err); +void setClockRTCBtn(); /* ******************* SETUP() ************************* */ void setup() { @@ -84,8 +81,8 @@ void setup() { ESP_LOGI(TAG_MAIN, "Rejestrator parametrow"); ESP_LOGI(TAG_MAIN, "Firmware: %s", VERSION); ESP_LOGI(TAG_MAIN, "----------------------"); - ESP_LOGI(TAG_MAIN, "ESP32 model: %s Rev %d", ESP.getChipModel(), ESP.getChipRevision()); - ESP_LOGI(TAG_MAIN, "Chip cores: %d", ESP.getChipCores()); + ESP_LOGI(TAG_MAIN, "ESP32 model: %s Rev %d", ESP.getChipModel(), ESP.getChipRevision()); + ESP_LOGI(TAG_MAIN, "Chip cores: %d", ESP.getChipCores()); // Inicjalizacja Watchdoga na 5 sek, panic_on_trigger = true if (Watchdog::init(25, true)) { @@ -99,6 +96,8 @@ void setup() { configManager.begin(); // konfiguracja EEPROM urządzenia + //configManager.showConfig(); + // Test LCD I2C if (!isI2CDevPresent(0x27)) { ESP_LOGE(TAG_MAIN, "LCD 0x27 wire error!"); @@ -158,6 +157,10 @@ void setup() { settings.setTimeRTC(); } } + + // Przycisk ustawianai czasu: przytrzymaj OK przy starcie systemu + if(settings.isSetClock()) setClockRTCBtn(); + delay(500); // Karta SD @@ -165,7 +168,7 @@ void setup() { ESP_LOGI(TAG_MAIN, "SPI2 SD SCK: %d, MISO: %d, MOSI: %d, CS: %d", SD_SCK, SD_MISO, SD_MOSI, SD_CS); SPI_SD.begin(SD_SCK, SD_MISO, SD_MOSI); display.textStatus("SD CARD:"); - while(!SD.begin(SD_CS, SPI_SD, 4000000)) { + while(!SD.begin(SD_CS, SPI_SD, 4000000)) { // 10 MHz na start; w razie czego 4 MHz (bez tego często SD Failed) ESP_LOGE(TAG_MAIN, "SD mount failed"); display.print("FAILED"); delay(4000); @@ -181,7 +184,8 @@ void setup() { // Inicjalizacja ADXL345 ESP_LOGI(TAG_MAIN, "ADXL345 init"); display.textStatus("ADXL345: "); - if(!adxl.begin(&SPI_ADXL, 5000000, ADXL345FastSPI::RATE_3200HZ, ADXL345FastSPI::RANGE_16G, 1)){ + //if(!adxl.begin(&SPI_ADXL, 5000000, ADXL345FastSPI::RATE_3200HZ, ADXL345FastSPI::RANGE_16G, 1)){ + if(!adxl.begin(&SPI_ADXL, 2000000, ADXL345FastSPI::RATE_3200HZ, ADXL345FastSPI::RANGE_16G, 1)){ ESP_LOGE(TAG_MAIN, "ADXL345 Error"); display.print("FAILED"); isAccelExists = false; @@ -208,36 +212,23 @@ void setup() { delay(1000); if(config.connect){ - display.textStatus("WiFi Connect..."); wifi.begin(); - - int retry_count = 0; - while (WiFi.status() != WL_CONNECTED && retry_count < 60) { - delay(500); - retry_count++; - Watchdog::feed(); - if(retry_count % 10 == 0) display.print("."); - } - - if (WiFi.status() != WL_CONNECTED) { - ESP_LOGW(TAG_MAIN, "WiFi Failed. Starting Configuration Portal..."); - wifi.startConfigPortal(display); - } else { - ESP_LOGI(TAG_MAIN, "WiFi OK"); - wifiTestThread.onRun(checkWiFi); - wifiTestThread.setInterval(5000); - - uploadThread.onRun(runUploader); - uploadThread.setInterval(30000); - } + wifiTestThread.onRun(checkWiFi); + wifiTestThread.setInterval(5000); // Test WiFi co 3 sekundy } else { offlineThread.onRun(showOfflineScreen); - offlineThread.setInterval(1000); + offlineThread.setInterval(1000); // Jeśli offline, to wyświetl ekran co 1 sek } measureThread.onRun(measure); - measureThread.setInterval(config.pause); + measureThread.setInterval(config.pause); // Test co X sekund + if(config.connect){ + uploadThread.onRun([]() { uploadManager.processPendingUploads(); }); + uploadThread.setInterval(60000); // Co 1 minutę sprawdzaj zaległe + } + + // Dodanie taska loop do WDT if (Watchdog::addThisTask()) { ESP_LOGI(TAG_MAIN, "Added main task to Watchdog"); } @@ -246,14 +237,23 @@ void setup() { display.clear(); } -void runUploader() { - if (!testingNow) { - uploader.processQueue(3); - } -} +// Factory reset +void setClockRTCBtn(){ + ESP_LOGI(TAG_MAIN, "SET DATE TIME"); + while(settings.isSetClock()){ + display.clear(); + display.textCenter(0, " PLEASE "); + display.textCenter(1, "release key"); + display.textCenter(3, "TO SET DATE TIME"); + delay(500); + } + settings.setTimeRTC(); +} + +// Factory reset void resetBtnClick(){ - uint8_t counter = 10; + uint8_t counter = 10; // ile sekund trzymać przycisk na factory reset ESP_LOGI(TAG_MAIN, "RESET BTN to 10 sec."); while(settings.isBtnReset()){ Serial.print(counter); Serial.print(","); @@ -280,6 +280,8 @@ void resetBtnClick(){ } } +/////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void showError(String err){ Watchdog::feed(); display.clear(); @@ -288,6 +290,7 @@ void showError(String err){ delay(3000); } + void checkWiFi(){ Watchdog::feed(); bool isConnected = WiFi.isConnected(); @@ -297,6 +300,7 @@ void checkWiFi(){ display.updateNetwork(ip, isConnected); } +////// Ekran główny tylko gdy brak pomiaru ///////////// void showOfflineScreen(){ if((!config.connect) && (!testingNow)) { bool isGB; @@ -306,23 +310,26 @@ void showOfflineScreen(){ } } + void reboot() { display.clear(); display.textCenter(1, "SYSTEM"); display.textCenter(2, "RESTARTING"); #if defined(ARDUINO_RASPBERRY_PI_PICO) - watchdog_enable(1, 1); - while (true); + watchdog_enable(1, 1); // 1 ms timeout, restart po upływie + while (true); // czekaj na watchdog reset #endif #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) ESP_LOGI(TAG_MAIN, "RESTART"); - ESP.restart(); + ESP.restart(); // restart ESP #endif #if defined(ARDUINO_ARCH_STM32) - NVIC_SystemReset(); + NVIC_SystemReset(); // restartuj STM32 #endif } + +// pomiar z Thread START POMIARU TUTAJ void measure(){ Watchdog::feed(); testingNow = true; @@ -334,7 +341,7 @@ void measure(){ snprintf(btime, sizeof(btime), "%02d:%02d:%02d", now.hour(), now.minute(), now.second()); display.initMeasure(config.measure, testingNow, runMeasure, config.pause, config.duration, config.connect, licznik, bdate, btime); ESP_LOGI(TAG_MAIN, "MEASURE RUNNING"); - + //delay(1000); capture.captureAuto(config.duration, "/"); testingNow = false; Watchdog::feed(); @@ -344,14 +351,21 @@ void measure(){ ESP_LOGI(TAG_MAIN, "MEASURE FINISH"); } else { ESP_LOGI(TAG_MAIN, "MEASURE INTERRUPT"); + //runMeasure = false; } + + if (config.connect && WiFi.status() == WL_CONNECTED) { + ESP_LOGI(TAG_MAIN, "TRIGGER UPLOAD PENDING..."); + uploadManager.processPendingUploads(); + } + runMeasure = false; ESP_LOGI(TAG_MAIN, "DISPLAY Offline"); display.displayOffline(testingNow, adxl.connectedSensorsCount(), capture.freeSpaceFloat(), licznik, true); offlineThread.enabled = true; } -void toogleMode(){ +void toogleMode(){ // tryb ciągłego, pojedynczego pomiaru config.measure = !config.measure; configManager.saveConfig(); display.textStatus("Mode changed"); @@ -385,18 +399,30 @@ void loop() { if(settings.isPressed(3)) settingsDevice(); // DOWN if(settings.isPressed(1)) toogleMode(); // UP + if (settings.readBtnUp() && settings.readBtnDown()) { + ESP_LOGI(TAG_MAIN, "Manual AP Mode trigger"); + wifi.startCaptivePortal(); + display.clear(); + display.textCenter(1, "AP MODE"); + display.textCenter(2, "192.168.4.1"); + while(settings.readBtnUp() && settings.readBtnDown()) { + Watchdog::feed(); + delay(50); + } + } + if(testingNow) { if((runMeasure) && (settings.isPressed(2))){ runMeasure = false; display.textStatus("STOPPING. WAIT"); } - return; + return; // jeśli trwa akurat test, nic nie rób } if(wifiTestThread.shouldRun() && (config.connect)) wifiTestThread.run(); - if(uploadThread.shouldRun() && (config.connect)) + if(uploadThread.shouldRun() && config.connect) uploadThread.run(); if(offlineThread.shouldRun() && (!config.connect)){ @@ -423,11 +449,12 @@ void loop() { if (isRebootRequired) { ESP_LOGI(TAG_MAIN, "Reboot required"); Watchdog::feed(); - delay(1000); - reboot(); + delay(1000); + reboot(); } + wifi.handleClient(); Watchdog::feed(); - delay(20); + delay(20); // 100 licznik ++; -} \ No newline at end of file +} From 05766b3aea8c73c5bdcf5b34eb9eb134adf0ae7e Mon Sep 17 00:00:00 2001 From: Victus Date: Sun, 10 May 2026 21:17:18 +0200 Subject: [PATCH 3/5] Zmieniono wyzwalacz AP na UP + OK --- src/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 5d90aa2..206af86 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -399,13 +399,13 @@ void loop() { if(settings.isPressed(3)) settingsDevice(); // DOWN if(settings.isPressed(1)) toogleMode(); // UP - if (settings.readBtnUp() && settings.readBtnDown()) { + if (settings.readBtnUp() && settings.readBtnOk()) { ESP_LOGI(TAG_MAIN, "Manual AP Mode trigger"); wifi.startCaptivePortal(); display.clear(); display.textCenter(1, "AP MODE"); display.textCenter(2, "192.168.4.1"); - while(settings.readBtnUp() && settings.readBtnDown()) { + while(settings.readBtnUp() && settings.readBtnOk()) { Watchdog::feed(); delay(50); } From b723838413bed25d8b700ef4924ccc76e7d0640e Mon Sep 17 00:00:00 2001 From: Victus Date: Sun, 10 May 2026 22:17:23 +0200 Subject: [PATCH 4/5] Poprawka logiki Captive Portal: zapobieganie wielokrotnemu uruchomieniu serwera --- src/Network.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Network.cpp b/src/Network.cpp index 9e07e3a..70b475d 100644 --- a/src/Network.cpp +++ b/src/Network.cpp @@ -275,6 +275,11 @@ bool WiFiManager::performOTAUpdate(bool allowInsecureTLS, std::function Date: Sun, 10 May 2026 23:02:00 +0200 Subject: [PATCH 5/5] Dodano szczegolowe logi do debugowania crashy w Measure, UploadManager i APIClient --- src/APIClient.cpp | 4 ++++ src/UploadManager.cpp | 7 +++++++ src/main.cpp | 4 ++++ 3 files changed, 15 insertions(+) diff --git a/src/APIClient.cpp b/src/APIClient.cpp index a87d042..eb888fa 100644 --- a/src/APIClient.cpp +++ b/src/APIClient.cpp @@ -71,6 +71,7 @@ bool APIClient::uploadMeasurement(const String &filePath) { // Multipart body: head + file data + tail client.print(head); + ESP_LOGI(TAG_API, "DEBUG: Beginning file read loop"); uint8_t buffer[2048]; while (file.available()) { @@ -79,8 +80,10 @@ bool APIClient::uploadMeasurement(const String &filePath) { Watchdog::feed(); } + ESP_LOGI(TAG_API, "DEBUG: File read loop finished. Writing tail."); client.print(tail); file.close(); + ESP_LOGI(TAG_API, "DEBUG: Tail written. Waiting for response..."); // --- Parsowanie odpowiedzi --- int httpCode = 0; @@ -98,6 +101,7 @@ bool APIClient::uploadMeasurement(const String &filePath) { } Watchdog::feed(); } + ESP_LOGI(TAG_API, "DEBUG: Header parsing loop finished. httpCode: %d", httpCode); String responseBody = ""; while (client.available()) { diff --git a/src/UploadManager.cpp b/src/UploadManager.cpp index 4a74d3b..f23ee0a 100644 --- a/src/UploadManager.cpp +++ b/src/UploadManager.cpp @@ -63,7 +63,9 @@ void UploadManager::uploadFile(const String& filePath) { return; } + ESP_LOGI(TAG_UPLOAD, "DEBUG: Before apiClient.uploadMeasurement"); bool success = apiClient.uploadMeasurement(filePath); + ESP_LOGI(TAG_UPLOAD, "DEBUG: After apiClient.uploadMeasurement. Success: %d", success); if (success) { appendLog(filePath, "OK"); } else { @@ -79,11 +81,14 @@ void UploadManager::processPendingUploads() { if (WiFi.status() != WL_CONNECTED) return; ESP_LOGI(TAG_UPLOAD, "Checking for pending uploads..."); + ESP_LOGI(TAG_UPLOAD, "DEBUG: findHighestNumericDir()"); uint32_t highestDir = capture_.findHighestNumericDir(); + ESP_LOGI(TAG_UPLOAD, "DEBUG: highestDir: %u", highestDir); if (highestDir == 0) return; for (uint32_t d = 1; d <= highestDir; d++) { String path = capture_.dirPath(d); + ESP_LOGI(TAG_UPLOAD, "DEBUG: Scanning dir: %s", path.c_str()); File dir = SD.open(path); if (!dir || !dir.isDirectory()) { if(dir) dir.close(); @@ -100,7 +105,9 @@ void UploadManager::processPendingUploads() { if (childPath.endsWith(".wmt")) { if (!isAlreadyUploaded(childPath)) { ESP_LOGI(TAG_UPLOAD, "Found pending file: %s", childPath.c_str()); + ESP_LOGI(TAG_UPLOAD, "DEBUG: Calling uploadFile()"); uploadFile(childPath); + ESP_LOGI(TAG_UPLOAD, "DEBUG: Finished uploadFile(), delaying 1000ms"); delay(1000); } } diff --git a/src/main.cpp b/src/main.cpp index 206af86..dfc3f60 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -347,6 +347,7 @@ void measure(){ Watchdog::feed(); if(!capture.isExit){ capture.printLastFileInfoSerial(); + ESP_LOGI(TAG_MAIN, "DEBUG: before readHeaderAndPrint"); capture.readHeaderAndPrint(capture.generateNextFilename().c_str()); ESP_LOGI(TAG_MAIN, "MEASURE FINISH"); } else { @@ -354,9 +355,12 @@ void measure(){ //runMeasure = false; } + ESP_LOGI(TAG_MAIN, "DEBUG: Checking WiFi status for upload"); if (config.connect && WiFi.status() == WL_CONNECTED) { ESP_LOGI(TAG_MAIN, "TRIGGER UPLOAD PENDING..."); + ESP_LOGI(TAG_MAIN, "DEBUG: Before uploadManager.processPendingUploads()"); uploadManager.processPendingUploads(); + ESP_LOGI(TAG_MAIN, "DEBUG: After uploadManager.processPendingUploads()"); } runMeasure = false;