#include <WiFi.h>
#include <WebServer.h>
#include <SPI.h>
#include <SD.h>
#include <Wire.h>
#include <U8g2lib.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
// ================= KONFIGURACE =================
const float TOTAL_TRAVEL_MM = 160.0;
const float ADC_TO_MM = 0.05;
const int LOG_INTERVAL_MS = 20;
// ================= PINY (Heltec V3) =================
#define POT_PIN 2 // ZMĚNA: Přesunuto na GPIO 2
#define BTN_PIN 0 // Integrované tlačítko "PRG"
#define BAT_ADC 1 // ZMĚNA: Dle schématu je baterie na GPIO 1
#define I2C_SDA 41
#define I2C_SCL 42
#define SD_CS 7 // Nové piny pro SD kartu
#define SD_SCK 6
#define SD_MOSI 5
#define SD_MISO 4
// ================= OBJEKTY =================
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
Adafruit_MPU6050 mpu1;
Adafruit_MPU6050 mpu2;
WebServer server(80);
File logFile;
// ================= PROMĚNNÉ =================
bool isRecording = false;
int zero_adc = 0;
float current_max_mm = 0.0;
unsigned long lastLogTime = 0;
bool sd_ok = false;
bool mpu1_ok = false;
bool mpu2_ok = false;
String currentLogFileName = "";
bool lastBtnState = HIGH;
// --- FUNKCE PRO MĚŘENÍ BATERIE ---
float getBatteryVoltage() {
uint16_t rawVbat = analogRead(BAT_ADC);
return (rawVbat / 4095.0) * 2.0 * 3.3 * 1.1;
}
// --- START/STOP ZÁZNAMU A WIFI ---
void startRecording() {
if (!isRecording) { // Odebrána podmínka sd_ok pro testování UI
isRecording = true;
current_max_mm = 0.0;
WiFi.mode(WIFI_OFF); // WiFi pryč
// Zkusíme otevřít soubor jen pokud je karta OK
if (sd_ok) {
int fileNum = 1;
while (true) {
currentLogFileName = "/log_" + String(fileNum) + ".csv";
if (!SD.exists(currentLogFileName)) break;
fileNum++;
}
logFile = SD.open(currentLogFileName, FILE_WRITE);
if (logFile) {
logFile.println("time_ms,travel_mm,accel1_z,accel2_z");
}
}
}
}
void stopRecording() {
if (isRecording) {
isRecording = false;
if (logFile) {
logFile.close();
}
WiFi.mode(WIFI_AP);
WiFi.softAP("MTB_Telemetrie", "");
server.begin();
}
}
// --- WEBOVÝ SERVER ---
void handleRoot() {
String html = "<html><head><meta name='viewport' content='width=device-width, initial-scale=1'>";
html += "<style>body{font-family:sans-serif; text-align:center; margin-top:30px; background-color:#121212; color:white;} .btn{padding:15px; font-size:18px; margin:10px; border-radius:10px; text-decoration:none; background-color:#007bff; color:white; display:inline-block; border:none;} li{margin: 15px 0; list-style-type: none;}</style></head><body>";
html += "<h1>Zaznamy z SD karty</h1><ul>";
if (sd_ok) {
File root = SD.open("/");
File file = root.openNextFile();
while (file) {
if (!file.isDirectory()) {
String fName = String(file.name());
if (fName.startsWith("/")) fName = fName.substring(1);
html += "<li><a href='/download?file=" + fName + "' class='btn'>" + fName + " (" + String(file.size() / 1024) + " KB)</a></li>";
}
file = root.openNextFile();
}
root.close();
} else {
html += "<li>Chyba SD karty! Nelze nacist soubory.</li>";
}
html += "</ul></body></html>";
server.send(200, "text/html", html);
}
void handleFileDownload() {
if (server.hasArg("file")) {
String filename = "/" + server.arg("file");
if (SD.exists(filename)) {
File file = SD.open(filename, FILE_READ);
server.streamFile(file, "text/csv");
file.close();
return;
}
}
server.send(404, "text/plain", "Soubor nenalezen.");
}
// ================= SETUP =================
void setup() {
Serial.begin(115200);
pinMode(POT_PIN, INPUT);
pinMode(BTN_PIN, INPUT_PULLUP);
analogReadResolution(12);
Wire.begin(I2C_SDA, I2C_SCL, 400000);
u8g2.begin();
if (mpu1.begin(0x68, &Wire)) { mpu1_ok = true; mpu1.setAccelerometerRange(MPU6050_RANGE_8_G); }
if (mpu2.begin(0x69, &Wire)) { mpu2_ok = true; mpu2.setAccelerometerRange(MPU6050_RANGE_16_G); }
SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
if (SD.begin(SD_CS)) {
sd_ok = true;
}
WiFi.mode(WIFI_AP);
WiFi.softAP("MTB_Telemetrie", "");
server.on("/", handleRoot);
server.on("/download", handleFileDownload);
server.begin();
delay(1000);
zero_adc = analogRead(POT_PIN);
}
// ================= LOOP =================
void loop() {
unsigned long currentMillis = millis();
// --- NEPRŮSTŘELNÉ TLAČÍTKO ---
bool btnState = digitalRead(BTN_PIN);
if (btnState == LOW && lastBtnState == HIGH) { // Reaguje na okamžik stisku
if (isRecording) {
stopRecording();
} else {
startRecording();
}
delay(300); // Debounce pauza
}
lastBtnState = btnState;
if (!isRecording) {
server.handleClient();
}
// Výpočet zanoření
int pot_now = analogRead(POT_PIN);
float travel_mm = abs(pot_now - zero_adc) * ADC_TO_MM;
if(travel_mm > TOTAL_TRAVEL_MM) travel_mm = TOTAL_TRAVEL_MM;
float travel_percent = (travel_mm / TOTAL_TRAVEL_MM) * 100.0;
if (travel_mm > current_max_mm) {
current_max_mm = travel_mm;
}
// --- ZÁPIS NA SD ---
if (isRecording && sd_ok && logFile && (currentMillis - lastLogTime >= LOG_INTERVAL_MS)) {
lastLogTime = currentMillis;
sensors_event_t a1, g1, t1, a2, g2, t2;
if (mpu1_ok) mpu1.getEvent(&a1, &g1, &t1);
if (mpu2_ok) mpu2.getEvent(&a2, &g2, &t2);
logFile.print(currentMillis); logFile.print(",");
logFile.print(travel_mm, 1); logFile.print(",");
logFile.print(mpu1_ok ? a1.acceleration.z : 0.0); logFile.print(",");
logFile.println(mpu2_ok ? a2.acceleration.z : 0.0);
}
// --- AKTUALIZACE DISPLEJE ---
static unsigned long lastDisp = 0;
if (currentMillis - lastDisp > 200) {
u8g2.clearBuffer();
if (isRecording) {
// 🔴 RECORDING
u8g2.setFont(u8g2_font_ncenB12_tr);
u8g2.setCursor(0, 25);
u8g2.print("* RECORDING *");
u8g2.setFont(u8g2_font_ncenB14_tr);
u8g2.setCursor(0, 55);
u8g2.print("Max: "); u8g2.print(current_max_mm, 1); u8g2.print("mm");
// Varování, pokud nejede SD karta
if (!sd_ok) {
u8g2.setFont(u8g2_font_5x8_tf);
u8g2.setCursor(80, 62);
u8g2.print("NO SD!");
}
} else {
// 🟢 IDLE
u8g2.setFont(u8g2_font_6x10_tf);
u8g2.setCursor(0, 10);
u8g2.print("IDLE");
u8g2.setCursor(85, 10);
u8g2.print(getBatteryVoltage(), 1); u8g2.print("V");
u8g2.setFont(u8g2_font_ncenB12_tr);
u8g2.setCursor(0, 32);
u8g2.print(travel_mm, 1); u8g2.print(" mm");
u8g2.setCursor(85, 32);
u8g2.print((int)travel_percent); u8g2.print("%");
u8g2.setFont(u8g2_font_6x10_tf);
u8g2.setCursor(0, 48);
u8g2.print("Session Max: "); u8g2.print(current_max_mm, 1); u8g2.print(" mm");
u8g2.setCursor(0, 62);
u8g2.print("IP: "); u8g2.print(WiFi.softAPIP().toString());
}
u8g2.sendBuffer();
lastDisp = currentMillis;
}
}