Dodanie Captive Portal z logarytmami

This commit is contained in:
Victus
2026-05-10 20:46:23 +02:00
parent 0985792a06
commit 66475edad4
17 changed files with 776 additions and 316 deletions

View File

@@ -21,7 +21,7 @@ public:
ADXL345FreshSPI() = default; ADXL345FreshSPI() = default;
// --- Init (SPI only) --- // --- Init (SPI) ---
// Uwaga: ADXL345 wymaga SPI MODE3, zegar ≤ ~5 MHz. // Uwaga: ADXL345 wymaga SPI MODE3, zegar ≤ ~5 MHz.
bool begin(SPIClass *spi, uint8_t csPin, uint32_t clockHz = 5000000); bool begin(SPIClass *spi, uint8_t csPin, uint32_t clockHz = 5000000);

19
include/APIClient.h Normal file
View File

@@ -0,0 +1,19 @@
#ifndef APICLIENT_H
#define APICLIENT_H
#include <Arduino.h>
#include <WiFiClient.h>
#include <HTTPClient.h>
#include <FS.h>
#include <SD.h>
#include "Config.h"
#include "Watchdog.h"
class APIClient {
public:
APIClient();
bool uploadMeasurement(const String& filePath);
};
#endif

View File

@@ -60,10 +60,6 @@ public:
void setCursor(int16_t x, int16_t y); void setCursor(int16_t x, int16_t y);
void showAccel(float a, float b, float c); void showAccel(float a, float b, float c);
void displayOnOffM(bool measure, String myDir, String myFile); void displayOnOffM(bool measure, String myDir, String myFile);
// Metoda sterująca ikoną SSL (dodana do klasy)
void setSSLStatus(bool active);
void initMeasure( void initMeasure(
bool measure, // czy pomiar ciągły? bool measure, // czy pomiar ciągły?
bool run, // czy uruchomiony? bool run, // czy uruchomiony?
@@ -78,9 +74,20 @@ public:
private: private:
LiquidCrystal_I2C *_lcd; LiquidCrystal_I2C *_lcd;
RTC_DS3231 &rtc_; RTC_DS3231 &rtc_;
uint8_t _address;
uint8_t _columns; uint8_t _columns;
uint8_t _rows; 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 #endif

View File

@@ -17,8 +17,6 @@ static constexpr uint32_t SPI_HZ = 5000000; // 5 MHz (MODE3)
static constexpr float ODR_HZ = 3200.0f; // maks. ODR static constexpr float ODR_HZ = 3200.0f; // maks. ODR
// Zakres ustawiany w main.cpp przez ADXL345FastSPI::begin(..., RANGE_2G, ...) // Zakres ustawiany w main.cpp przez ADXL345FastSPI::begin(..., RANGE_2G, ...)
//extern Display display;
struct FileInfo { struct FileInfo {
String path; // np. "/3/00000057.wmt" String path; // np. "/3/00000057.wmt"
uint64_t size; // bajty uint64_t size; // bajty
@@ -31,7 +29,7 @@ struct SpaceInfo {
}; };
class DataCapture { class DataCapture {
// --- Nagłówek pliku WMT (jak w oryginale) --- // --- Nagłówek pliku WMT ---
struct FileHeader { struct FileHeader {
char magic[3]; // "WMT" char magic[3]; // "WMT"
uint16_t version; // 1 uint16_t version; // 1
@@ -42,7 +40,7 @@ class DataCapture {
} __attribute__((packed)); } __attribute__((packed));
public: public:
// --- Rekord próbki (jak w oryginale) --- // --- Rekord próbki ---
struct Sample { struct Sample {
uint32_t offset; // µs od startu akwizycji (wspólny dla ramki) uint32_t offset; // µs od startu akwizycji (wspólny dla ramki)
uint8_t sensor_id; // 0..3 uint8_t sensor_id; // 0..3
@@ -105,8 +103,6 @@ private:
return ispress; return ispress;
} // szybkidigitalRead : czy BTN stop??? } // szybkidigitalRead : czy BTN stop???
// Helpers (zachowane z Twojej wersji)
public: public:
bool isAllDigits(const char *s); bool isAllDigits(const char *s);
uint32_t toUint(const char *s); uint32_t toUint(const char *s);

View File

@@ -9,13 +9,12 @@
#include <WiFi.h> #include <WiFi.h>
#include <WebServer.h> #include <WebServer.h>
#include <ESPmDNS.h> #include <ESPmDNS.h>
#include <DNSServer.h>
#elif defined(ESP8266) #elif defined(ESP8266)
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESP8266mDNS.h> #include <ESP8266mDNS.h>
#include <ESP8266WebServer.h> #include <DNSServer.h>
#endif #endif
#include <DNSServer.h> // Dodano dla Captive Portal
//#include <Pinout.h> //#include <Pinout.h>
//#include <IPAddress.h> //#include <IPAddress.h>
//#include <WiFiUdp.h> //#include <WiFiUdp.h>
@@ -28,11 +27,6 @@
#include <functional> #include <functional>
// OTA // 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 { class WiFiManager {
public: public:
WiFiManager(); WiFiManager();
@@ -50,11 +44,11 @@ class WiFiManager {
int rssiToPercent(int rssi); // rssi na procenty int rssiToPercent(int rssi); // rssi na procenty
int8_t getRSSI(); int8_t getRSSI();
/** void handleClient();
* NOWA METODA: Portal konfiguracyjny (Captive Portal) void startCaptivePortal();
* Uruchamiany automatycznie przy braku połączenia. void handleRoot();
*/ void handleSave();
void startConfigPortal(Display &display); void handleNotFound();
/** /**
* Aktualizacja systemu przez internet z adresu config.updateUrl. * Aktualizacja systemu przez internet z adresu config.updateUrl.
@@ -75,18 +69,10 @@ class WiFiManager {
private: private:
bool isAccessPoint = false; bool isAccessPoint = false;
bool captivePortalActive = false;
// Obiekty serwerów dla Portalu (ESP32/ESP8266)
#ifdef ESP32
WebServer server{80}; WebServer server{80};
#elif defined(ESP8266)
ESP8266WebServer server{80};
#endif
DNSServer dnsServer; DNSServer dnsServer;
void handleRoot(); int expectedCaptchaAnswer = 0;
void handleSave();
}; };
extern WiFiManager wifi; #endif // WIFIMANAGER_H
#endif // WIFIMANAGER_H

View File

@@ -24,7 +24,7 @@ class Settings {
// bool is1and3(); // bool is1and3();
bool isBtnReset(); bool isBtnReset();
bool isSetClock();
bool readBtnUp() { return digitalRead(BTN_UP) == LOW; } bool readBtnUp() { return digitalRead(BTN_UP) == LOW; }
bool readBtnOk() { return digitalRead(BTN_OK) == LOW; } bool readBtnOk() { return digitalRead(BTN_OK) == LOW; }
bool readBtnDown() { return digitalRead(BTN_DOWN) == LOW; } bool readBtnDown() { return digitalRead(BTN_DOWN) == LOW; }

31
include/UploadManager.h Normal file
View File

@@ -0,0 +1,31 @@
#ifndef UPLOADMANAGER_H
#define UPLOADMANAGER_H
#include <Arduino.h>
#include <FS.h>
#include <SD.h>
#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

View File

@@ -1,7 +1,7 @@
#ifndef VERSION_H #ifndef VERSION_H
#define VERSION_H #define VERSION_H
#define VERSION "1.3.2" #define VERSION "1.3.4.1"
// 1: graphical 128x64, 2: LCD I2C Text 4x20 // 1: graphical 128x64, 2: LCD I2C Text 4x20
#define LCD_TYPE 2 #define LCD_TYPE 2

View File

@@ -1,4 +1,5 @@
#include "ADXL345FastSPI.h" #include "ADXL345FastSPI.h"
#include "Watchdog.h"
static const char *TAG_FRESH = "ADXLFAST"; 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){ 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) // 1) Czekaj aż każdy ma ≥1 próbkę (DATA_READY => FIFO>0)
uint32_t start_wait = millis();
while (!availableAll()) { 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 // 2) Zdejmij najstarszą z każdego sensora

124
src/APIClient.cpp Normal file
View File

@@ -0,0 +1,124 @@
#include "APIClient.h"
#include <WiFiClient.h>
#include <base64.h>
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;
}

View File

@@ -22,7 +22,7 @@ void ConfigManager::begin() {
// Check is EEPROM is empty // Check is EEPROM is empty
bool ConfigManager::isEEPROMEmpty() { bool ConfigManager::isEEPROMEmpty() {
if (EEPROM.read(0) != 251){ if (EEPROM.read(0) != 253){
ESP_LOGI(TAG_CONF, "EEPROM is new!"); ESP_LOGI(TAG_CONF, "EEPROM is new!");
return true; return true;
} else { } else {
@@ -40,7 +40,7 @@ void ConfigManager::readConfig() {
void ConfigManager::saveConfig() { void ConfigManager::saveConfig() {
ESP_LOGI(TAG_CONF, "SAVE CONFIG"); ESP_LOGI(TAG_CONF, "SAVE CONFIG");
EEPROM.put(1, config); EEPROM.put(1, config);
EEPROM.write(0, 251); EEPROM.write(0, 253);
if (EEPROM.commit()) { if (EEPROM.commit()) {
ESP_LOGI(TAG_CONF, "Config saved"); ESP_LOGI(TAG_CONF, "Config saved");
} else { } else {
@@ -77,15 +77,15 @@ void ConfigManager::resetToDefaults() {
strcpy(config.user, "admin"); strcpy(config.user, "admin");
strcpy(config.pass, "admin"); strcpy(config.pass, "admin");
strcpy(config.ntp, "pl.pool.ntp.org"); 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.measure = 1; // włącz automatyczny pomiar co x sekunt (pause)
config.duration = 5; // czas trwania pomiaru 5 sekund config.duration = 5; // czas trwania pomiaru 5 sekund
config.pause = 10000; // odstęp pomiędzy pomiarami w ms config.pause = 10000; // odstęp pomiędzy pomiarami w ms
//strcpy(config.ntp, "0.pl.pool.ntp.org"); //strcpy(config.ntp, "0.pl.pool.ntp.org");
strcpy(config.restURL, "http://62.93.60.19/accels/api1"); strcpy(config.restURL, "http://62.93.60.19");
config.restPort = 8000; config.restPort = 5004;
strcpy(config.restUser, "wmt"); strcpy(config.restUser, "SN001234ABCD56789012");
strcpy(config.restPass, "Zaq12wsx"); strcpy(config.restPass, "device001");
strcpy(config.S0, "ACCEL1"); strcpy(config.S0, "ACCEL1");
strcpy(config.S1, "ACCEL2"); strcpy(config.S1, "ACCEL2");
strcpy(config.S2, "ACCEL3"); strcpy(config.S2, "ACCEL3");
@@ -95,7 +95,7 @@ void ConfigManager::resetToDefaults() {
strcpy(config.S6, "ACCEL7"); strcpy(config.S6, "ACCEL7");
strcpy(config.S7, "ACCEL8"); strcpy(config.S7, "ACCEL8");
EEPROM.put(1, config); EEPROM.put(1, config);
EEPROM.write(0, 251); EEPROM.write(0, 253);
saveConfig(); saveConfig();
readConfig(); readConfig();
isRebootRequired = true; isRebootRequired = true;

View File

@@ -3,9 +3,6 @@
static const char *DISP = "display"; 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) { Display::Display(RTC_DS3231 &rtc, uint8_t address, uint8_t columns, uint8_t rows):rtc_(rtc) {
_lcd = new LiquidCrystal_I2C(address, columns, rows); _lcd = new LiquidCrystal_I2C(address, columns, rows);
_address = address; _address = address;
@@ -28,9 +25,6 @@ bool Display::begin(TwoWire *wire) {
return true; return true;
} }
void Display::setSSLStatus(bool active) {
_sslActive = active;
}
void Display::initMeasure(bool measure, bool run, bool runmes, uint16_t pause, uint8_t duration, void Display::initMeasure(bool measure, bool run, bool runmes, uint16_t pause, uint8_t duration,
bool connect, long counter, String gdate, String gtime) { 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"; String type = "Sample:" + String(duration) + "s Pause:" + String(pause/1000)+"s";
textCenter(2, type.c_str()); textCenter(2, type.c_str());
textCenter(3, String("Time:" + gtime).c_str()); textCenter(3, String("Time:" + gtime).c_str());
} textStatus("initialisation");
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() { void Display::clear() {
_lcd->clear(); _lcd->clear();
} }
void Display::mainScreen() { void Display::textCenter(uint8_t line, const char *txt){
// Twoja logika mainScreen if (line >= _rows) {
_lcd->clear(); ESP_LOGE(DISP, "Line >= max rows!");
textCenter(1, "MAIN SCREEN"); 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) { void Display::print(String text) {
@@ -99,77 +136,96 @@ void Display::print(String text) {
void Display::println(String text) { void Display::println(String text) {
_lcd->print(text); _lcd->print(text);
_lcd->print("\n");
} }
void Display::setCursor(int16_t x, int16_t y) { void Display::setCursor(int16_t x, int16_t y) {
_lcd->setCursor(x, y); _lcd->setCursor((uint8_t)x, (uint8_t)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) { void Display::showAccel(float a, float b, float c) {
_lcd->setCursor(0, 1); clear();
_lcd->print("X:"); _lcd->print(a, 2); // _lcd->setCursor(0, 0);
_lcd->setCursor(0, 2); // _lcd->print("Ax: ");
_lcd->print("Y:"); _lcd->print(b, 2); // _lcd->print(a, 2);
_lcd->setCursor(0, 3); // _lcd->setCursor(0, 1);
_lcd->print("Z:"); _lcd->print(c, 2); // _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) { void Display::displayOffline(bool measure, uint8_t count, float freeSpace, long licznik, bool refresh){
static uint8_t oadxlcnt = 255; if(refresh){
static bool omode = false; oyear, omonth, oday, ohour, omin, osec, ospace, oadxlcnt = 100;
static long last_tick = 0; _lcd->clear();
_lcd->setCursor(0, 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); DateTime now = rtc_.now();
_lcd->print("SD FREE: "); uint16_t year = now.year();
_lcd->print(freeSpace, 2); uint8_t month = now.month();
_lcd->print(" GB "); 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; oadxlcnt = count;
omode = config.measure; omode = config.measure;
_lcd->setCursor(0, 2); _lcd->setCursor(0, 2);
@@ -178,25 +234,24 @@ void Display::displayOffline(bool measure, uint8_t count, float freeSpace, long
_lcd->print(" MODE:"); _lcd->print(" MODE:");
_lcd->print(config.measure ? "AUTO ":"MANUAL "); _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) { void Display::displayOnOffM(bool measure, String myDir, String myFile) {
clear(); clear();
_lcd->setCursor(0, 0); _lcd->setCursor(0, 0);
_lcd->print(measure ? "MEASURE STARTED" : "MEASURE STOPPED"); _lcd->print(measure ? "MEASURE STARTED" : "MEASURE STOPPED");
_lcd->setCursor(0, 1); _lcd->setCursor(0, 1);
_lcd->print("DIR: " + myDir); _lcd->print("DIR: " + myDir);
//_lcd->print(myDir);
//_lcd->setCursor(0, 2);
//_lcd->print("FILE:");
//_lcd->setCursor(0, 3);
_lcd->setCursor(0, 2); _lcd->setCursor(0, 2);
_lcd->print(myFile); _lcd->print(myFile);
} }
/* Podsumowanie po pomiarze: ramki, sampling, etc. */
void Display::displaySampleRateSummary(uint32_t reccount, uint32_t captureSeconds, float khz, String filename){ void Display::displaySampleRateSummary(uint32_t reccount, uint32_t captureSeconds, float khz, String filename){
clear(); clear();
_lcd->setCursor(0, 0); _lcd->setCursor(0, 0);
@@ -205,12 +260,20 @@ void Display::displaySampleRateSummary(uint32_t reccount, uint32_t captureSecond
delay(1000); delay(1000);
return; return;
} }
float fs = (float)reccount / (float)captureSeconds; float fs = (float)reccount / (float)captureSeconds; // Hz (ramki/s)
_lcd->print("DONE: " + filename); float fs_kHz = fs / 1000.0f;
_lcd->setCursor(0, 1); _lcd->setCursor(0,0);
_lcd->print("Recs: " + String(reccount)); _lcd->print("Frames:");
_lcd->setCursor(0, 2); _lcd->print((unsigned)reccount);
_lcd->print("Freq: " + String(fs, 1) + " Hz"); _lcd->setCursor(0,1);
_lcd->setCursor(0, 3); _lcd->print("Time:");
_lcd->print("Target: " + String(khz, 1) + " kHz"); _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);
}

View File

@@ -264,12 +264,22 @@ bool DataCapture::capture(uint32_t captureSeconds, const char *filename) {
} }
// Czekamy na dane z sensora // Czekamy na dane z sensora
uint32_t start_wait = millis();
while (!adxl_.availableAll()) { 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); const uint8_t got = adxl_.readAlignedOnce(X, Y, Z, TS);
if (got != presentCnt) continue; if (got == 0 || got != presentCnt) continue;
// Wspólny timestamp ramki // Wspólny timestamp ramki
uint32_t tmin = UINT32_MAX; uint32_t tmin = UINT32_MAX;

View File

@@ -1,7 +1,8 @@
#include <Network.h> #include <Network.h>
#include "Watchdog.h" // Dodano dla feed() w portalu #include <SD.h>
static const char *WIFI = "wifi"; static const char *WIFI = "wifi";
extern ConfigManager configManager;
WiFiManager::WiFiManager() {} WiFiManager::WiFiManager() {}
@@ -19,62 +20,6 @@ void WiFiManager::begin() {
setupMDNS(); 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><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1'>";
html += "<style>body{font-family:sans-serif;background:#f0f0f0;padding:20px;}input{width:100%;padding:10px;margin:10px 0;}</style></head>";
html += "<body><h1>VICTUS WMT</h1><p>Konfiguracja WiFi:</p>";
html += "<form action='/save' method='POST'>";
html += "SSID:<br><input type='text' name='s' value='" + String(config.ssid) + "'><br>";
html += "Hasło:<br><input type='password' name='p'><br>";
html += "<input type='submit' value='Zapisz i Restartuj'></form></body></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 // Konwersja char na IPAddress
bool WiFiManager::convertCharToIPAddress(const char *str, IPAddress& ip) { 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; int retries = 0;
while (WiFi.status() != WL_CONNECTED && retries < 20) { while (WiFi.status() != WL_CONNECTED && retries < 20) {
delay(500); delay(500);
@@ -116,6 +61,44 @@ void WiFiManager::connectToWiFi() {
updateLED(); 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) { if (WiFi.status() == WL_CONNECTED) {
ESP_LOGI(WIFI, "SSID: %s", config.ssid); ESP_LOGI(WIFI, "SSID: %s", config.ssid);
String ipString = "IP: " + WiFi.localIP().toString(); String ipString = "IP: " + WiFi.localIP().toString();
@@ -125,7 +108,7 @@ void WiFiManager::connectToWiFi() {
String dnsInfo = "DNS: " + WiFi.dnsIP().toString(); String dnsInfo = "DNS: " + WiFi.dnsIP().toString();
ESP_LOGI(WIFI, "%s", dnsInfo.c_str()); ESP_LOGI(WIFI, "%s", dnsInfo.c_str());
} else { } else {
String infoWiFi = "WIFI CONNECTION ERROR: " + String(config.ssid); String infoWiFi = "WIFI CONNECTION ERROR: ALL FAILED";
ESP_LOGI(WIFI, "%s", infoWiFi.c_str()); ESP_LOGI(WIFI, "%s", infoWiFi.c_str());
} }
updateLED(); updateLED();
@@ -165,31 +148,31 @@ bool WiFiManager::isWiFiOK(){
void WiFiManager::checkWiFiConnection() { void WiFiManager::checkWiFiConnection() {
if (!isAccessPoint) { if (!isAccessPoint) {
getRSSI(); getRSSI();
if (WiFi.status() != WL_CONNECTED) { if (WiFi.status() != WL_CONNECTED) {
String infoWiFi = "WiFi reconnecting: " + String(config.ssid); String infoWiFi = "WiFi reconnecting: " + String(config.ssid);
ESP_LOGI(WIFI, "%s", infoWiFi.c_str()); ESP_LOGI(WIFI, "%s", infoWiFi.c_str());
WiFi.reconnect(); WiFi.reconnect();
int retries = 0; int retries = 0;
while (WiFi.status() != WL_CONNECTED && retries < 20) { while (WiFi.status() != WL_CONNECTED && retries < 20) {
delay(500); delay(500);
retries++; retries++;
updateLED(); updateLED();
} }
if (WiFi.status() == WL_CONNECTED) { if (WiFi.status() == WL_CONNECTED) {
//String infoWiFi = "WiFi connected: " + String(config.ssid); //String infoWiFi = "WiFi connected: " + String(config.ssid);
//ESP_LOGI(WIFI, "%s", infoWiFi.c_str()); //ESP_LOGI(WIFI, "%s", infoWiFi.c_str());
//String ipString = "IP: " + WiFi.localIP().toString(); //String ipString = "IP: " + WiFi.localIP().toString();
//ESP_LOGI(WIFI, "%s", ipString.c_str()); //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(); getRSSI();
} setupMDNS();
} } else {
updateLED(); 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<void(int
} }
// 4) Konfiguracja klienta i wywołanie aktualizacji // 4) Konfiguracja klienta i wywołanie aktualizacji
httpUpdate.rebootOnUpdate(true); // po sukcesie reboot httpUpdate.rebootOnUpdate(true); // po sukcesie reboot
t_httpUpdate_return ret; t_httpUpdate_return ret;
if (url.startsWith("https://")) { if (url.startsWith("https://")) {
@@ -279,7 +262,7 @@ bool WiFiManager::performOTAUpdate(bool allowInsecureTLS, std::function<void(int
// 5) Obsługa rezultatów // 5) Obsługa rezultatów
switch (ret) { switch (ret) {
case HTTP_UPDATE_OK: case HTTP_UPDATE_OK:
ESP_LOGI(WIFI, "[OTA] Sukces - restart nastąpi za chwilę."); ESP_LOGI(WIFI, "[OTA] Sukces restart nastąpi za chwilę.");
return true; // reboot i tak zaraz nastąpi return true; // reboot i tak zaraz nastąpi
case HTTP_UPDATE_NO_UPDATES: case HTTP_UPDATE_NO_UPDATES:
ESP_LOGW(WIFI, "[OTA] Brak nowej wersji (304/Not Modified)."); ESP_LOGW(WIFI, "[OTA] Brak nowej wersji (304/Not Modified).");
@@ -289,4 +272,90 @@ bool WiFiManager::performOTAUpdate(bool allowInsecureTLS, std::function<void(int
ESP_LOGE(WIFI, "[OTA] Błąd (%d): %s", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); ESP_LOGE(WIFI, "[OTA] Błąd (%d): %s", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str());
return false; return false;
} }
}
void WiFiManager::startCaptivePortal() {
ESP_LOGI(WIFI, "Starting Captive Portal");
setupAccessPoint(ssidAP.c_str(), passwordAP.c_str());
// Start DNS Server
const byte DNS_PORT = 53;
dnsServer.start(DNS_PORT, "*", WiFi.softAPIP());
// Start Web Server
server.on("/", std::bind(&WiFiManager::handleRoot, this));
server.on("/save", HTTP_POST, std::bind(&WiFiManager::handleSave, this));
server.onNotFound(std::bind(&WiFiManager::handleNotFound, this));
server.begin();
captivePortalActive = true;
ESP_LOGI(WIFI, "Captive Portal Started");
}
void WiFiManager::handleClient() {
if (captivePortalActive) {
dnsServer.processNextRequest();
server.handleClient();
}
}
void WiFiManager::handleRoot() {
int bases[] = {2, 2, 2, 2, 3, 3, 10, 10};
int vals[] = {4, 8, 16, 32, 9, 27, 100, 1000};
int answers[] = {2, 3, 4, 5, 2, 3, 2, 3};
int idx = random(0, 8);
int base = bases[idx];
int val = vals[idx];
expectedCaptchaAnswer = answers[idx];
String html = "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
html += "<style>body{font-family: Arial, sans-serif; text-align: center; margin:0; padding: 20px;}";
html += "input[type=text], input[type=password], input[type=number] {width: 100%; padding: 12px; margin: 8px 0; display: inline-block; border: 1px solid #ccc; box-sizing: border-box;}";
html += "input[type=submit] {background-color: #4CAF50; color: white; padding: 14px 20px; margin: 8px 0; border: none; cursor: pointer; width: 100%;}";
html += "input[type=submit]:hover {opacity: 0.8;}";
html += ".container {max-width: 400px; margin: auto; padding: 20px; border: 1px solid #ddd; border-radius: 5px; box-shadow: 0 4px 8px rgba(0,0,0,0.1);}</style></head><body>";
html += "<h2>WiFi Configuration</h2>";
html += "<div class=\"container\">";
html += "<form action=\"/save\" method=\"POST\">";
html += "<label for=\"ssid\"><b>SSID</b></label>";
html += "<input type=\"text\" placeholder=\"Enter SSID\" name=\"ssid\" required>";
html += "<label for=\"password\"><b>Password</b></label>";
html += "<input type=\"password\" placeholder=\"Enter Password\" name=\"password\" required>";
html += "<label for=\"captcha\"><b>Captcha: What is log<sub>" + String(base) + "</sub>(" + String(val) + ")?</b></label>";
html += "<input type=\"number\" placeholder=\"Enter Answer\" name=\"captcha\" required>";
html += "<input type=\"submit\" value=\"Save and Restart\">";
html += "</form></div></body></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", "");
} }

View File

@@ -30,6 +30,10 @@ bool Settings::isBtnReset() {
return (digitalRead(BTN_UP) == LOW && digitalRead(BTN_DOWN) == LOW); 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 Ustawienie opcji
@@ -141,7 +145,7 @@ int Settings::editField(int value, int minVal, int maxVal, const char *label) {
lastDown = down; lastDown = down;
lastOk = ok; lastOk = ok;
delay(80); // prosty debounce + ograniczenie odpytywania delay(80); //debouncing
} }
} }

117
src/UploadManager.cpp Normal file
View File

@@ -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.");
}

View File

@@ -15,7 +15,8 @@
#include <Measure.h> #include <Measure.h>
#include <Tool.h> #include <Tool.h>
#include <Settings.h> #include <Settings.h>
#include <Uploader.h> // Dodano moduł wysyłki #include "APIClient.h"
#include "UploadManager.h"
#define WDT_TIMEOUT 60 // Czas watchdoga do restartu #define WDT_TIMEOUT 60 // Czas watchdoga do restartu
#define MAX_ADXL345_SENSORS 4 // Maksymalna ilość podłączanych sensorów #define MAX_ADXL345_SENSORS 4 // Maksymalna ilość podłączanych sensorów
@@ -23,7 +24,7 @@
SPIClass SPI_ADXL(FSPI); // SPI2 (VSPI) SPIClass SPI_ADXL(FSPI); // SPI2 (VSPI)
SPIClass SPI_SD(HSPI); // SPI3 (HSPI) 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; String name;
bool isRebootRequired = false; bool isRebootRequired = false;
@@ -46,14 +47,13 @@ Settings settings(display, rtc); // obsługa przycisków
WiFiManager wifi; WiFiManager wifi;
DataCapture capture(adxl, display, rtc, SD, 8192); // NEW !!! MEASURE!!! DataCapture capture(adxl, display, rtc, SD, 8192); // NEW !!! MEASURE!!!
APIClient apiClient;
// Inicjalizacja Uploadera UploadManager uploadManager(apiClient, rtc, capture);
Uploader uploader(display);
Thread wifiTestThread = Thread(); // Cykliczny test WiFi Thread wifiTestThread = Thread(); // Cykliczny test WiFi
Thread offlineThread = Thread(); // Jeśli offline Thread offlineThread = Thread(); // Jeśli offline
Thread measureThread = Thread(); // Pomiar i zapis na SD Thread measureThread = Thread(); // Pomiar i zapis na SD
Thread uploadThread = Thread(); // Wątek wysyłki SSL Thread uploadThread = Thread(); // Background upload
//////// PROTOTYPY ///////////////// //////// PROTOTYPY /////////////////
void setup(); void setup();
@@ -63,10 +63,7 @@ void resetBtnClick();
void checkWiFi(); void checkWiFi();
void showOfflineScreen(); void showOfflineScreen();
void measure(); void measure();
void runUploader(); void setClockRTCBtn();
void toogleMode();
void settingsDevice();
void showError(String err);
/* ******************* SETUP() ************************* */ /* ******************* SETUP() ************************* */
void setup() { void setup() {
@@ -84,8 +81,8 @@ void setup() {
ESP_LOGI(TAG_MAIN, "Rejestrator parametrow"); ESP_LOGI(TAG_MAIN, "Rejestrator parametrow");
ESP_LOGI(TAG_MAIN, "Firmware: %s", VERSION); ESP_LOGI(TAG_MAIN, "Firmware: %s", VERSION);
ESP_LOGI(TAG_MAIN, "----------------------"); ESP_LOGI(TAG_MAIN, "----------------------");
ESP_LOGI(TAG_MAIN, "ESP32 model: %s Rev %d", ESP.getChipModel(), ESP.getChipRevision()); 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, "Chip cores: %d", ESP.getChipCores());
// Inicjalizacja Watchdoga na 5 sek, panic_on_trigger = true // Inicjalizacja Watchdoga na 5 sek, panic_on_trigger = true
if (Watchdog::init(25, true)) { if (Watchdog::init(25, true)) {
@@ -99,6 +96,8 @@ void setup() {
configManager.begin(); // konfiguracja EEPROM urządzenia configManager.begin(); // konfiguracja EEPROM urządzenia
//configManager.showConfig();
// Test LCD I2C // Test LCD I2C
if (!isI2CDevPresent(0x27)) { if (!isI2CDevPresent(0x27)) {
ESP_LOGE(TAG_MAIN, "LCD 0x27 wire error!"); ESP_LOGE(TAG_MAIN, "LCD 0x27 wire error!");
@@ -158,6 +157,10 @@ void setup() {
settings.setTimeRTC(); settings.setTimeRTC();
} }
} }
// Przycisk ustawianai czasu: przytrzymaj OK przy starcie systemu
if(settings.isSetClock()) setClockRTCBtn();
delay(500); delay(500);
// Karta SD // 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); 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); SPI_SD.begin(SD_SCK, SD_MISO, SD_MOSI);
display.textStatus("SD CARD:"); 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"); ESP_LOGE(TAG_MAIN, "SD mount failed");
display.print("FAILED"); display.print("FAILED");
delay(4000); delay(4000);
@@ -181,7 +184,8 @@ void setup() {
// Inicjalizacja ADXL345 // Inicjalizacja ADXL345
ESP_LOGI(TAG_MAIN, "ADXL345 init"); ESP_LOGI(TAG_MAIN, "ADXL345 init");
display.textStatus("ADXL345: "); 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"); ESP_LOGE(TAG_MAIN, "ADXL345 Error");
display.print("FAILED"); display.print("FAILED");
isAccelExists = false; isAccelExists = false;
@@ -208,36 +212,23 @@ void setup() {
delay(1000); delay(1000);
if(config.connect){ if(config.connect){
display.textStatus("WiFi Connect...");
wifi.begin(); wifi.begin();
wifiTestThread.onRun(checkWiFi);
int retry_count = 0; wifiTestThread.setInterval(5000); // Test WiFi co 3 sekundy
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 { } else {
offlineThread.onRun(showOfflineScreen); offlineThread.onRun(showOfflineScreen);
offlineThread.setInterval(1000); offlineThread.setInterval(1000); // Jeśli offline, to wyświetl ekran co 1 sek
} }
measureThread.onRun(measure); 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()) { if (Watchdog::addThisTask()) {
ESP_LOGI(TAG_MAIN, "Added main task to Watchdog"); ESP_LOGI(TAG_MAIN, "Added main task to Watchdog");
} }
@@ -246,14 +237,23 @@ void setup() {
display.clear(); display.clear();
} }
void runUploader() { // Factory reset
if (!testingNow) { void setClockRTCBtn(){
uploader.processQueue(3); 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(){ 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."); ESP_LOGI(TAG_MAIN, "RESET BTN to 10 sec.");
while(settings.isBtnReset()){ while(settings.isBtnReset()){
Serial.print(counter); Serial.print(","); Serial.print(counter); Serial.print(",");
@@ -280,6 +280,8 @@ void resetBtnClick(){
} }
} }
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
void showError(String err){ void showError(String err){
Watchdog::feed(); Watchdog::feed();
display.clear(); display.clear();
@@ -288,6 +290,7 @@ void showError(String err){
delay(3000); delay(3000);
} }
void checkWiFi(){ void checkWiFi(){
Watchdog::feed(); Watchdog::feed();
bool isConnected = WiFi.isConnected(); bool isConnected = WiFi.isConnected();
@@ -297,6 +300,7 @@ void checkWiFi(){
display.updateNetwork(ip, isConnected); display.updateNetwork(ip, isConnected);
} }
////// Ekran główny tylko gdy brak pomiaru /////////////
void showOfflineScreen(){ void showOfflineScreen(){
if((!config.connect) && (!testingNow)) { if((!config.connect) && (!testingNow)) {
bool isGB; bool isGB;
@@ -306,23 +310,26 @@ void showOfflineScreen(){
} }
} }
void reboot() { void reboot() {
display.clear(); display.clear();
display.textCenter(1, "SYSTEM"); display.textCenter(1, "SYSTEM");
display.textCenter(2, "RESTARTING"); display.textCenter(2, "RESTARTING");
#if defined(ARDUINO_RASPBERRY_PI_PICO) #if defined(ARDUINO_RASPBERRY_PI_PICO)
watchdog_enable(1, 1); watchdog_enable(1, 1); // 1 ms timeout, restart po upływie
while (true); while (true); // czekaj na watchdog reset
#endif #endif
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
ESP_LOGI(TAG_MAIN, "RESTART"); ESP_LOGI(TAG_MAIN, "RESTART");
ESP.restart(); ESP.restart(); // restart ESP
#endif #endif
#if defined(ARDUINO_ARCH_STM32) #if defined(ARDUINO_ARCH_STM32)
NVIC_SystemReset(); NVIC_SystemReset(); // restartuj STM32
#endif #endif
} }
// pomiar z Thread START POMIARU TUTAJ
void measure(){ void measure(){
Watchdog::feed(); Watchdog::feed();
testingNow = true; testingNow = true;
@@ -334,7 +341,7 @@ void measure(){
snprintf(btime, sizeof(btime), "%02d:%02d:%02d", now.hour(), now.minute(), now.second()); 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); display.initMeasure(config.measure, testingNow, runMeasure, config.pause, config.duration, config.connect, licznik, bdate, btime);
ESP_LOGI(TAG_MAIN, "MEASURE RUNNING"); ESP_LOGI(TAG_MAIN, "MEASURE RUNNING");
//delay(1000);
capture.captureAuto(config.duration, "/"); capture.captureAuto(config.duration, "/");
testingNow = false; testingNow = false;
Watchdog::feed(); Watchdog::feed();
@@ -344,14 +351,21 @@ void measure(){
ESP_LOGI(TAG_MAIN, "MEASURE FINISH"); ESP_LOGI(TAG_MAIN, "MEASURE FINISH");
} else { } else {
ESP_LOGI(TAG_MAIN, "MEASURE INTERRUPT"); 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; runMeasure = false;
ESP_LOGI(TAG_MAIN, "DISPLAY Offline"); ESP_LOGI(TAG_MAIN, "DISPLAY Offline");
display.displayOffline(testingNow, adxl.connectedSensorsCount(), capture.freeSpaceFloat(), licznik, true); display.displayOffline(testingNow, adxl.connectedSensorsCount(), capture.freeSpaceFloat(), licznik, true);
offlineThread.enabled = true; offlineThread.enabled = true;
} }
void toogleMode(){ void toogleMode(){ // tryb ciągłego, pojedynczego pomiaru
config.measure = !config.measure; config.measure = !config.measure;
configManager.saveConfig(); configManager.saveConfig();
display.textStatus("Mode changed"); display.textStatus("Mode changed");
@@ -385,18 +399,30 @@ void loop() {
if(settings.isPressed(3)) settingsDevice(); // DOWN if(settings.isPressed(3)) settingsDevice(); // DOWN
if(settings.isPressed(1)) toogleMode(); // UP 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(testingNow) {
if((runMeasure) && (settings.isPressed(2))){ if((runMeasure) && (settings.isPressed(2))){
runMeasure = false; runMeasure = false;
display.textStatus("STOPPING. WAIT"); display.textStatus("STOPPING. WAIT");
} }
return; return; // jeśli trwa akurat test, nic nie rób
} }
if(wifiTestThread.shouldRun() && (config.connect)) if(wifiTestThread.shouldRun() && (config.connect))
wifiTestThread.run(); wifiTestThread.run();
if(uploadThread.shouldRun() && (config.connect)) if(uploadThread.shouldRun() && config.connect)
uploadThread.run(); uploadThread.run();
if(offlineThread.shouldRun() && (!config.connect)){ if(offlineThread.shouldRun() && (!config.connect)){
@@ -423,11 +449,12 @@ void loop() {
if (isRebootRequired) { if (isRebootRequired) {
ESP_LOGI(TAG_MAIN, "Reboot required"); ESP_LOGI(TAG_MAIN, "Reboot required");
Watchdog::feed(); Watchdog::feed();
delay(1000); delay(1000);
reboot(); reboot();
} }
wifi.handleClient();
Watchdog::feed(); Watchdog::feed();
delay(20); delay(20); // 100
licznik ++; licznik ++;
} }