7 Commits

12 changed files with 1527 additions and 849 deletions

View File

@@ -68,6 +68,7 @@
// -> 6.90µl / Pulse // -> 6.90µl / Pulse
#define DEFAULT_PUMP_DOSE 7 #define DEFAULT_PUMP_DOSE 7
// --- System status enum with sentinel ---
typedef enum eSystem_Status typedef enum eSystem_Status
{ {
sysStat_Startup, sysStat_Startup,
@@ -76,7 +77,8 @@ typedef enum eSystem_Status
sysStat_Wash, sysStat_Wash,
sysStat_Purge, sysStat_Purge,
sysStat_Error, sysStat_Error,
sysStat_Shutdown sysStat_Shutdown,
SYSSTAT_COUNT // <- sentinel (must be last)
} tSystem_Status; } tSystem_Status;
// Enum for different sources of speed input // Enum for different sources of speed input
@@ -89,13 +91,10 @@ typedef enum SpeedSource_e
SOURCE_GPS, SOURCE_GPS,
SOURCE_CAN, SOURCE_CAN,
SOURCE_OBD2_KLINE, SOURCE_OBD2_KLINE,
SOURCE_OBD2_CAN SOURCE_OBD2_CAN,
SPEEDSOURCE_COUNT // <- sentinel (must be last)
} SpeedSource_t; } SpeedSource_t;
// String representation of SpeedSource enum
extern const char *SpeedSourceString[];
extern const size_t SpeedSourceString_Elements;
// Enum for GPS baud rates // Enum for GPS baud rates
typedef enum GPSBaudRate_e typedef enum GPSBaudRate_e
{ {
@@ -104,23 +103,29 @@ typedef enum GPSBaudRate_e
BAUD_19200, BAUD_19200,
BAUD_38400, BAUD_38400,
BAUD_57600, BAUD_57600,
BAUD_115200 BAUD_115200,
GPSBAUDRATE_COUNT // <- sentinel (must be last)
} GPSBaudRate_t; } GPSBaudRate_t;
// String representation of GPSBaudRate enum
extern const char *GPSBaudRateString[];
extern const size_t GPSBaudRateString_Elements;
// Enum for CAN bus sources // Enum for CAN bus sources
typedef enum CANSource_e typedef enum CANSource_e
{ {
KTM_890_ADV_R_2021, KTM_890_ADV_R_2021,
KTM_1290_SD_R_2023 KTM_1290_SD_R_2023,
CANSOURCE_COUNT // <- sentinel (must be last)
} CANSource_t; } CANSource_t;
// String representation of CANSource enum // String tables (kept internal to the module)
extern const char *CANSourceString[]; extern const char * const SystemStatusString[SYSSTAT_COUNT];
extern const size_t CANSourceString_Elements; extern const char * const SpeedSourceString[SPEEDSOURCE_COUNT];
extern const char * const GPSBaudRateString[GPSBAUDRATE_COUNT];
extern const char * const CANSourceString[CANSOURCE_COUNT];
// Safe getters (centralized bounds check)
const char* ToString(SpeedSource_t v);
const char* ToString(GPSBaudRate_t v);
const char* ToString(CANSource_t v);
const char* ToString(tSystem_Status v);
#define STARTUP_DELAY 2500 #define STARTUP_DELAY 2500
#define SHUTDOWN_DELAY_MS 2500 #define SHUTDOWN_DELAY_MS 2500

View File

@@ -1,27 +1,26 @@
/** /**
* @file config.h * @file config.h
* @brief Configuration structures and EEPROM API for ChainLube firmware.
* *
* @brief Header file for configuration settings and EEPROM operations in the ChainLube application. * Defines EEPROM layout versions, configuration and persistence data structures,
* and the public functions for storing, loading, formatting and validating
* configuration/persistence records.
* *
* This file defines configuration settings for the ChainLube project, including default values, * Notes:
* EEPROM structures, and functions for EEPROM operations. It also defines enums for different sources * - The system always boots with defaults in RAM; EEPROM is optional.
* of speed input, GPS baud rates, and CAN bus sources. Additionally, it includes functions for EEPROM handling * - DTC handling for EEPROM availability and integrity is centralized in EEPROM_Process().
* such as storing, retrieving, and formatting configuration data.
*
* @author Marcel Peterkau
* @date 09.01.2024
*/ */
#ifndef _CONFIG_H_ #ifndef _CONFIG_H_
#define _CONFIG_H_ #define _CONFIG_H_
#include <Arduino.h> #include <stdint.h>
#include <Wire.h>
#include <I2C_eeprom.h> #include <I2C_eeprom.h>
#include "dtc.h" #include "dtc.h"
#include "common.h" #include "common.h"
#define EEPROM_STRUCTURE_REVISION 4 // Increment this version when changing EEPROM structures // Increment when EEPROM structure changes
#define EEPROM_STRUCTURE_REVISION 4
#if PCB_REV == 1 || PCB_REV == 2 || PCB_REV == 3 #if PCB_REV == 1 || PCB_REV == 2 || PCB_REV == 3
#define EEPROM_SIZE_BYTES I2C_DEVICESIZE_24LC64 #define EEPROM_SIZE_BYTES I2C_DEVICESIZE_24LC64
@@ -29,9 +28,14 @@
#define EEPROM_SIZE_BYTES I2C_DEVICESIZE_24LC256 #define EEPROM_SIZE_BYTES I2C_DEVICESIZE_24LC256
#endif #endif
/**
* @brief EEPROM request state machine codes.
*
* Used by globals.requestEEAction to schedule EEPROM operations.
*/
typedef enum EERequest_e typedef enum EERequest_e
{ {
EE_IDLE, EE_IDLE = 0,
EE_CFG_SAVE, EE_CFG_SAVE,
EE_CFG_LOAD, EE_CFG_LOAD,
EE_CFG_FORMAT, EE_CFG_FORMAT,
@@ -39,11 +43,13 @@ typedef enum EERequest_e
EE_PDS_LOAD, EE_PDS_LOAD,
EE_PDS_FORMAT, EE_PDS_FORMAT,
EE_FORMAT_ALL, EE_FORMAT_ALL,
EE_ALL_SAVE EE_ALL_SAVE,
EE_REINITIALIZE
} EERequest_t; } EERequest_t;
// Structure for persistence data stored in EEPROM /**
* @brief Wear-levelled persistence data block.
*/
typedef struct typedef struct
{ {
uint16_t writeCycleCounter; uint16_t writeCycleCounter;
@@ -54,7 +60,9 @@ typedef struct
uint32_t checksum; uint32_t checksum;
} persistenceData_t; } persistenceData_t;
// Structure for configuration settings stored in EEPROM /**
* @brief User configuration stored in EEPROM.
*/
typedef struct typedef struct
{ {
uint8_t EEPROM_Version; uint8_t EEPROM_Version;
@@ -85,7 +93,9 @@ typedef struct
uint32_t checksum; uint32_t checksum;
} LubeConfig_t; } LubeConfig_t;
// Default configuration settings /**
* @brief Factory defaults for configuration (in RAM).
*/
const LubeConfig_t LubeConfig_defaults = { const LubeConfig_t LubeConfig_defaults = {
0, 8000, 4000, 320, DEFAULT_PUMP_DOSE, 30, 1, 150, 70, 18, 2000, 25, 500, 10, SOURCE_IMPULSE, 0, 8000, 4000, 320, DEFAULT_PUMP_DOSE, 30, 1, 150, 70, 18, 2000, 25, 500, 10, SOURCE_IMPULSE,
BAUD_115200, BAUD_115200,
@@ -100,21 +110,31 @@ const LubeConfig_t LubeConfig_defaults = {
true, true,
0}; 0};
/* ==== Public API ==== */
// Initialization & main process
void InitEEPROM(); void InitEEPROM();
void EEPROM_Process(); void EEPROM_Process();
// Config & persistence access
void StoreConfig_EEPROM(); void StoreConfig_EEPROM();
void GetConfig_EEPROM(); void GetConfig_EEPROM();
void StorePersistence_EEPROM(); void StorePersistence_EEPROM();
void GetPersistence_EEPROM(); void GetPersistence_EEPROM();
void FormatConfig_EEPROM(); void FormatConfig_EEPROM();
void FormatPersistence_EEPROM(); void FormatPersistence_EEPROM();
void MovePersistencePage_EEPROM(boolean reset);
// Utilities
uint32_t Checksum_EEPROM(uint8_t const *data, size_t len); uint32_t Checksum_EEPROM(uint8_t const *data, size_t len);
void dumpEEPROM(uint16_t memoryAddress, uint16_t length); void dumpEEPROM(uint16_t memoryAddress, uint16_t length);
void MovePersistencePage_EEPROM(boolean reset);
uint32_t ConfigSanityCheck(bool autocorrect = false); uint32_t ConfigSanityCheck(bool autocorrect = false);
bool validateWiFiString(char *string, size_t size); bool validateWiFiString(char *string, size_t size);
/* ==== Externals ==== */
extern LubeConfig_t LubeConfig; extern LubeConfig_t LubeConfig;
extern persistenceData_t PersistenceData; extern persistenceData_t PersistenceData;
extern uint16_t eePersistenceMarker; extern uint16_t eePersistenceAddress;
#endif // _CONFIG_H_ #endif // _CONFIG_H_

View File

@@ -22,12 +22,11 @@ typedef struct Globals_s
{ {
tSystem_Status systemStatus = sysStat_Startup; /**< Current system status */ tSystem_Status systemStatus = sysStat_Startup; /**< Current system status */
tSystem_Status resumeStatus = sysStat_Startup; /**< Status to resume after rain mode */ tSystem_Status resumeStatus = sysStat_Startup; /**< Status to resume after rain mode */
char systemStatustxt[16] = ""; /**< Text representation of system status */
uint16_t purgePulses = 0; /**< Number of purge pulses */ uint16_t purgePulses = 0; /**< Number of purge pulses */
EERequest_t requestEEAction = EE_IDLE; /**< EEPROM-related request */ EERequest_t requestEEAction = EE_IDLE; /**< EEPROM-related request */
char DeviceName[33]; /**< Device name */ char DeviceName[33]; /**< Device name */
char FlashVersion[10]; /**< Flash version */ char FlashVersion[10]; /**< Flash version */
uint16_t eePersistanceAdress; /**< EEPROM persistence address */ uint16_t eePersistenceAddress; /**< EEPROM persistence address */
uint8_t TankPercentage; /**< Tank percentage */ uint8_t TankPercentage; /**< Tank percentage */
bool hasDTC; /**< Flag indicating the presence of Diagnostic Trouble Codes (DTC) */ bool hasDTC; /**< Flag indicating the presence of Diagnostic Trouble Codes (DTC) */
bool measurementActive; /**< Flag indicating active measurement */ bool measurementActive; /**< Flag indicating active measurement */

View File

@@ -3,8 +3,10 @@
#include <Arduino.h> #include <Arduino.h>
// === Funktionen === // Init MCP2515 und OBD2-CAN-Poller (non-blocking)
void Init_OBD2_CAN(); void Init_OBD2_CAN();
// Verarbeitet OBD2-CAN nicht-blockierend, integriert Strecke (mm) seit letztem Aufruf.
uint32_t Process_OBD2_CAN_Speed(); uint32_t Process_OBD2_CAN_Speed();
#endif #endif

View File

@@ -143,7 +143,7 @@ void sendCANDebugMessage()
data[5] = (0x01 & globals.hasDTC) | ((0x01 & globals.measurementActive) << 1); data[5] = (0x01 & globals.hasDTC) | ((0x01 & globals.measurementActive) << 1);
break; break;
case 2: case 2:
memcpy(&data[1], &globals.eePersistanceAdress, sizeof(globals.eePersistanceAdress)); memcpy(&data[1], &globals.eePersistenceAddress, sizeof(globals.eePersistenceAddress));
memcpy(&data[3], &PersistenceData.tankRemain_microL, sizeof(PersistenceData.tankRemain_microL)); memcpy(&data[3], &PersistenceData.tankRemain_microL, sizeof(PersistenceData.tankRemain_microL));
break; break;
case 3: case 3:

View File

@@ -1,7 +1,27 @@
#include "common.h" #include "common.h"
#define ARR_LEN(a) (sizeof(a)/sizeof((a)[0]))
static_assert(ARR_LEN(SystemStatusString) == SYSSTAT_COUNT, "SystemStatusString size mismatch");
static_assert(ARR_LEN(SpeedSourceString) == SPEEDSOURCE_COUNT, "SpeedSourceString size mismatch");
static_assert(ARR_LEN(GPSBaudRateString) == GPSBAUDRATE_COUNT, "GPSBaudRateString size mismatch");
static_assert(ARR_LEN(CANSourceString) == CANSOURCE_COUNT, "CANSourceString size mismatch");
static const char kUnknownStr[] = "Unknown";
// ---- System status string table ----
const char *const SystemStatusString[SYSSTAT_COUNT] = {
"Startup",
"Normal",
"Rain",
"Wash",
"Purge",
"Error",
"Shutdown",
};
// String representation of SpeedSource enum // String representation of SpeedSource enum
const char *SpeedSourceString[] = { const char *const SpeedSourceString[SPEEDSOURCE_COUNT] = {
#ifdef FEATURE_ENABLE_TIMER #ifdef FEATURE_ENABLE_TIMER
"Timer", "Timer",
#endif #endif
@@ -12,10 +32,8 @@ const char *SpeedSourceString[] = {
"OBD2 (CAN)", "OBD2 (CAN)",
}; };
const size_t SpeedSourceString_Elements = sizeof(SpeedSourceString) / sizeof(SpeedSourceString[0]);
// String representation of GPSBaudRate enum // String representation of GPSBaudRate enum
const char *GPSBaudRateString[] = { const char *const GPSBaudRateString[GPSBAUDRATE_COUNT] = {
"4800", "4800",
"9600", "9600",
"19200", "19200",
@@ -24,12 +42,49 @@ const char *GPSBaudRateString[] = {
"115200", "115200",
}; };
const size_t GPSBaudRateString_Elements = sizeof(GPSBaudRateString) / sizeof(GPSBaudRateString[0]);
// String representation of CANSource enum // String representation of CANSource enum
const char *CANSourceString[] = { const char *const CANSourceString[CANSOURCE_COUNT] = {
"KTM 890 Adventure R (2021)", "KTM 890 Adventure R (2021)",
"KTM 1290 Superduke R (2023)", "KTM 1290 Superduke R (2023)",
}; };
const size_t CANSourceString_Elements = sizeof(CANSourceString) / sizeof(CANSourceString[0]); // ---- Centralized, safe getters ----
// ---- Local helper for range check ----
static inline bool in_range(int v, int max_exclusive)
{
return (v >= 0) && (v < max_exclusive);
}
// ---- Safe getter ----
const char *ToString(tSystem_Status v)
{
const int i = static_cast<int>(v);
return in_range(i, static_cast<int>(SYSSTAT_COUNT))
? SystemStatusString[i]
: kUnknownStr;
}
const char *ToString(SpeedSource_t v)
{
const int i = static_cast<int>(v);
return in_range(i, static_cast<int>(SPEEDSOURCE_COUNT))
? SpeedSourceString[i]
: kUnknownStr;
}
const char *ToString(GPSBaudRate_t v)
{
const int i = static_cast<int>(v);
return in_range(i, static_cast<int>(GPSBAUDRATE_COUNT))
? GPSBaudRateString[i]
: kUnknownStr;
}
const char *ToString(CANSource_t v)
{
const int i = static_cast<int>(v);
return in_range(i, static_cast<int>(CANSOURCE_COUNT))
? CANSourceString[i]
: kUnknownStr;
}

View File

@@ -1,56 +1,181 @@
/** /**
* @file config.cpp * @file config.cpp
* @brief Implementation of EEPROM and configuration-related functions. * @brief EEPROM handling and configuration storage for the ChainLube firmware.
* *
* This file contains functions for managing EEPROM storage and handling configuration data. * Responsibilities:
* It includes the definitions of configuration structures, EEPROM access, and utility functions. * - Bring-up of the external I²C EEPROM
* - Robust availability checks with optional bus recovery
* - Central processing of EEPROM requests (save/load/format/move page)
* - CRC32 utilities and debug dump helpers
*
* Design notes:
* - The device boots with sane in-RAM defaults so the system stays operable
* even when EEPROM is missing. Actual lube execution is gated by DTCs elsewhere.
* - The DTC DTC_NO_EEPROM_FOUND is set/cleared only in EEPROM_Process(), never here ad-hoc.
* - Background recovery is non-blocking and driven by millis().
*/ */
#include <Arduino.h>
#include <Wire.h>
#include "config.h" #include "config.h"
#include "debugger.h" #include "debugger.h"
#include "globals.h" #include "globals.h"
// Instance of I2C_eeprom for EEPROM access // Recovery edge flag: set when availability changes 0 -> 1
static bool eeRecoveredOnce = false;
// Non-blocking retry scheduling
static uint32_t eeNextTryMs = 0;
static uint32_t eeRetryIntervalMs = 2000; // ms between background attempts
// I²C EEPROM instance
I2C_eeprom ee(0x50, EEPROM_SIZE_BYTES); I2C_eeprom ee(0x50, EEPROM_SIZE_BYTES);
// Configuration and persistence data structures // Configuration and persistence data
LubeConfig_t LubeConfig; LubeConfig_t LubeConfig;
persistenceData_t PersistenceData; persistenceData_t PersistenceData;
// EEPROM version identifier // EEPROM structure version (bumped when layout changes)
const uint16_t eeVersion = EEPROM_STRUCTURE_REVISION; const uint16_t eeVersion = EEPROM_STRUCTURE_REVISION;
// Flag indicating whether EEPROM is available // Latched availability flag
boolean eeAvailable = false; static bool eeAvailable = false;
// Offsets within EEPROM for LubeConfig and PersistenceData // EEPROM layout offsets
const uint16_t startofLubeConfig = 16; const uint16_t startofLubeConfig = 16;
const uint16_t startofPersistence = 16 + sizeof(LubeConfig) + (sizeof(LubeConfig) % 16); const uint16_t startofPersistence = 16 + sizeof(LubeConfig) + (sizeof(LubeConfig) % 16);
// Function prototype to check EEPROM availability // availability probe
boolean checkEEPROMavailable(); bool EEPROM_Available(bool recover = false, uint8_t attempts = 3, uint16_t delay_ms = 25);
// Robust EEPROM handling (internal helpers)
void I2C_BusReset();
bool TryRecoverEEPROM(uint8_t attempts = 5, uint16_t delay_ms = 50);
/** /**
* @brief Initializes EEPROM and checks its availability. * @brief Initialize I²C and EEPROM driver, load in-RAM defaults.
* *
* This function initializes the EEPROM using the I2C_eeprom instance and checks if it's available. * Loads defaults into RAM to keep the application operational.
* Availability is checked but no DTC is set here—EEPROM_Process() is the single place
* that sets/clears DTC_NO_EEPROM_FOUND.
*/ */
void InitEEPROM() void InitEEPROM()
{ {
LubeConfig = LubeConfig_defaults; LubeConfig = LubeConfig_defaults;
PersistenceData = {0}; PersistenceData = {0};
Wire.begin();
ee.begin(); ee.begin();
checkEEPROMavailable();
eeAvailable = ee.isConnected();
} }
/** /**
* @brief Processes EEPROM actions based on the request from the global state. * @brief Try to free a stuck I²C bus and enforce a STOP condition.
* *
* This function processes EEPROM actions based on the request from the global state. * Pulses SCL up to 9 times to release a held SDA, then issues a STOP (SDA ↑ while SCL ↑).
* It performs actions such as saving, loading, and formatting EEPROM data for both configuration and persistence. * Finally returns control to Wire.
*/
void I2C_BusReset()
{
pinMode(SCL, OUTPUT_OPEN_DRAIN);
pinMode(SDA, INPUT_PULLUP);
for (int i = 0; i < 9; i++)
{
digitalWrite(SCL, LOW);
delayMicroseconds(5);
digitalWrite(SCL, HIGH);
delayMicroseconds(5);
}
pinMode(SDA, OUTPUT_OPEN_DRAIN);
digitalWrite(SDA, LOW);
delayMicroseconds(5);
digitalWrite(SCL, HIGH);
delayMicroseconds(5);
digitalWrite(SDA, HIGH);
delayMicroseconds(5);
pinMode(SCL, INPUT);
pinMode(SDA, INPUT);
}
/**
* @brief Attempt to recover EEPROM connectivity.
*
* Sequence per attempt:
* - I²C bus reset
* - Wire.begin()
* - ee.begin()
* - short settle delay
*
* On first successful probe (0->1) the eeRecoveredOnce flag is raised.
*
* @param attempts Number of attempts
* @param delay_ms Delay between attempts (after ee.begin())
* @return true if EEPROM is reachable after recovery, false otherwise
*/
bool TryRecoverEEPROM(uint8_t attempts, uint16_t delay_ms)
{
for (uint8_t n = 0; n < attempts; n++)
{
I2C_BusReset();
// ESP8266 core: Wire.end() is not available; re-begin is sufficient
Wire.begin();
delay(2);
ee.begin();
delay(delay_ms);
if (ee.isConnected())
{
if (!eeAvailable)
eeRecoveredOnce = true; // edge 0 -> 1
eeAvailable = true;
return true;
}
}
eeAvailable = false;
return false;
}
/**
* @brief Central EEPROM task: background recovery, DTC handling, and request dispatch.
*
* Called periodically from the main loop. Non-blocking by design.
* - Schedules gentle recovery tries based on millis()
* - Sets DTC_NO_EEPROM_FOUND when unavailable
* - On successful recovery edge, clears DTC and reloads CFG/PDS exactly once
* - Executes requested actions (save/load/format/move)
*/ */
void EEPROM_Process() void EEPROM_Process()
{ {
// Background recovery (single soft attempt per interval)
const uint32_t now = millis();
if (!EEPROM_Available() && now >= eeNextTryMs)
{
(void)TryRecoverEEPROM(1, 10);
eeNextTryMs = now + eeRetryIntervalMs;
}
// Central DTC handling
if (!EEPROM_Available())
{
MaintainDTC(DTC_NO_EEPROM_FOUND, true);
}
// Recovery edge: clear DTC and reload persisted data exactly once
if (EEPROM_Available() && eeRecoveredOnce)
{
MaintainDTC(DTC_NO_EEPROM_FOUND, false);
GetConfig_EEPROM();
GetPersistence_EEPROM();
eeRecoveredOnce = false;
Debug_pushMessage("EEPROM recovered reloaded CFG/PDS\n");
}
// Request dispatcher
switch (globals.requestEEAction) switch (globals.requestEEAction)
{ {
case EE_CFG_SAVE: case EE_CFG_SAVE:
@@ -58,33 +183,39 @@ void EEPROM_Process()
globals.requestEEAction = EE_IDLE; globals.requestEEAction = EE_IDLE;
Debug_pushMessage("Stored EEPROM CFG\n"); Debug_pushMessage("Stored EEPROM CFG\n");
break; break;
case EE_CFG_LOAD: case EE_CFG_LOAD:
GetConfig_EEPROM(); GetConfig_EEPROM();
globals.requestEEAction = EE_IDLE; globals.requestEEAction = EE_IDLE;
Debug_pushMessage("Loaded EEPROM CFG\n"); Debug_pushMessage("Loaded EEPROM CFG\n");
break; break;
case EE_CFG_FORMAT: case EE_CFG_FORMAT:
FormatConfig_EEPROM(); FormatConfig_EEPROM();
globals.requestEEAction = EE_IDLE; globals.requestEEAction = EE_IDLE;
GetConfig_EEPROM(); GetConfig_EEPROM();
Debug_pushMessage("Formatted EEPROM CFG\n"); Debug_pushMessage("Formatted EEPROM CFG\n");
break; break;
case EE_PDS_SAVE: case EE_PDS_SAVE:
StorePersistence_EEPROM(); StorePersistence_EEPROM();
globals.requestEEAction = EE_IDLE; globals.requestEEAction = EE_IDLE;
Debug_pushMessage("Stored EEPROM PDS\n"); Debug_pushMessage("Stored EEPROM PDS\n");
break; break;
case EE_PDS_LOAD: case EE_PDS_LOAD:
GetPersistence_EEPROM(); GetPersistence_EEPROM();
globals.requestEEAction = EE_IDLE; globals.requestEEAction = EE_IDLE;
Debug_pushMessage("Loaded EEPROM PDS\n"); Debug_pushMessage("Loaded EEPROM PDS\n");
break; break;
case EE_PDS_FORMAT: case EE_PDS_FORMAT:
FormatPersistence_EEPROM(); FormatPersistence_EEPROM();
globals.requestEEAction = EE_IDLE; globals.requestEEAction = EE_IDLE;
GetPersistence_EEPROM(); GetPersistence_EEPROM();
Debug_pushMessage("Formatted EEPROM PDS\n"); Debug_pushMessage("Formatted EEPROM PDS\n");
break; break;
case EE_FORMAT_ALL: case EE_FORMAT_ALL:
FormatConfig_EEPROM(); FormatConfig_EEPROM();
FormatPersistence_EEPROM(); FormatPersistence_EEPROM();
@@ -93,73 +224,100 @@ void EEPROM_Process()
globals.requestEEAction = EE_IDLE; globals.requestEEAction = EE_IDLE;
Debug_pushMessage("Formatted EEPROM ALL\n"); Debug_pushMessage("Formatted EEPROM ALL\n");
break; break;
case EE_ALL_SAVE: case EE_ALL_SAVE:
StorePersistence_EEPROM(); StorePersistence_EEPROM();
StoreConfig_EEPROM(); StoreConfig_EEPROM();
globals.requestEEAction = EE_IDLE; globals.requestEEAction = EE_IDLE;
Debug_pushMessage("Stored EEPROM ALL\n"); Debug_pushMessage("Stored EEPROM ALL\n");
break; break;
case EE_REINITIALIZE:
{
// quick burst of attempts
const bool ok = TryRecoverEEPROM(5, 20);
if (ok)
{
// Edge & reload are handled by the block above
Debug_pushMessage("EEPROM reinitialize OK\n");
}
else
{
MaintainDTC(DTC_NO_EEPROM_FOUND, true);
Debug_pushMessage("EEPROM reinitialize FAILED\n");
}
globals.requestEEAction = EE_IDLE;
break;
}
case EE_IDLE: case EE_IDLE:
default: default:
globals.requestEEAction = EE_IDLE; globals.requestEEAction = EE_IDLE;
break;
} }
} }
/** /**
* @brief Stores the configuration data in EEPROM. * @brief Store configuration to EEPROM (with CRC and sanity report).
* *
* This function calculates the checksum for the configuration data, updates it, and stores it in EEPROM. * Writes only if EEPROM is available. On completion, DTC_EEPROM_CFG_SANITY is
* It also performs a sanity check on the configuration and raises a diagnostic trouble code (DTC) if needed. * raised if any config fields are out of plausible bounds (bitmask payload).
*/ */
void StoreConfig_EEPROM() void StoreConfig_EEPROM()
{ {
LubeConfig.checksum = 0; LubeConfig.checksum = 0;
LubeConfig.checksum = Checksum_EEPROM((uint8_t *)&LubeConfig, sizeof(LubeConfig)); LubeConfig.checksum = Checksum_EEPROM((uint8_t *)&LubeConfig, sizeof(LubeConfig));
if (!checkEEPROMavailable()) if (!EEPROM_Available())
return; return;
ee.updateBlock(startofLubeConfig, (uint8_t *)&LubeConfig, sizeof(LubeConfig)); ee.updateBlock(startofLubeConfig, (uint8_t *)&LubeConfig, sizeof(LubeConfig));
uint32_t ConfigSanityCheckResult = ConfigSanityCheck(false); const uint32_t sanity = ConfigSanityCheck(false);
if (sanity > 0)
if (ConfigSanityCheckResult > 0)
{ {
MaintainDTC(DTC_EEPROM_CFG_SANITY, true, ConfigSanityCheckResult); MaintainDTC(DTC_EEPROM_CFG_SANITY, true, sanity);
} }
} }
/** /**
* @brief Retrieves the configuration data from EEPROM. * @brief Load configuration from EEPROM and validate.
* *
* This function reads the configuration data from EEPROM, performs a checksum validation, * On CRC failure: raise DTC_EEPROM_CFG_BAD and fall back to in-RAM defaults (no writes).
* and conducts a sanity check on the configuration. It raises a diagnostic trouble code (DTC) if needed. * On CRC OK: run sanity with autocorrect=true and raise DTC_EEPROM_CFG_SANITY with bitmask if needed.
*/ */
void GetConfig_EEPROM() void GetConfig_EEPROM()
{ {
if (!checkEEPROMavailable()) if (!EEPROM_Available())
return; return;
ee.readBlock(startofLubeConfig, (uint8_t *)&LubeConfig, sizeof(LubeConfig)); ee.readBlock(startofLubeConfig, (uint8_t *)&LubeConfig, sizeof(LubeConfig));
uint32_t checksum = LubeConfig.checksum; const uint32_t checksum = LubeConfig.checksum;
LubeConfig.checksum = 0; LubeConfig.checksum = 0;
MaintainDTC(DTC_EEPROM_CFG_BAD, (Checksum_EEPROM((uint8_t *)&LubeConfig, sizeof(LubeConfig)) != checksum)); const bool badCrc = (Checksum_EEPROM((uint8_t *)&LubeConfig, sizeof(LubeConfig)) != checksum);
MaintainDTC(DTC_EEPROM_CFG_BAD, badCrc);
if (badCrc) {
// Dont keep corrupted data in RAM
LubeConfig = LubeConfig_defaults;
LubeConfig.EEPROM_Version = EEPROM_STRUCTURE_REVISION; // explicit in-RAM version
return;
}
// CRC OK → restore checksum and sanitize (with autocorrect)
LubeConfig.checksum = checksum; LubeConfig.checksum = checksum;
uint32_t ConfigSanityCheckResult = ConfigSanityCheck(false); const uint32_t sanity = ConfigSanityCheck(true);
MaintainDTC(DTC_EEPROM_CFG_SANITY, (sanity > 0), sanity);
MaintainDTC(DTC_EEPROM_CFG_SANITY, (ConfigSanityCheckResult > 0), ConfigSanityCheckResult);
} }
/** /**
* @brief Stores the persistence data in EEPROM. * @brief Store persistence record to EEPROM (wear-levelled page).
* *
* This function increments the write cycle counter, performs a checksum calculation on the persistence data, * Increments the write-cycle counter and moves the page if close to the limit.
* and stores it in EEPROM. It also handles EEPROM page movement when needed. * Writes only if EEPROM is available.
*/ */
void StorePersistence_EEPROM() void StorePersistence_EEPROM()
{ {
@@ -171,103 +329,108 @@ void StorePersistence_EEPROM()
PersistenceData.checksum = 0; PersistenceData.checksum = 0;
PersistenceData.checksum = Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)); PersistenceData.checksum = Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData));
if (!checkEEPROMavailable()) if (!EEPROM_Available())
return; return;
ee.updateBlock(globals.eePersistanceAdress, (uint8_t *)&PersistenceData, sizeof(PersistenceData)); ee.updateBlock(globals.eePersistenceAddress, (uint8_t *)&PersistenceData, sizeof(PersistenceData));
} }
/** /**
* @brief Retrieves the persistence data from EEPROM. * @brief Load persistence record, validating address range and CRC.
* *
* This function reads the EEPROM to get the start address of the persistence data. * If the stored start address is out of range, the persistence partition is reset,
* If the start address is out of range, it resets and stores defaults. Otherwise, * formatted, and DTC_EEPROM_PDSADRESS_BAD is raised.
* it reads from EEPROM and checks if the data is correct. * Otherwise, the record is read and checked; on CRC failure DTC_EEPROM_PDS_BAD is raised
* and the in-RAM persistence data is reset to a safe default (no writes performed here).
*/ */
void GetPersistence_EEPROM() void GetPersistence_EEPROM()
{ {
if (!checkEEPROMavailable()) if (!EEPROM_Available())
return; return;
ee.readBlock(0, (uint8_t *)&globals.eePersistanceAdress, sizeof(globals.eePersistanceAdress)); // Read wear-level start address
// if we got the StartAdress of Persistance and it's out of Range - we Reset it and store defaults ee.readBlock(0, (uint8_t *)&globals.eePersistenceAddress, sizeof(globals.eePersistenceAddress));
// otherwise we Read from eeprom and check if everything is correct
if (globals.eePersistanceAdress < startofPersistence || globals.eePersistanceAdress > ee.getDeviceSize()) const uint16_t addr = globals.eePersistenceAddress;
const uint16_t need = sizeof(PersistenceData);
const uint16_t dev = ee.getDeviceSize();
// Strict range check: addr must be within partition and block must fit into device
if (addr < startofPersistence || (uint32_t)addr + (uint32_t)need > (uint32_t)dev)
{ {
MovePersistencePage_EEPROM(true); MovePersistencePage_EEPROM(true);
FormatPersistence_EEPROM(); FormatPersistence_EEPROM();
MaintainDTC(DTC_EEPROM_PDSADRESS_BAD, true); MaintainDTC(DTC_EEPROM_PDSADRESS_BAD, true);
return;
} }
else
{
ee.readBlock(globals.eePersistanceAdress, (uint8_t *)&PersistenceData, sizeof(PersistenceData));
uint32_t checksum = PersistenceData.checksum; // Safe to read the record
ee.readBlock(addr, (uint8_t *)&PersistenceData, sizeof(PersistenceData));
const uint32_t checksum = PersistenceData.checksum;
PersistenceData.checksum = 0; PersistenceData.checksum = 0;
MaintainDTC(DTC_EEPROM_PDS_BAD, (Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)) != checksum)); const bool badCrc = (Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)) != checksum);
MaintainDTC(DTC_EEPROM_PDS_BAD, badCrc);
PersistenceData.checksum = checksum; if (badCrc)
{
// Do not keep corrupted data in RAM; leave DTC set, no EEPROM writes here
PersistenceData = {0};
return;
} }
// CRC ok -> restore checksum into the struct kept in RAM
PersistenceData.checksum = checksum;
} }
/** /**
* @brief Formats the configuration partition in EEPROM. * @brief Reset the configuration partition to defaults and write it.
*
* This function resets the configuration data to defaults and stores it in EEPROM.
*/ */
void FormatConfig_EEPROM() void FormatConfig_EEPROM()
{ {
Debug_pushMessage("Formatting Config-Partition\n"); Debug_pushMessage("Formatting Config partition\n");
LubeConfig = LubeConfig_defaults; LubeConfig = LubeConfig_defaults;
LubeConfig.EEPROM_Version = eeVersion; LubeConfig.EEPROM_Version = eeVersion;
StoreConfig_EEPROM(); StoreConfig_EEPROM();
} }
/** /**
* @brief Formats the persistence partition in EEPROM. * @brief Reset the persistence partition and write an empty record.
*
* This function resets the persistence data to defaults and stores it in EEPROM.
*/ */
void FormatPersistence_EEPROM() void FormatPersistence_EEPROM()
{ {
Debug_pushMessage("Formatting Persistance-Partition\n"); Debug_pushMessage("Formatting Persistence partition\n");
PersistenceData = {0}; PersistenceData = {0};
// memset(&PersistenceData, 0, sizeof(PersistenceData));
StorePersistence_EEPROM(); StorePersistence_EEPROM();
} }
/** /**
* @brief Moves the persistence page in EEPROM. * @brief Advance the persistence page (wear levelling) and store the new start address.
* *
* This function adjusts the persistence page address and resets the write cycle counter. * When end-of-device (or reset=true), wrap back to startofPersistence.
* Requires EEPROM availability.
* *
* @param reset If true, the function resets the persistence page address to the start of the partition. * @param reset If true, force wrap to the start of the partition.
*/ */
void MovePersistencePage_EEPROM(boolean reset) void MovePersistencePage_EEPROM(boolean reset)
{ {
if (!checkEEPROMavailable()) if (!EEPROM_Available())
return; return;
globals.eePersistanceAdress += sizeof(PersistenceData); globals.eePersistenceAddress += sizeof(PersistenceData);
PersistenceData.writeCycleCounter = 0; PersistenceData.writeCycleCounter = 0;
// Check if we reached the end of the EEPROM and start over at the beginning if ((globals.eePersistenceAddress + sizeof(PersistenceData)) > ee.getDeviceSize() || reset)
if ((globals.eePersistanceAdress + sizeof(PersistenceData)) > ee.getDeviceSize() || reset)
{ {
globals.eePersistanceAdress = startofPersistence; globals.eePersistenceAddress = startofPersistence;
} }
ee.updateBlock(0, (uint8_t *)&globals.eePersistanceAdress, sizeof(globals.eePersistanceAdress)); ee.updateBlock(0, (uint8_t *)&globals.eePersistenceAddress, sizeof(globals.eePersistenceAddress));
} }
/** /**
* @brief Calculate CRC-32 checksum for a block of data. * @brief Compute CRC-32 (poly 0xEDB88320) over a byte buffer.
*
* This function implements the CRC-32 algorithm.
*
* @param data Pointer to the data block.
* @param len Length of the data block in bytes.
* @return CRC-32 checksum.
*/ */
uint32_t Checksum_EEPROM(uint8_t const *data, size_t len) uint32_t Checksum_EEPROM(uint8_t const *data, size_t len)
{ {
@@ -275,55 +438,43 @@ uint32_t Checksum_EEPROM(uint8_t const *data, size_t len)
return 0; return 0;
uint32_t crc = 0xFFFFFFFF; uint32_t crc = 0xFFFFFFFF;
uint32_t mask;
while (len--) while (len--)
{ {
crc ^= *data++; crc ^= *data++;
for (uint8_t k = 0; k < 8; k++) for (uint8_t k = 0; k < 8; k++)
{ crc = (crc >> 1) ^ (0xEDB88320 & (-(int32_t)(crc & 1)));
mask = -(crc & 1);
crc = (crc >> 1) ^ (0xEDB88320 & mask);
} }
}
return ~crc; return ~crc;
} }
/** /**
* @brief Dump a portion of EEPROM contents for debugging. * @brief Print a hex/ASCII dump of a region of the EEPROM for debugging.
* *
* This function prints the contents of a specified portion of EEPROM in a formatted way. * Output format:
* * Address 00 01 02 ... 0F ASCII
* @param memoryAddress Starting address in EEPROM. * 0x00000: XX XX ... .....
* @param length Number of bytes to dump.
*/ */
void dumpEEPROM(uint16_t memoryAddress, uint16_t length) void dumpEEPROM(uint16_t memoryAddress, uint16_t length)
{ {
#define BLOCK_TO_LENGTH 16 #define BLOCK_TO_LENGTH 16
if (!checkEEPROMavailable()) if (!EEPROM_Available())
return; return;
char ascii_buf[BLOCK_TO_LENGTH + 1]; char ascii_buf[BLOCK_TO_LENGTH + 1];
sprintf(ascii_buf, "%*s", BLOCK_TO_LENGTH, "ASCII"); sprintf(ascii_buf, "%*s", BLOCK_TO_LENGTH, "ASCII");
// Print column headers
Debug_pushMessage(PSTR("\nAddress ")); Debug_pushMessage(PSTR("\nAddress "));
for (int x = 0; x < BLOCK_TO_LENGTH; x++) for (int x = 0; x < BLOCK_TO_LENGTH; x++)
Debug_pushMessage("%3d", x); Debug_pushMessage("%3d", x);
// Align address and length to BLOCK_TO_LENGTH boundaries memoryAddress = (memoryAddress / BLOCK_TO_LENGTH) * BLOCK_TO_LENGTH;
memoryAddress = memoryAddress / BLOCK_TO_LENGTH * BLOCK_TO_LENGTH; length = ((length + BLOCK_TO_LENGTH - 1) / BLOCK_TO_LENGTH) * BLOCK_TO_LENGTH;
length = (length + BLOCK_TO_LENGTH - 1) / BLOCK_TO_LENGTH * BLOCK_TO_LENGTH;
// Iterate through the specified portion of EEPROM
for (unsigned int i = 0; i < length; i++) for (unsigned int i = 0; i < length; i++)
{ {
int blockpoint = memoryAddress % BLOCK_TO_LENGTH; const int blockpoint = memoryAddress % BLOCK_TO_LENGTH;
// Print ASCII representation header for each block
if (blockpoint == 0) if (blockpoint == 0)
{ {
ascii_buf[BLOCK_TO_LENGTH] = 0; ascii_buf[BLOCK_TO_LENGTH] = 0;
@@ -331,55 +482,54 @@ void dumpEEPROM(uint16_t memoryAddress, uint16_t length)
Debug_pushMessage("\n0x%05X:", memoryAddress); Debug_pushMessage("\n0x%05X:", memoryAddress);
} }
// Read and print each byte
ascii_buf[blockpoint] = ee.readByte(memoryAddress); ascii_buf[blockpoint] = ee.readByte(memoryAddress);
Debug_pushMessage(" %02X", ascii_buf[blockpoint]); Debug_pushMessage(" %02X", ascii_buf[blockpoint]);
// Replace non-printable characters with dots in ASCII representation
if (ascii_buf[blockpoint] < 0x20 || ascii_buf[blockpoint] > 0x7E) if (ascii_buf[blockpoint] < 0x20 || ascii_buf[blockpoint] > 0x7E)
ascii_buf[blockpoint] = '.'; ascii_buf[blockpoint] = '.';
memoryAddress++; memoryAddress++;
} }
// Print a new line at the end of the dump
Debug_pushMessage("\n"); Debug_pushMessage("\n");
} }
/** /**
* @brief Check if EEPROM is available and connected. * @brief Unified availability probe with optional recovery.
* *
* This function checks if the EEPROM is available and connected. If not, it triggers * Fast path returns the latched availability flag. If not available,
* a diagnostic trouble code (DTC) indicating the absence of EEPROM. * performs a direct probe and, optionally, a recovery sequence.
* *
* @param recover If true, attempt recovery when not available (default: false).
* @param attempts Recovery attempts (default: 3).
* @param delay_ms Delay between attempts in ms (default: 25).
* @return true if EEPROM is available, false otherwise. * @return true if EEPROM is available, false otherwise.
*/ */
boolean checkEEPROMavailable() bool EEPROM_Available(bool recover, uint8_t attempts, uint16_t delay_ms)
{ {
// Check if EEPROM is connected if (eeAvailable)
if (!ee.isConnected()) return true;
if (ee.isConnected())
{ {
// Trigger DTC for no EEPROM found eeAvailable = true;
MaintainDTC(DTC_NO_EEPROM_FOUND, true); eeRecoveredOnce = true; // edge 0 -> 1
return false; return true;
} }
// Clear DTC for no EEPROM found since it's available now if (recover)
MaintainDTC(DTC_NO_EEPROM_FOUND, false); {
return TryRecoverEEPROM(attempts, delay_ms);
}
// EEPROM is available return false;
return true;
} }
/** /**
* @brief Perform sanity check on configuration settings. * @brief Validate config fields; return bitmask of invalid entries.
* *
* This function checks the validity of various configuration settings and returns a bitmask * If autocorrect is true, invalid fields are reset to default values.
* indicating which settings need to be reset. If autocorrect is enabled, it resets the settings * Each bit in the returned mask identifies a specific field-group that was out-of-bounds.
* to their default values.
*
* @param autocorrect If true, automatically correct invalid settings by resetting to defaults.
* @return A bitmask indicating which settings need to be reset.
*/ */
uint32_t ConfigSanityCheck(bool autocorrect) uint32_t ConfigSanityCheck(bool autocorrect)
{ {
@@ -465,21 +615,21 @@ uint32_t ConfigSanityCheck(bool autocorrect)
LubeConfig.BleedingPulses = LubeConfig_defaults.BleedingPulses; LubeConfig.BleedingPulses = LubeConfig_defaults.BleedingPulses;
} }
if (!(LubeConfig.SpeedSource >= 0) || !(LubeConfig.SpeedSource < SpeedSourceString_Elements)) if (!(LubeConfig.SpeedSource >= 0) || !(LubeConfig.SpeedSource < SPEEDSOURCE_COUNT))
{ {
SET_BIT(setting_reset_bits, 11); SET_BIT(setting_reset_bits, 11);
if (autocorrect) if (autocorrect)
LubeConfig.SpeedSource = LubeConfig_defaults.SpeedSource; LubeConfig.SpeedSource = LubeConfig_defaults.SpeedSource;
} }
if (!(LubeConfig.GPSBaudRate >= 0) || !(LubeConfig.GPSBaudRate < GPSBaudRateString_Elements)) if (!(LubeConfig.GPSBaudRate >= 0) || !(LubeConfig.GPSBaudRate < GPSBAUDRATE_COUNT))
{ {
SET_BIT(setting_reset_bits, 12); SET_BIT(setting_reset_bits, 12);
if (autocorrect) if (autocorrect)
LubeConfig.GPSBaudRate = LubeConfig_defaults.GPSBaudRate; LubeConfig.GPSBaudRate = LubeConfig_defaults.GPSBaudRate;
} }
if (!(LubeConfig.CANSource >= 0) || !(LubeConfig.CANSource < CANSourceString_Elements)) if (!(LubeConfig.CANSource >= 0) || !(LubeConfig.CANSource < CANSOURCE_COUNT))
{ {
SET_BIT(setting_reset_bits, 13); SET_BIT(setting_reset_bits, 13);
if (autocorrect) if (autocorrect)
@@ -513,22 +663,17 @@ uint32_t ConfigSanityCheck(bool autocorrect)
if (autocorrect) if (autocorrect)
strncpy(LubeConfig.wifi_client_password, LubeConfig_defaults.wifi_client_password, sizeof(LubeConfig.wifi_client_password)); strncpy(LubeConfig.wifi_client_password, LubeConfig_defaults.wifi_client_password, sizeof(LubeConfig.wifi_client_password));
} }
// Return the bitmask indicating which settings need to be reset
return setting_reset_bits; return setting_reset_bits;
} }
/** /**
* @brief Validates whether a given string contains only characters allowed in WiFi SSIDs and passwords. * @brief Validate that a string contains only characters allowed for WiFi SSIDs/passwords.
* *
* This function checks each character in the provided string to ensure * Allowed: AZ, az, 09 and the printable ASCII punctuation: ! " # $ % & ' ( ) * + , - . / : ;
* that it contains only characters allowed in WiFi SSIDs and passwords. * < = > ? @ [ \ ] ^ _ ` { | } ~
* It considers characters from 'A' to 'Z', 'a' to 'z', '0' to '9', as well as
* the following special characters: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~
* *
* @param string Pointer to the string to be validated. * @return true if valid (or empty), false otherwise.
* @param size Size of the string including the null-terminator.
* @return true if the string contains only allowed characters or is NULL,
* false otherwise.
*/ */
bool validateWiFiString(char *string, size_t size) bool validateWiFiString(char *string, size_t size)
{ {
@@ -539,10 +684,8 @@ bool validateWiFiString(char *string, size_t size)
{ {
char c = string[i]; char c = string[i];
if (c == '\0') if (c == '\0')
{ return true; // reached end with valid chars
// Reached the end of the string, all characters were valid WiFi characters.
return true;
}
if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') || c == '!' || c == '"' || c == '#' || (c >= '0' && c <= '9') || c == '!' || c == '"' || c == '#' ||
c == '$' || c == '%' || c == '&' || c == '\'' || c == '(' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '(' ||
@@ -552,11 +695,9 @@ bool validateWiFiString(char *string, size_t size)
c == '\\' || c == ']' || c == '^' || c == '_' || c == '`' || c == '\\' || c == ']' || c == '^' || c == '_' || c == '`' ||
c == '{' || c == '|' || c == '}' || c == '~')) c == '{' || c == '|' || c == '}' || c == '~'))
{ {
// Found a character that is not a valid WiFi character.
return false; return false;
} }
} }
// If the loop completes without finding a null terminator, the string is invalid. // No NUL within buffer: treat as invalid
return false; return false;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -56,7 +56,6 @@ void RunLubeApp(uint32_t add_milimeters)
if (lastSystemStatus != globals.systemStatus) if (lastSystemStatus != globals.systemStatus)
{ {
strcpy_P(globals.systemStatustxt, PSTR("Startup"));
LEDControl_SetBasic(LED_STARTUP_NORMAL, LED_PATTERN_BLINK); LEDControl_SetBasic(LED_STARTUP_NORMAL, LED_PATTERN_BLINK);
lastSystemStatus = globals.systemStatus; lastSystemStatus = globals.systemStatus;
globals.resumeStatus = sysStat_Startup; globals.resumeStatus = sysStat_Startup;
@@ -72,7 +71,6 @@ void RunLubeApp(uint32_t add_milimeters)
case sysStat_Normal: case sysStat_Normal:
if (lastSystemStatus != globals.systemStatus) if (lastSystemStatus != globals.systemStatus)
{ {
strcpy_P(globals.systemStatustxt, PSTR("Normal"));
LEDControl_SetBasic(LED_NORMAL_COLOR, LED_PATTERN_ON); LEDControl_SetBasic(LED_NORMAL_COLOR, LED_PATTERN_ON);
lastSystemStatus = globals.systemStatus; lastSystemStatus = globals.systemStatus;
globals.resumeStatus = sysStat_Normal; globals.resumeStatus = sysStat_Normal;
@@ -89,7 +87,6 @@ void RunLubeApp(uint32_t add_milimeters)
case sysStat_Rain: case sysStat_Rain:
if (lastSystemStatus != globals.systemStatus) if (lastSystemStatus != globals.systemStatus)
{ {
strcpy_P(globals.systemStatustxt, PSTR("Rain"));
LEDControl_SetBasic(LED_RAIN_COLOR, LED_PATTERN_ON); LEDControl_SetBasic(LED_RAIN_COLOR, LED_PATTERN_ON);
lastSystemStatus = globals.systemStatus; lastSystemStatus = globals.systemStatus;
globals.resumeStatus = sysStat_Rain; globals.resumeStatus = sysStat_Rain;
@@ -107,7 +104,6 @@ void RunLubeApp(uint32_t add_milimeters)
if (lastSystemStatus != globals.systemStatus) if (lastSystemStatus != globals.systemStatus)
{ {
washModeRemainDistance = LubeConfig.WashMode_Distance; washModeRemainDistance = LubeConfig.WashMode_Distance;
strcpy_P(globals.systemStatustxt, PSTR("Wash"));
LEDControl_SetBasic(LED_WASH_COLOR, LED_PATTERN_BREATH); LEDControl_SetBasic(LED_WASH_COLOR, LED_PATTERN_BREATH);
lastSystemStatus = globals.systemStatus; lastSystemStatus = globals.systemStatus;
} }
@@ -134,7 +130,6 @@ void RunLubeApp(uint32_t add_milimeters)
if (lastSystemStatus != globals.systemStatus) if (lastSystemStatus != globals.systemStatus)
{ {
globals.purgePulses = LubeConfig.BleedingPulses; globals.purgePulses = LubeConfig.BleedingPulses;
strcpy_P(globals.systemStatustxt, PSTR("Purge"));
LEDControl_SetBasic(LED_PURGE_COLOR, LED_PATTERN_BLINK); LEDControl_SetBasic(LED_PURGE_COLOR, LED_PATTERN_BLINK);
lastSystemStatus = globals.systemStatus; lastSystemStatus = globals.systemStatus;
} }
@@ -161,7 +156,6 @@ void RunLubeApp(uint32_t add_milimeters)
if (lastSystemStatus != globals.systemStatus) if (lastSystemStatus != globals.systemStatus)
{ {
strcpy_P(globals.systemStatustxt, PSTR("Error"));
LEDControl_SetBasic(LED_ERROR_COLOR, LED_PATTERN_BLINK_FAST); LEDControl_SetBasic(LED_ERROR_COLOR, LED_PATTERN_BLINK_FAST);
lastSystemStatus = globals.systemStatus; lastSystemStatus = globals.systemStatus;
} }
@@ -173,7 +167,6 @@ void RunLubeApp(uint32_t add_milimeters)
if (lastSystemStatus != globals.systemStatus) if (lastSystemStatus != globals.systemStatus)
{ {
strcpy_P(globals.systemStatustxt, PSTR("Shutdown"));
LEDControl_SetBasic(LED_SHUTDOWN_COLOR, LED_PATTERN_BREATH_REVERSE); LEDControl_SetBasic(LED_SHUTDOWN_COLOR, LED_PATTERN_BREATH_REVERSE);
lastSystemStatus = globals.systemStatus; lastSystemStatus = globals.systemStatus;
} }

View File

@@ -398,7 +398,7 @@ void Display_Process()
DistRemain = DistRemain - (PersistenceData.TravelDistance_highRes_mm / 1000); DistRemain = DistRemain - (PersistenceData.TravelDistance_highRes_mm / 1000);
// Display relevant information on the OLED screen based on system status // Display relevant information on the OLED screen based on system status
u8x8.printf(PSTR("Mode: %10s\n"), globals.systemStatustxt); u8x8.printf(PSTR("Mode: %10s\n"), ToString(globals.systemStatus));
if (globals.systemStatus == sysStat_Error) if (globals.systemStatus == sysStat_Error)
{ {
// Display the last Diagnostic Trouble Code (DTC) in case of an error // Display the last Diagnostic Trouble Code (DTC) in case of an error
@@ -412,7 +412,8 @@ void Display_Process()
u8x8.printf(PSTR("WiFi: %10s\n"), (WiFi.getMode() == WIFI_AP ? "AP" : WiFi.getMode() == WIFI_OFF ? "OFF" u8x8.printf(PSTR("WiFi: %10s\n"), (WiFi.getMode() == WIFI_AP ? "AP" : WiFi.getMode() == WIFI_OFF ? "OFF"
: WiFi.getMode() == WIFI_STA ? "CLIENT" : WiFi.getMode() == WIFI_STA ? "CLIENT"
: "UNKNOWN")); : "UNKNOWN"));
u8x8.printf(PSTR("Source: %8s\n"), SpeedSourceString[LubeConfig.SpeedSource]); u8x8.printf(PSTR("Source: %8s\n"), ToString(LubeConfig.SpeedSource));
u8x8.printf("%s\n", WiFi.localIP().toString().c_str()); u8x8.printf("%s\n", WiFi.localIP().toString().c_str());
} }

View File

@@ -6,79 +6,203 @@
#include "dtc.h" #include "dtc.h"
#include "debugger.h" #include "debugger.h"
// === Setup: MCP2515 CS-Pin definieren === // =======================
// Konfiguration
// =======================
#ifndef OBD2_CAN_CS_PIN
#define OBD2_CAN_CS_PIN 10 #define OBD2_CAN_CS_PIN 10
#define OBD2_OBD_REQUEST_ID 0x7DF #endif
#define OBD2_OBD_RESPONSE_ID 0x7E8
MCP_CAN OBD_CAN(OBD2_CAN_CS_PIN); // 11-bit OBD-II IDs (ISO 15765-4, üblich 0x7DF/0x7E8..0x7EF)
#define OBD2_OBD_REQUEST_ID 0x7DF
#define OBD2_OBD_RESP_BASE 0x7E8
#define OBD2_OBD_RESP_LAST 0x7EF
// Tuning: Poll schneller als 500ms für WheelSpeed
#ifndef OBD2_QUERY_INTERVAL_MS
#define OBD2_QUERY_INTERVAL_MS 100 // 10 Hz Pollrate
#endif
#ifndef OBD2_RESP_TIMEOUT_MS
#define OBD2_RESP_TIMEOUT_MS 60 // max. Wartezeit auf Antwort (non-blocking überwacht)
#endif
// Wenn wir X ms keine gültige Antwort haben, wird die Geschwindigkeit als stale behandelt
#ifndef OBD2_STALE_MS
#define OBD2_STALE_MS 600 // danach Speed -> 0
#endif
// Wie viele RX-Frames pro Aufruf maximal ziehen (Begrenzung gegen Busy-Loops)
#ifndef OBD2_MAX_READS_PER_CALL
#define OBD2_MAX_READS_PER_CALL 4
#endif
// Debug-Rate-Limit
#ifndef OBD2_DEBUG_INTERVAL_MS
#define OBD2_DEBUG_INTERVAL_MS 1000
#endif
// =======================
// Internals
// =======================
static MCP_CAN OBD_CAN(OBD2_CAN_CS_PIN);
static uint32_t lastQueryTime = 0; static uint32_t lastQueryTime = 0;
static uint32_t lastRecvTime = 0; static uint32_t lastRespTime = 0;
static uint32_t lastIntegrateTime = 0;
static uint32_t requestDeadline = 0;
static uint32_t lastDebugTime = 0;
static uint32_t lastSpeedMMperSec = 0; static uint32_t lastSpeedMMperSec = 0;
#define OBD2_QUERY_INTERVAL 500 // alle 500ms enum class ObdState : uint8_t { IDLE = 0, WAITING = 1 };
static ObdState state = ObdState::IDLE;
// =======================
// Hilfsfunktionen
// =======================
static inline bool isObdResponseId(unsigned long id) {
return (id >= OBD2_OBD_RESP_BASE) && (id <= OBD2_OBD_RESP_LAST);
}
static void setupObdFilters() {
// Für STD-IDs: Filter auf 0x7E8..0x7EF, Maske 0x7F0
// Hinweis: Signaturen des MCP_CAN libs:
// init_Mask(num, ext, mask);
// init_Filt(num, ext, filt);
// ext=0 -> Standard (11-bit)
OBD_CAN.init_Mask(0, 0, 0x7F0);
OBD_CAN.init_Filt(0, 0, 0x7E8);
OBD_CAN.init_Filt(1, 0, 0x7E9);
OBD_CAN.init_Mask(1, 0, 0x7F0);
OBD_CAN.init_Filt(2, 0, 0x7EA);
OBD_CAN.init_Filt(3, 0, 0x7EB);
OBD_CAN.init_Filt(4, 0, 0x7EC);
OBD_CAN.init_Filt(5, 0, 0x7ED);
// (0x7EE, 0x7EF fallen auch unter Maske; wenn du willst, kannst du die letzten zwei Filt-Slots umbiegen)
}
static void maybeDebug(uint32_t now, const char* fmt, ...) {
if (now - lastDebugTime < OBD2_DEBUG_INTERVAL_MS) return;
lastDebugTime = now;
va_list ap;
va_start(ap, fmt);
Debug_pushMessage(fmt, ap);
va_end(ap);
}
// =======================
// Öffentliche API
// =======================
void Init_OBD2_CAN() void Init_OBD2_CAN()
{ {
if (OBD_CAN.begin(MCP_STD, CAN_500KBPS, MCP_16MHZ) != CAN_OK) // Kein delay() hier — watchdog-freundlich.
{ // Standard-OBD: 500 kbit/s, 11-bit
Serial.println("OBD2 CAN Init FAILED!"); if (OBD_CAN.begin(MCP_STD, CAN_500KBPS, MCP_16MHZ) != CAN_OK) {
Debug_pushMessage("OBD2 CAN init FAILED\n");
MaintainDTC(DTC_OBD2_CAN_TIMEOUT, true);
return; return;
} }
setupObdFilters();
OBD_CAN.setMode(MCP_NORMAL); OBD_CAN.setMode(MCP_NORMAL);
delay(100); MaintainDTC(DTC_OBD2_CAN_TIMEOUT, false);
Serial.println("OBD2 CAN Init OK"); MaintainDTC(DTC_OBD2_CAN_NO_RESPONSE, true); // bis erste Antwort kommt
Debug_pushMessage("OBD2 CAN init OK\n");
// Timestamps zurücksetzen
uint32_t now = millis();
lastQueryTime = now;
lastRespTime = 0;
lastIntegrateTime = now;
requestDeadline = 0;
state = ObdState::IDLE;
} }
uint32_t Process_OBD2_CAN_Speed() uint32_t Process_OBD2_CAN_Speed()
{ {
if (millis() - lastQueryTime < OBD2_QUERY_INTERVAL) const uint32_t now = millis();
return 0;
lastQueryTime = millis(); // 1) Nicht-blockierende Query: nur senden, wenn es Zeit ist und keine offene Anfrage wartet
if (state == ObdState::IDLE && (now - lastQueryTime) >= OBD2_QUERY_INTERVAL_MS) {
byte req[8] = {0x02, 0x01, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00}; // Mode 01, PID 0x0D (Speed)
byte stat = OBD_CAN.sendMsgBuf(OBD2_OBD_REQUEST_ID, 0, 8, req);
lastQueryTime = now;
// Anfrage: 01 0D → Geschwindigkeit if (stat == CAN_OK) {
byte obdRequest[8] = {0x02, 0x01, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00}; state = ObdState::WAITING;
byte sendStat = OBD_CAN.sendMsgBuf(OBD2_OBD_REQUEST_ID, 0, 8, obdRequest); requestDeadline = now + OBD2_RESP_TIMEOUT_MS;
// kein delay(), sofort zurück zu Loop
if (sendStat != CAN_OK) } else {
{
MaintainDTC(DTC_OBD2_CAN_TIMEOUT, true); MaintainDTC(DTC_OBD2_CAN_TIMEOUT, true);
Debug_pushMessage("OBD2_CAN: send failed (%d)\n", sendStat); maybeDebug(now, "OBD2_CAN send failed (%d)\n", stat);
return 0; // kein busy-wait, wir versuchen es einfach im nächsten Zyklus wieder
} }
}
// 2) Non-blocking Receive: ziehe nur wenige Frames pro Aufruf
for (uint8_t i = 0; i < OBD2_MAX_READS_PER_CALL; ++i) {
if (OBD_CAN.checkReceive() != CAN_MSGAVAIL) break;
unsigned long rxId; unsigned long rxId;
byte len = 0; byte len = 0;
byte rxBuf[8]; byte rxBuf[8];
uint32_t timeout = millis() + 100;
while (millis() < timeout)
{
if (OBD_CAN.checkReceive() == CAN_MSGAVAIL)
{
OBD_CAN.readMsgBuf(&rxId, &len, rxBuf); OBD_CAN.readMsgBuf(&rxId, &len, rxBuf);
if ((rxId & 0xFFF8) == OBD2_OBD_RESPONSE_ID && rxBuf[1] == 0x0D)
{
MaintainDTC(DTC_OBD2_CAN_NO_RESPONSE, false); // alles ok
uint8_t speed_kmh = rxBuf[3]; if (!isObdResponseId(rxId)) continue; // hart gefiltert
uint32_t speed_mm_per_sec = (uint32_t)speed_kmh * 1000000 / 3600; if (len < 4) continue; // zu kurz für 01 0D A (SPEED)
uint32_t dt = millis() - lastRecvTime;
lastRecvTime = millis();
lastSpeedMMperSec = speed_mm_per_sec;
Debug_pushMessage("OBD2_CAN: %d km/h (%lu mm/s)\n", speed_kmh, speed_mm_per_sec); // Erwartetes Echo: 0x41 0x0D <A>
return (speed_mm_per_sec * dt) / 1000; // Viele Stacks liefern 03 41 0D <A> ... wir prüfen tolerant:
uint8_t modeResp = 0, pid = 0, speedKmh = 0;
if (rxBuf[0] == 0x03 && rxBuf[1] == 0x41 && rxBuf[2] == 0x0D) {
modeResp = rxBuf[1];
pid = rxBuf[2];
speedKmh = rxBuf[3];
} else if (rxBuf[0] == 0x41 && rxBuf[1] == 0x0D) {
modeResp = rxBuf[0];
pid = rxBuf[1];
speedKmh = rxBuf[2];
} else {
continue; // nicht die erwartete Antwort
} }
if (modeResp == 0x41 && pid == 0x0D) {
// gültige Antwort
MaintainDTC(DTC_OBD2_CAN_TIMEOUT, false);
MaintainDTC(DTC_OBD2_CAN_NO_RESPONSE, false);
// mm/s = km/h * (1e6 / 3600)
const uint32_t speed_mmps = (uint32_t)speedKmh * 1000000UL / 3600UL;
lastSpeedMMperSec = speed_mmps;
lastRespTime = now;
state = ObdState::IDLE; // Anfrage bedient
maybeDebug(now, "OBD2_CAN: %u km/h (%lu mm/s)\n", speedKmh, (unsigned long)speed_mmps);
break; // eine valide Antwort reicht
} }
} }
// Keine Antwort erhalten // 3) Timeout von offenen Anfragen prüfen (non-blocking)
if (state == ObdState::WAITING && (int32_t)(now - requestDeadline) >= 0) {
// keine Antwort in der Zeit
MaintainDTC(DTC_OBD2_CAN_NO_RESPONSE, true); MaintainDTC(DTC_OBD2_CAN_NO_RESPONSE, true);
Debug_pushMessage("OBD2_CAN: no response within timeout\n"); state = ObdState::IDLE; // freigeben für nächsten Poll
return 0; }
}
// 4) Distanz-Integration (sanft, watchdog-freundlich)
if (lastIntegrateTime == 0) lastIntegrateTime = now;
uint32_t dt_ms = now - lastIntegrateTime;
lastIntegrateTime = now;
// Wenn zu lange keine Antwort, setze Speed -> 0 (kein ausuferndes dt auf Antwortbasis)
uint32_t effectiveSpeed = lastSpeedMMperSec;
if (lastRespTime == 0 || (now - lastRespTime) > OBD2_STALE_MS) {
effectiveSpeed = 0;
}
// mm = (mm/s * ms) / 1000
uint32_t add_mm = (effectiveSpeed * (uint64_t)dt_ms) / 1000ULL;
return add_mm;
}

View File

@@ -337,89 +337,145 @@ void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const Stri
} }
} }
} }
void WebserverEERestore_Callback(AsyncWebServerRequest *request,
/** const String &filename,
* @brief Callback function for handling EEPROM restore via the web server. size_t index,
* uint8_t *data,
* This function is invoked during the EEPROM restore process when a new EEPROM file size_t len,
* is received. It handles the restore process by reading the data from the received file, bool final)
* deserializing the JSON data, and updating the configuration and persistence data accordingly.
* If the restore is successful, it triggers a system shutdown.
*
* @param request Pointer to the AsyncWebServerRequest object.
* @param filename The name of the file being restored.
* @param index The index of the file being restored.
* @param data Pointer to the data buffer.
* @param len The length of the data buffer.
* @param final Boolean indicating if this is the final chunk of data.
*/
void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final)
{ {
constexpr size_t kBufCap = 1536;
bool ee_done = false; bool ee_done = false;
static bool validext = false; static bool validext = false;
static char *buffer = NULL; static char *buffer = nullptr;
static uint32_t read_ptr = 0; static uint32_t read_ptr = 0;
DeserializationError error; DeserializationError error;
// kleines Helferlein zum sicheren Kopieren & Terminieren
auto safe_copy = [](char *dst, size_t dst_sz, const char *src)
{
if (!dst || dst_sz == 0)
return;
if (!src)
{
dst[0] = '\0';
return;
}
strncpy(dst, src, dst_sz - 1);
dst[dst_sz - 1] = '\0';
};
// Grenzen/Hilfen für Enum-Ranges (Sentinel bevorzugt, sonst *_Elements)
const int maxSpeedSrc = static_cast<int>(SPEEDSOURCE_COUNT);
const int maxGPSBaud = static_cast<int>(GPSBAUDRATE_COUNT);
const int maxCANSrc = static_cast<int>(CANSOURCE_COUNT);
if (!index) if (!index)
{ {
validext = (filename.indexOf(".ee.json") > -1); validext = (filename.indexOf(".ee.json") > -1);
if (validext) if (validext)
{ {
buffer = (char *)malloc(1536); buffer = (char *)malloc(kBufCap);
read_ptr = 0; read_ptr = 0;
if (buffer == NULL) if (!buffer)
{
Debug_pushMessage("malloc() failed for EEPROM-Restore\n"); Debug_pushMessage("malloc() failed for EEPROM-Restore\n");
} }
} }
}
if (buffer != NULL && len > 0) // Chunked receive mit Cap/Trunkierungsschutz
if (buffer && len > 0)
{ {
memcpy(buffer + read_ptr, data, len); size_t remain = (read_ptr < kBufCap) ? (kBufCap - read_ptr) : 0;
read_ptr = read_ptr + len; size_t to_copy = (len <= remain) ? len : remain;
if (to_copy > 0)
{
memcpy(buffer + read_ptr, data, to_copy);
read_ptr += to_copy;
}
else
{
Debug_pushMessage("EEPROM-Restore input exceeds buffer, truncating\n");
}
} }
if (final) if (final)
{ {
if (buffer != NULL) if (buffer)
{ {
// Ensure zero-termination just in case // Null-terminieren
if (read_ptr >= 1536) if (read_ptr == kBufCap)
read_ptr = 1535; read_ptr = kBufCap - 1;
buffer[read_ptr] = '\0'; buffer[read_ptr] = '\0';
Serial.print(buffer); // Parse
JsonDocument json; JsonDocument json; // entspricht deinem bisherigen Stil
error = deserializeJson(json, buffer); error = deserializeJson(json, buffer);
if (error) if (error)
{ {
Debug_pushMessage("deserializeJson() failed: %s\n", error.f_str()); Debug_pushMessage("deserializeJson() failed: %s\n", error.f_str());
} }
else else if (validext)
{ {
// ---- Konfiguration sicher in RAM übernehmen ----
// clamp-Helfer passend zu deinen Sanity-Grenzen
auto clamp_u32 = [](uint32_t v, uint32_t lo, uint32_t hi)
{ return (v < lo) ? lo : (v > hi ? hi : v); };
auto clamp_u16 = [](uint16_t v, uint16_t lo, uint16_t hi)
{ return (v < lo) ? lo : (v > hi ? hi : v); };
auto clamp_u8 = [](uint8_t v, uint8_t lo, uint8_t hi)
{ return (v < lo) ? lo : (v > hi ? hi : v); };
LubeConfig.DistancePerLube_Default = json["config"]["DistancePerLube_Default"].as<uint32_t>(); // config.*
LubeConfig.DistancePerLube_Rain = json["config"]["DistancePerLube_Rain"].as<uint32_t>(); LubeConfig.DistancePerLube_Default = clamp_u32(json["config"]["DistancePerLube_Default"].as<uint32_t>(), 0, 50000);
LubeConfig.tankCapacity_ml = json["config"]["tankCapacity_ml"].as<uint32_t>(); LubeConfig.DistancePerLube_Rain = clamp_u32(json["config"]["DistancePerLube_Rain"].as<uint32_t>(), 0, 50000);
LubeConfig.amountPerDose_microL = json["config"]["amountPerDose_microL"].as<uint32_t>(); LubeConfig.tankCapacity_ml = clamp_u32(json["config"]["tankCapacity_ml"].as<uint32_t>(), 0, 5000);
LubeConfig.TankRemindAtPercentage = json["config"]["TankRemindAtPercentage"].as<uint8_t>(); LubeConfig.amountPerDose_microL = clamp_u32(json["config"]["amountPerDose_microL"].as<uint32_t>(), 0, 100);
LubeConfig.PulsePerRevolution = json["config"]["PulsePerRevolution"].as<uint8_t>(); LubeConfig.TankRemindAtPercentage = clamp_u8(json["config"]["TankRemindAtPercentage"].as<uint8_t>(), 0, 100);
LubeConfig.TireWidth_mm = json["config"]["TireWidth_mm"].as<uint32_t>(); LubeConfig.PulsePerRevolution = clamp_u8(json["config"]["PulsePerRevolution"].as<uint8_t>(), 0, 255);
LubeConfig.TireWidthHeight_Ratio = json["config"]["TireWidthHeight_Ratio"].as<uint32_t>(); LubeConfig.TireWidth_mm = clamp_u32(json["config"]["TireWidth_mm"].as<uint32_t>(), 0, 500);
LubeConfig.RimDiameter_Inch = json["config"]["RimDiameter_Inch"].as<uint32_t>(); LubeConfig.TireWidthHeight_Ratio = clamp_u32(json["config"]["TireWidthHeight_Ratio"].as<uint32_t>(), 0, 150);
LubeConfig.DistancePerRevolution_mm = json["config"]["DistancePerRevolution_mm"].as<uint32_t>(); LubeConfig.RimDiameter_Inch = clamp_u32(json["config"]["RimDiameter_Inch"].as<uint32_t>(), 0, 30);
LubeConfig.BleedingPulses = json["config"]["BleedingPulses"].as<uint16_t>(); LubeConfig.DistancePerRevolution_mm = clamp_u32(json["config"]["DistancePerRevolution_mm"].as<uint32_t>(), 0, 10000);
LubeConfig.SpeedSource = (SpeedSource_t)json["config"]["SpeedSource"].as<int>(); LubeConfig.BleedingPulses = clamp_u16(json["config"]["BleedingPulses"].as<uint16_t>(), 0, 1000);
LubeConfig.GPSBaudRate = (GPSBaudRate_t)json["config"]["GPSBaudRate"].as<int>(); LubeConfig.WashMode_Distance = json["config"]["WashMode_Distance"].as<uint16_t>(); // ggf. Grenzen anpassen
LubeConfig.CANSource = (CANSource_t)json["config"]["CANSource"].as<int>(); LubeConfig.WashMode_Interval = json["config"]["WashMode_Interval"].as<uint16_t>(); // ggf. Grenzen anpassen
LubeConfig.LED_Mode_Flash = json["config"]["LED_Mode_Flash"].as<bool>(); LubeConfig.LED_Mode_Flash = json["config"]["LED_Mode_Flash"].as<bool>();
LubeConfig.LED_Max_Brightness = json["config"]["LED_Max_Brightness"].as<uint8_t>(); LubeConfig.LED_Max_Brightness = json["config"]["LED_Max_Brightness"].as<uint8_t>();
LubeConfig.LED_Min_Brightness = json["config"]["LED_Min_Brightness"].as<uint8_t>(); LubeConfig.LED_Min_Brightness = json["config"]["LED_Min_Brightness"].as<uint8_t>();
strncpy(LubeConfig.wifi_ap_ssid, json["config"]["wifi_ap_ssid"].as<const char *>(), sizeof(LubeConfig.wifi_ap_ssid));
strncpy(LubeConfig.wifi_ap_password, json["config"]["wifi_ap_password"].as<const char *>(), sizeof(LubeConfig.wifi_ap_password));
strncpy(LubeConfig.wifi_client_ssid, json["config"]["wifi_client_ssid"].as<const char *>(), sizeof(LubeConfig.wifi_client_ssid));
strncpy(LubeConfig.wifi_client_password, json["config"]["wifi_client_password"].as<const char *>(), sizeof(LubeConfig.wifi_client_password));
// Enums nur nach Range-Check übernehmen
{
int v = json["config"]["SpeedSource"].as<int>();
if (v >= 0 && v < maxSpeedSrc)
LubeConfig.SpeedSource = (SpeedSource_t)v;
else
Debug_pushMessage("Restore: invalid SpeedSource=%d\n", v);
}
{
int v = json["config"]["GPSBaudRate"].as<int>();
if (v >= 0 && v < maxGPSBaud)
LubeConfig.GPSBaudRate = (GPSBaudRate_t)v;
else
Debug_pushMessage("Restore: invalid GPSBaudRate=%d\n", v);
}
{
int v = json["config"]["CANSource"].as<int>();
if (v >= 0 && v < maxCANSrc)
LubeConfig.CANSource = (CANSource_t)v;
else
Debug_pushMessage("Restore: invalid CANSource=%d\n", v);
}
// Strings sicher kopieren (0-terminiert)
safe_copy(LubeConfig.wifi_ap_ssid, sizeof(LubeConfig.wifi_ap_ssid), json["config"]["wifi_ap_ssid"]);
safe_copy(LubeConfig.wifi_ap_password, sizeof(LubeConfig.wifi_ap_password), json["config"]["wifi_ap_password"]);
safe_copy(LubeConfig.wifi_client_ssid, sizeof(LubeConfig.wifi_client_ssid), json["config"]["wifi_client_ssid"]);
safe_copy(LubeConfig.wifi_client_password, sizeof(LubeConfig.wifi_client_password), json["config"]["wifi_client_password"]);
// persis.*
PersistenceData.writeCycleCounter = json["persis"]["writeCycleCounter"].as<uint16_t>(); PersistenceData.writeCycleCounter = json["persis"]["writeCycleCounter"].as<uint16_t>();
PersistenceData.tankRemain_microL = json["persis"]["tankRemain_microL"].as<uint32_t>(); PersistenceData.tankRemain_microL = json["persis"]["tankRemain_microL"].as<uint32_t>();
PersistenceData.TravelDistance_highRes_mm = json["persis"]["TravelDistance_highRes_mm"].as<uint32_t>(); PersistenceData.TravelDistance_highRes_mm = json["persis"]["TravelDistance_highRes_mm"].as<uint32_t>();
@@ -427,24 +483,33 @@ void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &f
PersistenceData.odometer = json["persis"]["odometer"].as<uint32_t>(); PersistenceData.odometer = json["persis"]["odometer"].as<uint32_t>();
PersistenceData.checksum = json["persis"]["checksum"].as<uint32_t>(); PersistenceData.checksum = json["persis"]["checksum"].as<uint32_t>();
// Optional: Sanity-Autokorrektur im RAM (keine EEPROM-Writes hier!)
{
uint32_t sanity = ConfigSanityCheck(true);
if (sanity > 0)
{
MaintainDTC(DTC_EEPROM_CFG_SANITY, true, sanity);
Debug_pushMessage("Restore: ConfigSanity corrected (mask=0x%08lX)\n", sanity);
}
}
ee_done = true; ee_done = true;
} }
}
if (buffer)
{
free(buffer); free(buffer);
buffer = NULL; buffer = nullptr;
} }
AsyncWebServerResponse *response = request->beginResponse(302, "text/plain", "Please wait while the device reboots"); // Browser zurückleiten & ggf. Shutdown
AsyncWebServerResponse *response =
request->beginResponse(302, "text/plain", "Please wait while the device reboots");
response->addHeader("Refresh", "20"); response->addHeader("Refresh", "20");
response->addHeader("Location", "/"); response->addHeader("Location", "/");
request->send(response); request->send(response);
if (ee_done) if (ee_done)
{ {
Debug_pushMessage("Update complete\n"); Debug_pushMessage("EEPROM restore complete\n");
globals.systemStatus = sysStat_Shutdown; globals.systemStatus = sysStat_Shutdown;
} }
} }
@@ -479,8 +544,8 @@ void WebServerEEJSON_Callback(AsyncWebServerRequest *request)
generateJsonObject_PersistenceData(persis); generateJsonObject_PersistenceData(persis);
JsonObject eepart = json["eepart"].to<JsonObject>(); JsonObject eepart = json["eepart"].to<JsonObject>();
sprintf(buffer, "0x%04X", globals.eePersistanceAdress); sprintf(buffer, "0x%04X", globals.eePersistenceAddress);
eepart["PersistanceAddress"] = buffer; eepart["PersistenceAddress"] = buffer;
serializeJsonPretty(json, *response); serializeJsonPretty(json, *response);
@@ -648,24 +713,24 @@ void Websocket_HandleSettings(uint8_t *data)
} }
else if (strcmp(identifier, "speedsource") == 0) else if (strcmp(identifier, "speedsource") == 0)
{ {
int index = findIndexByString(value, SpeedSourceString, (int)SpeedSourceString_Elements); int index = findIndexByString(value, SpeedSourceString, (int)SPEEDSOURCE_COUNT);
if (validIndex(index, (int)SpeedSourceString_Elements)) if (validIndex(index, (int)SPEEDSOURCE_COUNT))
speedsourcePreselect = (SpeedSource_t)index; speedsourcePreselect = (SpeedSource_t)index;
else else
Debug_pushMessage("Invalid speedsource '%s'\n", value); Debug_pushMessage("Invalid speedsource '%s'\n", value);
} }
else if (strcmp(identifier, "cansource") == 0) else if (strcmp(identifier, "cansource") == 0)
{ {
int index = findIndexByString(value, CANSourceString, (int)CANSourceString_Elements); int index = findIndexByString(value, CANSourceString, (int)CANSOURCE_COUNT);
if (validIndex(index, (int)CANSourceString_Elements)) if (validIndex(index, (int)CANSOURCE_COUNT))
LubeConfig.CANSource = (CANSource_t)index; LubeConfig.CANSource = (CANSource_t)index;
else else
Debug_pushMessage("Invalid cansource '%s'\n", value); Debug_pushMessage("Invalid cansource '%s'\n", value);
} }
else if (strcmp(identifier, "gpsbaud") == 0) else if (strcmp(identifier, "gpsbaud") == 0)
{ {
int index = findIndexByString(value, GPSBaudRateString, (int)GPSBaudRateString_Elements); int index = findIndexByString(value, GPSBaudRateString, (int)GPSBAUDRATE_COUNT);
if (validIndex(index, (int)GPSBaudRateString_Elements)) if (validIndex(index, (int)GPSBAUDRATE_COUNT))
LubeConfig.GPSBaudRate = (GPSBaudRate_t)index; LubeConfig.GPSBaudRate = (GPSBaudRate_t)index;
else else
Debug_pushMessage("Invalid gpsbaud '%s'\n", value); Debug_pushMessage("Invalid gpsbaud '%s'\n", value);
@@ -801,7 +866,7 @@ void Websocket_RefreshClientData_Status(uint32_t client_id, bool send_mapping)
String temp = "STATUS:"; String temp = "STATUS:";
temp.concat(String(nz(globals.systemStatustxt)) + ";"); temp.concat(String(ToString(globals.systemStatus)) + ";");
// Guard against division by zero (capacity==0) // Guard against division by zero (capacity==0)
uint32_t cap = LubeConfig.tankCapacity_ml; uint32_t cap = LubeConfig.tankCapacity_ml;
@@ -856,26 +921,26 @@ void Websocket_RefreshClientData_Static(uint32_t client_id, bool send_mapping)
temp += String(LubeConfig.RimDiameter_Inch) + ";"; temp += String(LubeConfig.RimDiameter_Inch) + ";";
// speedsource + Optionen // speedsource + Optionen
temp += tableStr(SpeedSourceString, (int)LubeConfig.SpeedSource, (int)SpeedSourceString_Elements) + ";"; temp += String(ToString(LubeConfig.SpeedSource)) + ";";
{ {
String csv; String csv;
appendCsv(csv, SpeedSourceString, SpeedSourceString_Elements); appendCsv(csv, SpeedSourceString, SPEEDSOURCE_COUNT);
temp += csv + ";"; temp += csv + ";";
} }
// gpsbaud + Optionen // gpsbaud + Optionen
temp += tableStr(GPSBaudRateString, (int)LubeConfig.GPSBaudRate, (int)GPSBaudRateString_Elements) + ";"; temp += String(ToString(LubeConfig.GPSBaudRate)) + ";";
{ {
String csv; String csv;
appendCsv(csv, GPSBaudRateString, GPSBaudRateString_Elements); appendCsv(csv, GPSBaudRateString, GPSBAUDRATE_COUNT);
temp += csv + ";"; temp += csv + ";";
} }
// cansource + Optionen // cansource + Optionen
temp += tableStr(CANSourceString, (int)LubeConfig.CANSource, (int)CANSourceString_Elements) + ";"; temp += String(ToString(LubeConfig.CANSource)) + ";";
{ {
String csv; String csv;
appendCsv(csv, CANSourceString, CANSourceString_Elements); appendCsv(csv, CANSourceString, CANSOURCE_COUNT);
temp += csv + ";"; temp += csv + ";";
} }