updated Project with elementes From ChainLube-Project
This commit is contained in:
		
							
								
								
									
										96
									
								
								Software/data_src/static/dtc_table.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								Software/data_src/static/dtc_table.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | |||||||
|  | { | ||||||
|  |     "codegenerator_checksum": "313d59949b074024df3c5d796f65e3bd518e34f0bb171185c30f008f21c19d30", | ||||||
|  |     "timestamp": "2024-05-30 21:56:51", | ||||||
|  |     "dtc_table_data": [ | ||||||
|  |         { | ||||||
|  |             "num": 0, | ||||||
|  |             "title": "No Error", | ||||||
|  |             "description": "No Error" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 1, | ||||||
|  |             "title": "Akku leer", | ||||||
|  |             "description": "Akku ist komplett leer. Den Akku aufladen!" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 2, | ||||||
|  |             "title": "Akku niedrig", | ||||||
|  |             "description": "Akku ist unter der Warnschwelle. Den Akku demnächst aufladen" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 3, | ||||||
|  |             "title": "kein EEPROM erkannt", | ||||||
|  |             "description": "Es wurde kein EEPROM gefunden. Dies lässt einen Hardware-Defekt vermuten." | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 4, | ||||||
|  |             "title": "EEPROM CFG Checksumme", | ||||||
|  |             "description": "Die Checksumme der Config-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 5, | ||||||
|  |             "title": "EEPROM PDS Checksumme", | ||||||
|  |             "description": "Die Checksumme der Betriebsdaten-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 6, | ||||||
|  |             "title": "EEPROM PDS Adresse", | ||||||
|  |             "description": "Die Adresse der Betriebsdaten-Partition im EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 7, | ||||||
|  |             "title": "EEPROM Version falsch", | ||||||
|  |             "description": "Die Layout-Version des EEPROM stimmt nicht mit der Firmware-Version überein. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 8, | ||||||
|  |             "title": "Flashspeicher Fehler", | ||||||
|  |             "description": "Der Flashspeicher konnte nicht initialisiert werden. Aktualisieren sie Flash & Firmware" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 9, | ||||||
|  |             "title": "Flashversion falsch", | ||||||
|  |             "description": "Die Version des Flashspeicher stimmt nicht mit der Firmware-Version überein. Aktualisieren sie den Flash mit der passenden Update-Datei" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 10, | ||||||
|  |             "title": "Keine Akkuüberwachung", | ||||||
|  |             "description": "Es wurde keine Akkuüberwachung über I2C gefunden, Prüfen sie die Hardware!" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 11, | ||||||
|  |             "title": "LoRa-Transceiver Error", | ||||||
|  |             "description": "Es konnte keine Verbindung zum LoRa-Transceiver hergestellt werden. Prüfen Sie die Hardware auf Defekte" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 12, | ||||||
|  |             "title": "Config-Validierung", | ||||||
|  |             "description": "Ein oder mehrer Einstellungswerte sind ausserhalb plausibler Werte. Prüfen Sie Ihre Einstellungen" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 13, | ||||||
|  |             "title": "EEPROM-Migration", | ||||||
|  |             "description": "Es wurde ein altes EEPROm Image erkannt, konnte aber nicht migriert werden. EEPROM manuell zurück setzen und neue Einstellunge speichern." | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 14, | ||||||
|  |             "title": "Dummy-DTC Info", | ||||||
|  |             "description": "Ein Dummy-DTC der Schwere \"Info\" für Debugging-Zwecke" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 15, | ||||||
|  |             "title": "Dummy-DTC Warnung", | ||||||
|  |             "description": "Ein Dummy-DTC der Schwere \"Warnung\" für Debugging-Zwecke" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 16, | ||||||
|  |             "title": "Dummy-DTC Kritisch", | ||||||
|  |             "description": "Ein Dummy-DTC der Schwere \"Kritisch\" für Debugging-Zwecke" | ||||||
|  |         }, | ||||||
|  |         { | ||||||
|  |             "num": 17, | ||||||
|  |             "title": "Last Error", | ||||||
|  |             "description": "Last Error" | ||||||
|  |         } | ||||||
|  |     ] | ||||||
|  | } | ||||||
| @@ -3,14 +3,18 @@ | |||||||
| #define _COMMON_H_ | #define _COMMON_H_ | ||||||
|  |  | ||||||
| #include <stdint.h> | #include <stdint.h> | ||||||
|  | #include <stddef.h> | ||||||
|  |  | ||||||
| #define Q(x) #x | #define Q(x) #x | ||||||
| #define QUOTE(x) Q(x) | #define QUOTE(x) Q(x) | ||||||
|  | #define SET_BIT(value, bitPosition) ((value) |= (1U << (bitPosition))) | ||||||
|  |  | ||||||
| #define TRUE 1 | #define TRUE 1 | ||||||
| #define FALSE 0 | #define FALSE 0 | ||||||
|  |  | ||||||
|  | #ifndef HOST_NAME | ||||||
| #define HOST_NAME "AirsoftTimer_%08X" | #define HOST_NAME "AirsoftTimer_%08X" | ||||||
|  | #endif | ||||||
|  |  | ||||||
| #define SHUTDOWN_DELAY_MS 5000 | #define SHUTDOWN_DELAY_MS 5000 | ||||||
| #define STARTUP_DELAY_MS 20000 | #define STARTUP_DELAY_MS 20000 | ||||||
| @@ -44,6 +48,17 @@ | |||||||
| #define OTA_DELAY 50 // ticks -> 10ms / tick | #define OTA_DELAY 50 // ticks -> 10ms / tick | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | typedef enum eSystem_Status | ||||||
|  | { | ||||||
|  |   sysStat_Startup, | ||||||
|  |   sysStat_Normal, | ||||||
|  |   sysStat_Rain, | ||||||
|  |   sysStat_Purge, | ||||||
|  |   sysStat_Error, | ||||||
|  |   sysStat_Shutdown | ||||||
|  | } tSystem_Status; | ||||||
|  |  | ||||||
|  | #define STARTUP_DELAY 2500 | ||||||
|  | #define SHUTDOWN_DELAY_MS 2500 | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -1,9 +1,21 @@ | |||||||
|  | /** | ||||||
|  |  * @file debugger.h | ||||||
|  |  * | ||||||
|  |  * @brief Header file for debugging functions and status in the ChainLube application. | ||||||
|  |  * | ||||||
|  |  * This file declares functions and status definitions for debugging purposes in the ChainLube project. | ||||||
|  |  * It includes functions to print system information, WiFi information, format EEPROM data, | ||||||
|  |  * handle debug messages, and manage the status of different debug ports. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   09.01.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
| #ifndef _DEBUGGER_H_ | #ifndef _DEBUGGER_H_ | ||||||
| #define _DEBUGGER_H_ | #define _DEBUGGER_H_ | ||||||
|  |  | ||||||
| #include <Arduino.h> | #include <Arduino.h> | ||||||
| #include "webui.h" | #include "webui.h" | ||||||
|  |  | ||||||
| const char PROGMEM helpCmd[] = "sysinfo     - System Info\n" | const char PROGMEM helpCmd[] = "sysinfo     - System Info\n" | ||||||
|                                "netinfo     - WiFi Info\n" |                                "netinfo     - WiFi Info\n" | ||||||
|                                "formatPDS   - Format Persistence EEPROM Data\n" |                                "formatPDS   - Format Persistence EEPROM Data\n" | ||||||
|   | |||||||
| @@ -1,57 +1,39 @@ | |||||||
|  | /** | ||||||
|  |  * @file dtc.h | ||||||
|  |  * | ||||||
|  |  * @brief Header file for handling Diagnostic Trouble Codes (DTC) in the ChainLube application. | ||||||
|  |  * | ||||||
|  |  * This file provides definitions and functions for handling Diagnostic Trouble Codes (DTC) | ||||||
|  |  * in the ChainLube project. It includes structures for DTC entries, severity levels, | ||||||
|  |  * and functions for DTC maintenance and processing. DTCs are used to track system errors and issues. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   09.01.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
| #ifndef _DTC_H_ | #ifndef _DTC_H_ | ||||||
| #define _DTC_H_ | #define _DTC_H_ | ||||||
|  |  | ||||||
| #include <Arduino.h> | #include <Arduino.h> | ||||||
|  | #include "dtc_defs.h" | ||||||
|  |  | ||||||
| #define MAX_DTC_STORAGE 12 | #define MAX_DTC_STORAGE 12 | ||||||
|  |  | ||||||
| typedef enum DTCNums_e | typedef struct | ||||||
| { | { | ||||||
|   DTC_NO_EEPROM_FOUND = 1, |   DTCNum_t Number; | ||||||
|   DTC_EEPROM_CFG_BAD, |  | ||||||
|   DTC_EEPROM_PDS_BAD, |  | ||||||
|   DTC_EEPROM_PDSADRESS_BAD, |  | ||||||
|   DTC_EEPROM_VERSION_BAD, |  | ||||||
|   DTC_FLASHFS_ERROR, |  | ||||||
|   DTC_FLASHFS_VERSION_ERROR, |  | ||||||
|   DTC_EEPROM_CFG_SANITY, |  | ||||||
|   DTC_NO_LORA_FOUND, |  | ||||||
|   DTC_NO_BATMNON_FOUND, |  | ||||||
|   DTC_BAT_LOW, |  | ||||||
|   DTC_BAT_CRITICAL, |  | ||||||
|   DTC_EEPROM_MIGRATE_FAILED, |  | ||||||
|   DTC_LAST_DTC |  | ||||||
| } DTCNums_t; |  | ||||||
|  |  | ||||||
| typedef enum DTCActive_e |  | ||||||
| { |  | ||||||
|   DTC_NONE, |  | ||||||
|   DTC_ACTIVE, |  | ||||||
|   DTC_PREVIOUS |  | ||||||
| } DTCActive_t; |  | ||||||
|  |  | ||||||
| typedef enum DTCSeverity_e |  | ||||||
| { |  | ||||||
|   DTC_INFO, |  | ||||||
|   DTC_WARN, |  | ||||||
|   DTC_CRITICAL |  | ||||||
| } DTCSeverity_t; |  | ||||||
|  |  | ||||||
| typedef struct DTCEntry_s |  | ||||||
| { |  | ||||||
|   DTCNums_t Number; |  | ||||||
|   uint32_t timestamp; |   uint32_t timestamp; | ||||||
|   DTCActive_t active; |   DTCActive_t active; | ||||||
|   DTCSeverity_t severity; |  | ||||||
|   uint32_t debugVal; |   uint32_t debugVal; | ||||||
| } DTCEntry_t; | } DTCEntry_t; | ||||||
|  |  | ||||||
| void MaintainDTC(DTCNums_t DTC_no, DTCSeverity_t DTC_severity, boolean active, uint32_t DebugValue = 0); | void MaintainDTC(DTCNum_t DTC_no, boolean active, uint32_t DebugValue = 0); | ||||||
| void ClearDTC(DTCNums_t DTC_no); | void ClearDTC(DTCNum_t DTC_no); | ||||||
| void ClearAllDTC(); | void ClearAllDTC(); | ||||||
| DTCNums_t getlastDTC(boolean only_active); | DTCNum_t getlastDTC(boolean only_active); | ||||||
| DTCNums_t getlastDTC_Severity(boolean only_active, DTCSeverity_t severity); | DTCNum_t ActiveDTCseverity(DTCSeverity_t severity); | ||||||
|  | DTCSeverity_t getSeverityForDTC(DTCNum_t targetCode); | ||||||
| void DTC_Process(); | void DTC_Process(); | ||||||
|  |  | ||||||
| extern DTCEntry_s DTCStorage[MAX_DTC_STORAGE]; | extern DTCEntry_t DTCStorage[MAX_DTC_STORAGE]; | ||||||
| #endif | #endif | ||||||
							
								
								
									
										85
									
								
								Software/include/dtc_defs.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								Software/include/dtc_defs.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | |||||||
|  | /** | ||||||
|  |  * @file dtc_defs.h | ||||||
|  |  * | ||||||
|  |  * @brief Header file for Diagnostic Trouble Code (DTC) definitions in the ChainLube application. | ||||||
|  |  * | ||||||
|  |  * This file contains definitions for Diagnostic Trouble Codes (DTC) in the ChainLube project. | ||||||
|  |  * It includes enums for DTC active status, severity levels, and specific DTC codes. | ||||||
|  |  * The file also defines an array of DTC definitions and a timestamp indicating the generation time. | ||||||
|  |  * | ||||||
|  |  * @note This file is auto-generated by a script on 2024-05-30 21:56:51. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   30.05.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef DTC_DEFS_H | ||||||
|  | #define DTC_DEFS_H | ||||||
|  |  | ||||||
|  | #include <stdint.h> | ||||||
|  |  | ||||||
|  | typedef uint32_t DTCNum_t; | ||||||
|  |  | ||||||
|  | typedef enum | ||||||
|  | { | ||||||
|  |   DTC_INACTIVE, | ||||||
|  |   DTC_ACTIVE, | ||||||
|  |   DTC_PREVIOUS | ||||||
|  | } DTCActive_t; | ||||||
|  |  | ||||||
|  | typedef enum | ||||||
|  | { | ||||||
|  |   DTC_NONE, | ||||||
|  |   DTC_INFO, | ||||||
|  |   DTC_WARN, | ||||||
|  |   DTC_CRITICAL | ||||||
|  | } DTCSeverity_t; | ||||||
|  |  | ||||||
|  | typedef struct { | ||||||
|  |   DTCNum_t code; | ||||||
|  |   DTCSeverity_t severity; | ||||||
|  | } DTC_t; | ||||||
|  |  | ||||||
|  | #define DTC_NO_DTC                     0 | ||||||
|  | #define DTC_BAT_CRITICAL               1 | ||||||
|  | #define DTC_BAT_LOW                    2 | ||||||
|  | #define DTC_NO_EEPROM_FOUND            3 | ||||||
|  | #define DTC_EEPROM_CFG_BAD             4 | ||||||
|  | #define DTC_EEPROM_PDS_BAD             5 | ||||||
|  | #define DTC_EEPROM_PDSADRESS_BAD       6 | ||||||
|  | #define DTC_EEPROM_VERSION_BAD         7 | ||||||
|  | #define DTC_FLASHFS_ERROR              8 | ||||||
|  | #define DTC_FLASHFS_VERSION_ERROR      9 | ||||||
|  | #define DTC_NO_BATMNON_FOUND           10 | ||||||
|  | #define DTC_NO_LORA_FOUND              11 | ||||||
|  | #define DTC_EEPROM_CFG_SANITY          12 | ||||||
|  | #define DTC_EEPROM_MIGRATE_FAILED      13 | ||||||
|  | #define DTC_FAKE_DTC_INFO              14 | ||||||
|  | #define DTC_FAKE_DTC_WARN              15 | ||||||
|  | #define DTC_FAKE_DTC_CRIT              16 | ||||||
|  | #define DTC_LAST_DTC                   17 | ||||||
|  |  | ||||||
|  | const DTC_t dtc_definitions[] = { | ||||||
|  |     { DTC_NO_DTC                    , DTC_NONE     }, // No Error | ||||||
|  |     { DTC_BAT_CRITICAL              , DTC_CRITICAL }, // Akku ist komplett leer. Den Akku aufladen! | ||||||
|  |     { DTC_BAT_LOW                   , DTC_WARN     }, // Akku ist unter der Warnschwelle. Den Akku demnächst aufladen | ||||||
|  |     { DTC_NO_EEPROM_FOUND           , DTC_CRITICAL }, // Es wurde kein EEPROM gefunden. Dies lässt einen Hardware-Defekt vermuten. | ||||||
|  |     { DTC_EEPROM_CFG_BAD            , DTC_CRITICAL }, // Die Checksumme der Config-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück | ||||||
|  |     { DTC_EEPROM_PDS_BAD            , DTC_CRITICAL }, // Die Checksumme der Betriebsdaten-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück | ||||||
|  |     { DTC_EEPROM_PDSADRESS_BAD      , DTC_CRITICAL }, // Die Adresse der Betriebsdaten-Partition im EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück | ||||||
|  |     { DTC_EEPROM_VERSION_BAD        , DTC_CRITICAL }, // Die Layout-Version des EEPROM stimmt nicht mit der Firmware-Version überein. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück | ||||||
|  |     { DTC_FLASHFS_ERROR             , DTC_CRITICAL }, // Der Flashspeicher konnte nicht initialisiert werden. Aktualisieren sie Flash & Firmware | ||||||
|  |     { DTC_FLASHFS_VERSION_ERROR     , DTC_CRITICAL }, // Die Version des Flashspeicher stimmt nicht mit der Firmware-Version überein. Aktualisieren sie den Flash mit der passenden Update-Datei | ||||||
|  |     { DTC_NO_BATMNON_FOUND          , DTC_CRITICAL }, // Es wurde keine Akkuüberwachung über I2C gefunden, Prüfen sie die Hardware! | ||||||
|  |     { DTC_NO_LORA_FOUND             , DTC_CRITICAL }, // Es konnte keine Verbindung zum LoRa-Transceiver hergestellt werden. Prüfen Sie die Hardware auf Defekte | ||||||
|  |     { DTC_EEPROM_CFG_SANITY         , DTC_WARN     }, // Ein oder mehrer Einstellungswerte sind ausserhalb plausibler Werte. Prüfen Sie Ihre Einstellungen | ||||||
|  |     { DTC_EEPROM_MIGRATE_FAILED     , DTC_CRITICAL }, // Es wurde ein altes EEPROm Image erkannt, konnte aber nicht migriert werden. EEPROM manuell zurück setzen und neue Einstellunge speichern. | ||||||
|  |     { DTC_FAKE_DTC_INFO             , DTC_INFO     }, // Ein Dummy-DTC der Schwere "Info" für Debugging-Zwecke | ||||||
|  |     { DTC_FAKE_DTC_WARN             , DTC_WARN     }, // Ein Dummy-DTC der Schwere "Warnung" für Debugging-Zwecke | ||||||
|  |     { DTC_FAKE_DTC_CRIT             , DTC_CRITICAL }, // Ein Dummy-DTC der Schwere "Kritisch" für Debugging-Zwecke | ||||||
|  |     { DTC_LAST_DTC                  , DTC_NONE     }  // Last Error | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif // DTC_DEFS_H | ||||||
|  |  | ||||||
|  | // CODEGENERATOR_CHECKSUM: 313d59949b074024df3c5d796f65e3bd518e34f0bb171185c30f008f21c19d30 | ||||||
| @@ -1,38 +1,63 @@ | |||||||
|  | /** | ||||||
|  |  * @file eeprom.h | ||||||
|  |  * | ||||||
|  |  * @brief Header file for configuration settings and EEPROM operations in the DE-Timer application. | ||||||
|  |  * | ||||||
|  |  * This file defines configuration settings for the DE-Timer project, including default values, | ||||||
|  |  * EEPROM structures, and functions for EEPROM operations. It also defines enums for different sources | ||||||
|  |  * of speed input, GPS baud rates, and CAN bus sources. Additionally, it includes functions for EEPROM handling | ||||||
|  |  * such as storing, retrieving, and formatting configuration data. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   09.01.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
| #ifndef _EEPROM_H_ | #ifndef _EEPROM_H_ | ||||||
| #define _EEPROM_H_ | #define _EEPROM_H_ | ||||||
|  |  | ||||||
| #include <Arduino.h> | #include <Arduino.h> | ||||||
| #include <Wire.h> | #include <Wire.h> | ||||||
| #include <I2C_eeprom.h> | #include <I2C_eeprom.h> | ||||||
|  |  | ||||||
| #include "globals.h" |  | ||||||
| #include "dtc.h" | #include "dtc.h" | ||||||
| #include "common.h" | #include "common.h" | ||||||
| #include "debugger.h" |  | ||||||
|  |  | ||||||
| #define I2C_EE_ADDRESS 0x50 | #define I2C_EE_ADDRESS 0x50 | ||||||
| #define EEPROM_SIZE_BYTES I2C_DEVICESIZE_24LC64 | #define EEPROM_STRUCTURE_REVISION 3 // Increment this version when changing EEPROM structures | ||||||
| #define EEPROM_ENDURANCE 1000000 | #define EEPROM_SIZE_BYTES I2C_DEVICESIZE_24LC256 | ||||||
|  |  | ||||||
| typedef enum | typedef enum Factions_e | ||||||
| { | { | ||||||
|   NONE, |   NONE, | ||||||
|   FACTION_1, |   FACTION_1, | ||||||
|   FACTION_2, |   FACTION_2, | ||||||
|   FACTION_3 |   FACTION_3 | ||||||
| } factions_t; | } Factions_t; | ||||||
|  |  | ||||||
|  | typedef enum EERequest_e | ||||||
|  | { | ||||||
|  |   EE_IDLE, | ||||||
|  |   EE_CFG_SAVE, | ||||||
|  |   EE_CFG_LOAD, | ||||||
|  |   EE_CFG_FORMAT, | ||||||
|  |   EE_PDS_SAVE, | ||||||
|  |   EE_PDS_LOAD, | ||||||
|  |   EE_PDS_FORMAT, | ||||||
|  |   EE_FORMAT_ALL, | ||||||
|  |   EE_ALL_SAVE | ||||||
|  |  | ||||||
|  | } EERequest_t; | ||||||
|  |  | ||||||
|  | // Structure for persistence data stored in EEPROM | ||||||
| typedef struct | typedef struct | ||||||
| { | { | ||||||
|   uint32_t writeCycleCounter = 0; |   uint32_t writeCycleCounter; | ||||||
|   uint32_t faction_1_timer = 0; |   uint32_t faction_1_timer; | ||||||
|   uint32_t faction_2_timer = 0; |   uint32_t faction_2_timer; | ||||||
|   uint32_t faction_3_timer = 0; |   uint32_t faction_3_timer; | ||||||
|   factions_t activeFaction = NONE; |   Factions_t activeFaction; | ||||||
|   uint32_t checksum = 0; |   uint32_t checksum; | ||||||
| } persistenceData_t; | } persistenceData_t; | ||||||
|  |  | ||||||
| extern persistenceData_t PersistenceData; |  | ||||||
| typedef enum | typedef enum | ||||||
| { | { | ||||||
|   BATTERY_UNDEFINED, |   BATTERY_UNDEFINED, | ||||||
| @@ -47,6 +72,7 @@ const char BatteryString[][10]{ | |||||||
|  |  | ||||||
| const size_t BatteryString_Elements = sizeof(BatteryString) / sizeof(BatteryString[0]); | const size_t BatteryString_Elements = sizeof(BatteryString) / sizeof(BatteryString[0]); | ||||||
|  |  | ||||||
|  | // Structure for configuration settings stored in EEPROM | ||||||
| typedef struct | typedef struct | ||||||
| { | { | ||||||
|   uint8_t EEPROM_Version; |   uint8_t EEPROM_Version; | ||||||
| @@ -55,24 +81,30 @@ typedef struct | |||||||
|   char Faction_1_Name[33]; |   char Faction_1_Name[33]; | ||||||
|   char Faction_2_Name[33]; |   char Faction_2_Name[33]; | ||||||
|   char Faction_3_Name[33]; |   char Faction_3_Name[33]; | ||||||
|  |   char wifi_ap_ssid[33]; | ||||||
|  |   char wifi_ap_password[64]; | ||||||
|  |   char wifi_client_ssid[33]; | ||||||
|  |   char wifi_client_password[64]; | ||||||
|  |   bool wifi_autoconnect; | ||||||
|   uint32_t checksum; |   uint32_t checksum; | ||||||
| } configData_t; | } configData_t; | ||||||
|  |  | ||||||
| extern configData_t ConfigData; | // Default configuration settings | ||||||
|  |  | ||||||
| const configData_t ConfigData_defaults = { | const configData_t ConfigData_defaults = { | ||||||
|     2,                      // EEPROM_Version (incerease this if anything on Layout changes!) |     2,               // EEPROM_Version (incerease this if anything on Layout changes!) | ||||||
|     BATTERY_LIPO_3S,        // batteryType |     BATTERY_LIPO_3S, // batteryType | ||||||
|     false,                  // active_faction_on_reboot |     false,           // active_faction_on_reboot | ||||||
|     "FACTION 1",            // Faction_1_Name |     "FACTION 1",     // Faction_1_Name | ||||||
|     "FACTION 2",            // Faction_2_Name |     "FACTION 2",     // Faction_2_Name | ||||||
|     "FACTION 3",            // Faction_3_Name |     "FACTION 3",     // Faction_3_Name | ||||||
|     0                       // checksum |     "ChainLube", | ||||||
|  |     QUOTE(WIFI_AP_PASSWORD), | ||||||
|  |     QUOTE(WIFI_SSID_CLIENT), | ||||||
|  |     QUOTE(WIFI_PASSWORD_CLIENT), | ||||||
|  |     true, | ||||||
|  |     0 // checksum | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const uint16_t startofConfigData = 16; |  | ||||||
| const uint16_t startofPersistence = 16 + sizeof(ConfigData) + (sizeof(ConfigData) % 16); |  | ||||||
|  |  | ||||||
| void InitEEPROM(); | void InitEEPROM(); | ||||||
| void EEPROM_Process(); | void EEPROM_Process(); | ||||||
| void StoreConfig_EEPROM(); | void StoreConfig_EEPROM(); | ||||||
| @@ -85,5 +117,9 @@ 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); | void MovePersistencePage_EEPROM(boolean reset); | ||||||
| uint32_t ConfigSanityCheck(bool autocorrect = false); | uint32_t ConfigSanityCheck(bool autocorrect = false); | ||||||
|  | bool validateWiFiString(char *string, size_t size); | ||||||
|  |  | ||||||
| #endif // _EEPROM_H_ | extern configData_t ConfigData; | ||||||
|  | extern persistenceData_t PersistenceData; | ||||||
|  | extern uint16_t eePersistenceMarker; | ||||||
|  | #endif // _CONFIG_H_ | ||||||
|   | |||||||
| @@ -2,68 +2,43 @@ | |||||||
| #define _GLOBALS_H_ | #define _GLOBALS_H_ | ||||||
|  |  | ||||||
| #include <Arduino.h> | #include <Arduino.h> | ||||||
|  | #include "eeprom.h" | ||||||
| typedef enum eSystem_Status | #include "common.h" | ||||||
| { |  | ||||||
|   sysStat_null, |  | ||||||
|   sysStat_Startup, |  | ||||||
|   sysStat_Normal, |  | ||||||
|   sysStat_Error, |  | ||||||
|   sysStat_Shutdown |  | ||||||
| } tSystem_Status; |  | ||||||
|  |  | ||||||
| const char sSystem_Status_txt[][9] = { |  | ||||||
|     "Null", |  | ||||||
|     "Startup", |  | ||||||
|     "Normal", |  | ||||||
|     "Error", |  | ||||||
|     "Shutdown" |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| typedef enum eEERequest |  | ||||||
| { |  | ||||||
|   EE_IDLE, |  | ||||||
|   EE_CFG_SAVE, |  | ||||||
|   EE_CFG_LOAD, |  | ||||||
|   EE_CFG_FORMAT, |  | ||||||
|   EE_PDS_SAVE, |  | ||||||
|   EE_PDS_LOAD, |  | ||||||
|   EE_PDS_FORMAT, |  | ||||||
|   EE_FORMAT_ALL, |  | ||||||
|   EE_ALL_SAVE |  | ||||||
|  |  | ||||||
| } tEERequest; |  | ||||||
|  |  | ||||||
| typedef struct Globals_s | typedef struct Globals_s | ||||||
| { | { | ||||||
|   char DeviceName[33]; |   tSystem_Status systemStatus = sysStat_Startup; /**< Current system status */ | ||||||
|   char DeviceName_ID[43]; |   tSystem_Status resumeStatus = sysStat_Startup; /**< Status to resume after rain mode */ | ||||||
|   char FlashVersion[10]; |   char systemStatustxt[16] = "";                 /**< Text representation of system status */ | ||||||
|   tSystem_Status systemStatus = sysStat_Startup; |   EERequest_t requestEEAction = EE_IDLE;         /**< EEPROM-related request */ | ||||||
|   eEERequest requestEEAction = EE_IDLE; |   char DeviceName[33];                           /**< Device name */ | ||||||
|   uint16_t eePersistanceAdress; |   char FlashVersion[10];                         /**< Flash version */ | ||||||
|  |   uint16_t eePersistanceAdress;                  /**< EEPROM persistence address */ | ||||||
|   bool hasDTC; |   bool hasDTC; | ||||||
|   int loadvoltage_mV = 0; |   int loadvoltage_mV = 0; | ||||||
|   int battery_level = 0; |   int battery_level = 0; | ||||||
|   bool timer_disabled = false; |   bool timer_disabled = false; | ||||||
| } Globals_t; | } Globals_t; | ||||||
|  |  | ||||||
| extern Globals_t globals; | extern Globals_t globals; /**< Global variable struct */ | ||||||
|  |  | ||||||
| typedef struct Constants_s | typedef struct Constants_s | ||||||
| { | { | ||||||
|   uint8_t FW_Version_major; |   uint8_t FW_Version_major;             /**< Firmware version major number */ | ||||||
|   uint8_t FW_Version_minor; |   uint8_t FW_Version_minor;             /**< Firmware version minor number */ | ||||||
|   uint8_t Required_Flash_Version_major; |   uint8_t Required_Flash_Version_major; /**< Required flash version major number */ | ||||||
|   uint8_t Required_Flash_Version_minor; |   uint8_t Required_Flash_Version_minor; /**< Required flash version minor number */ | ||||||
|   char GitHash[11]; |   char GitHash[11];                     /**< Git hash string */ | ||||||
| } Constants_t; | } Constants_t; | ||||||
|  |  | ||||||
| const Constants_t constants PROGMEM = { | const Constants_t constants PROGMEM = { | ||||||
|  1,5,     // Firmware_Version |     1, 4,   // Firmware_Version | ||||||
|  1,4,     // Required Flash Version |     1, 4,   // Required Flash Version | ||||||
|  GIT_REV  // Git-Hash-String |     GIT_REV // Git-Hash-String | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Initializes global variables. | ||||||
|  |  */ | ||||||
| void initGlobals(); | void initGlobals(); | ||||||
|  |  | ||||||
| #endif | #endif // _GLOBALS_H_ | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								Software/include/struct2json.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Software/include/struct2json.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | /** | ||||||
|  |  * @file struct2json.h | ||||||
|  |  * | ||||||
|  |  * @brief Header file for converting structs to JSON objects. | ||||||
|  |  * | ||||||
|  |  * @note This file is auto-generated by a script on 2024-05-30 22:54:25. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   30.05.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #ifndef _STRUCT2JSON_H_ | ||||||
|  | #define _STRUCT2JSON_H_ | ||||||
|  |  | ||||||
|  | #include <Arduino.h> | ||||||
|  | #include <ArduinoJson.h> | ||||||
|  |  | ||||||
|  | #include "eeprom.h" | ||||||
|  |  | ||||||
|  | void generateJsonObject_ConfigData(JsonObject data); | ||||||
|  | void generateJsonObject_PersistenceData(JsonObject data); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #endif /* _STRUCT2JSON_H_ */ | ||||||
|  |  | ||||||
|  | // CODEGENERATOR_CHECKSUM: 735cd4daf9a46bd773bdf5e6cd5a58d61b0d877196399bc2784a0d0ea7af717d | ||||||
| @@ -1,3 +1,16 @@ | |||||||
|  | /** | ||||||
|  |  * @file webui.h | ||||||
|  |  * | ||||||
|  |  * @brief Header file for the web-based user interface (WebUI) in the ChainLube application. | ||||||
|  |  * | ||||||
|  |  * This file contains declarations for functions related to the initialization and processing of the | ||||||
|  |  * web-based user interface (WebUI). It includes the necessary libraries and dependencies for handling | ||||||
|  |  * web server functionality, asynchronous JSON operations, and live debugging through WebSockets. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   09.01.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
| #ifndef _WEBUI_H_ | #ifndef _WEBUI_H_ | ||||||
| #define _WEBUI_H_ | #define _WEBUI_H_ | ||||||
|  |  | ||||||
| @@ -11,14 +24,26 @@ | |||||||
| #include <AsyncJson.h> | #include <AsyncJson.h> | ||||||
| #include <ArduinoJson.h> | #include <ArduinoJson.h> | ||||||
|  |  | ||||||
| #include "eeprom.h" | #include "config.h" | ||||||
| #include "globals.h" | #include "globals.h" | ||||||
| #include "dtc.h" | #include "dtc.h" | ||||||
| #include "common.h" | #include "common.h" | ||||||
| #include "debugger.h" | #include "debugger.h" | ||||||
|  | #include "struct2json.h" | ||||||
|  |  | ||||||
|  | typedef enum | ||||||
|  | { | ||||||
|  |     info, | ||||||
|  |     success, | ||||||
|  |     warning, | ||||||
|  |     error | ||||||
|  | } NotificationType_t; | ||||||
|  |  | ||||||
| void initWebUI(); | void initWebUI(); | ||||||
| void Webserver_Process(); | void Webserver_Process(); | ||||||
| void Websocket_PushLiveDebug(String Message); | void Webserver_Shutdown(); | ||||||
|  |  | ||||||
| #endif | void Websocket_PushLiveDebug(String Message); | ||||||
|  | void Websocket_PushNotification(String Message, NotificationType_t type); | ||||||
|  |  | ||||||
|  | #endif // _WEBUI_H_ | ||||||
|   | |||||||
| @@ -1,52 +1,79 @@ | |||||||
|  | /** | ||||||
|  |  * @file debugger.cpp | ||||||
|  |  * @brief Implementation of debugging functions for monitoring and diagnostics. | ||||||
|  |  * | ||||||
|  |  * This file contains the implementation of various debugging functions to monitor | ||||||
|  |  * and diagnose the system. It includes functions to print system information, WiFi | ||||||
|  |  * details, EEPROM status, dump configuration settings, dump persistence data, show | ||||||
|  |  * Diagnostic Trouble Codes (DTCs), and more. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date 09.04.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
| #include "debugger.h" | #include "debugger.h" | ||||||
|  |  | ||||||
| DebugStatus_t DebuggerStatus[dbg_cntElements]; | DebugStatus_t DebuggerStatus[dbg_cntElements]; | ||||||
|  |  | ||||||
| String IpAddress2String(const IPAddress &ipAddress); |  | ||||||
| void processCmdDebug(String command); | void processCmdDebug(String command); | ||||||
| void Debug_formatCFG(); | void Debug_formatCFG(); | ||||||
| void Debug_formatPersistence(); | void Debug_formatPersistence(); | ||||||
| void Debug_printSystemInfo(); | void Debug_printSystemInfo(); | ||||||
| void Debug_printWifiInfo(); | void Debug_printWifiInfo(); | ||||||
| void Debug_CheckEEPOM(); | void Debug_CheckEEPOM(bool autocorrect); | ||||||
| void Debug_dumpConfig(); | void Debug_dumpConfig(); | ||||||
| void Debug_dumpPersistance(); | void Debug_dumpPersistance(); | ||||||
| void Debug_ShowDTCs(); | void Debug_ShowDTCs(); | ||||||
| void Debug_dumpGlobals(); | void Debug_dumpGlobals(); | ||||||
| void Debug_printHelp(); | void Debug_printHelp(); | ||||||
|  | const char *uint32_to_binary_string(uint32_t num); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Initializes the debugger by setting the initial status for different debug ports. | ||||||
|  |  *        Serial debug output is turned off. | ||||||
|  |  */ | ||||||
| void initDebugger() | void initDebugger() | ||||||
| { | { | ||||||
|  |     // Set the initial status of debug ports | ||||||
|     DebuggerStatus[dbg_Serial] = disabled; |     DebuggerStatus[dbg_Serial] = disabled; | ||||||
|     DebuggerStatus[dbg_Webui] = disabled; |     DebuggerStatus[dbg_Webui] = disabled; | ||||||
|  |  | ||||||
|  |     // Disable serial debug output | ||||||
|     Serial.setDebugOutput(false); |     Serial.setDebugOutput(false); | ||||||
| } | } | ||||||
|  | /** | ||||||
|  |  * @brief Processes incoming debug commands from the Serial interface. | ||||||
|  |  *        It reads characters from Serial and interprets them as commands. | ||||||
|  |  *        The recognized commands are processed accordingly. | ||||||
|  |  */ | ||||||
| void Debug_Process() | void Debug_Process() | ||||||
| { | { | ||||||
|  |     // Enumeration for tracking the state of input processing | ||||||
|     typedef enum InputProcessed_e |     typedef enum InputProcessed_e | ||||||
|     { |     { | ||||||
|         IDLE, |         IDLE,         ///< No command processing is in progress | ||||||
|         CMD_COMPLETE, |         CMD_COMPLETE, ///< Received a complete command | ||||||
|         CMD_ABORT, |         CMD_ABORT,    ///< Received an abort command (Esc) | ||||||
|         CMD_OVERFLOW |         CMD_OVERFLOW  ///< Input buffer overflow occurred | ||||||
|     } InputProcessed_t; |     } InputProcessed_t; | ||||||
|  |  | ||||||
|     static unsigned int inputCnt = 0; |     static unsigned int inputCnt = 0;       ///< Counter for characters in the input buffer | ||||||
|     static char inputBuffer[32]; |     static char inputBuffer[32];            ///< Buffer to store the received characters | ||||||
|     InputProcessed_t InputProcessed = IDLE; |     InputProcessed_t InputProcessed = IDLE; ///< State variable for input processing | ||||||
|  |  | ||||||
|  |     // Check if there are characters available in the Serial input buffer | ||||||
|     if (Serial.available()) |     if (Serial.available()) | ||||||
|     { |     { | ||||||
|         char inputChar = Serial.read(); |         char inputChar = Serial.read(); | ||||||
|  |  | ||||||
|  |         // Process the received character based on its value | ||||||
|         switch (inputChar) |         switch (inputChar) | ||||||
|         { |         { | ||||||
|         case '\n': |         case '\n': | ||||||
|             inputBuffer[inputCnt] = 0; // terminate the String |             inputBuffer[inputCnt] = 0; // terminate the String | ||||||
|             inputCnt = 0; |             inputCnt = 0; | ||||||
|             InputProcessed = CMD_COMPLETE; |             InputProcessed = CMD_COMPLETE; | ||||||
|  |             Serial.write(inputChar); | ||||||
|             break; |             break; | ||||||
|  |  | ||||||
|         case 0x1B: // Esc |         case 0x1B: // Esc | ||||||
| @@ -55,15 +82,17 @@ void Debug_Process() | |||||||
|             InputProcessed = CMD_ABORT; |             InputProcessed = CMD_ABORT; | ||||||
|             break; |             break; | ||||||
|  |  | ||||||
|         case 0x21 ... 0x7E: // its a real letter or sign and not some control-chars |         case 0x21 ... 0x7E: // it's a real letter or sign and not some control-chars | ||||||
|             inputBuffer[inputCnt] = inputChar; |             inputBuffer[inputCnt] = inputChar; | ||||||
|             inputCnt++; |             inputCnt++; | ||||||
|  |             Serial.write(inputChar); | ||||||
|             break; |             break; | ||||||
|  |  | ||||||
|         default: |         default: | ||||||
|             break; |             break; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // Check for input buffer overflow | ||||||
|         if (inputCnt > sizeof(inputBuffer)) |         if (inputCnt > sizeof(inputBuffer)) | ||||||
|         { |         { | ||||||
|             inputCnt = 0; |             inputCnt = 0; | ||||||
| @@ -72,6 +101,7 @@ void Debug_Process() | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // Process the command based on the detected state of input processing | ||||||
|     switch (InputProcessed) |     switch (InputProcessed) | ||||||
|     { |     { | ||||||
|     case CMD_ABORT: |     case CMD_ABORT: | ||||||
| @@ -83,40 +113,65 @@ void Debug_Process() | |||||||
|         break; |         break; | ||||||
|  |  | ||||||
|     case CMD_OVERFLOW: |     case CMD_OVERFLOW: | ||||||
|         Debug_pushMessage("input Buffer overflow\n"); |         Debug_pushMessage("Input buffer overflow\n"); | ||||||
|         break; |         break; | ||||||
|  |  | ||||||
|     default: |     default: | ||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (InputProcessed != IDLE) | ||||||
|  |         Serial.print(">"); | ||||||
|  |  | ||||||
|     InputProcessed = IDLE; |     InputProcessed = IDLE; | ||||||
| } | } | ||||||
|  | /** | ||||||
|  |  * @brief Sets the status of a specific debug port (Serial or WebUI). | ||||||
|  |  *        Updates the status in the DebuggerStatus array and provides debug messages. | ||||||
|  |  * | ||||||
|  |  * @param port   The debug port to set the status for (dbg_Serial or dbg_Webui). | ||||||
|  |  * @param status The status to set (enabled or disabled). | ||||||
|  |  */ | ||||||
| void SetDebugportStatus(DebugPorts_t port, DebugStatus_t status) | void SetDebugportStatus(DebugPorts_t port, DebugStatus_t status) | ||||||
| { | { | ||||||
|  |     // Display a debug message based on the provided status | ||||||
|     if (status == disabled) |     if (status == disabled) | ||||||
|         Debug_pushMessage("disable DebugPort %s\n", sDebugPorts[port]); |         Debug_pushMessage("Disable DebugPort %s\n", sDebugPorts[port]); | ||||||
|  |  | ||||||
|  |     // Update the status in the DebuggerStatus array | ||||||
|     DebuggerStatus[port] = status; |     DebuggerStatus[port] = status; | ||||||
|  |  | ||||||
|  |     // Display a debug message based on the updated status | ||||||
|     if (status == enabled) |     if (status == enabled) | ||||||
|         Debug_pushMessage("enabled DebugPort %s\n", sDebugPorts[port]); |         Debug_pushMessage("Enabled DebugPort %s\n", sDebugPorts[port]); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Pushes a formatted debug message to the enabled debug ports (Serial or WebUI). | ||||||
|  |  * | ||||||
|  |  * @param format The format string for the debug message. | ||||||
|  |  * @param ...    Additional arguments for formatting the message. | ||||||
|  |  */ | ||||||
| void Debug_pushMessage(const char *format, ...) | void Debug_pushMessage(const char *format, ...) | ||||||
| { | { | ||||||
|  |     // Check if either the Serial or WebUI debug port is enabled | ||||||
|     if ((DebuggerStatus[dbg_Serial] == enabled) || (DebuggerStatus[dbg_Webui] == enabled)) |     if ((DebuggerStatus[dbg_Serial] == enabled) || (DebuggerStatus[dbg_Webui] == enabled)) | ||||||
|     { |     { | ||||||
|         char buff[64]; |         char buff[128]; // Buffer to hold the formatted message | ||||||
|         va_list arg; |         va_list arg;    // Variable argument list for vsnprintf | ||||||
|         va_start(arg, format); |         va_start(arg, format); | ||||||
|  |  | ||||||
|  |         // Format the message and store it in the buffer | ||||||
|         vsnprintf(buff, sizeof(buff), format, arg); |         vsnprintf(buff, sizeof(buff), format, arg); | ||||||
|         va_end(arg); |         va_end(arg); | ||||||
|  |  | ||||||
|  |         // Send the message to the Serial debug port if enabled | ||||||
|         if (DebuggerStatus[dbg_Serial] == enabled) |         if (DebuggerStatus[dbg_Serial] == enabled) | ||||||
|         { |         { | ||||||
|             Serial.print(buff); |             Serial.print(buff); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // Push the message to the WebUI debug port if enabled | ||||||
|         if (DebuggerStatus[dbg_Webui] == enabled) |         if (DebuggerStatus[dbg_Webui] == enabled) | ||||||
|         { |         { | ||||||
|             Websocket_PushLiveDebug(String(buff)); |             Websocket_PushLiveDebug(String(buff)); | ||||||
| @@ -124,8 +179,15 @@ void Debug_pushMessage(const char *format, ...) | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Processes a debug command and performs corresponding actions. | ||||||
|  |  * | ||||||
|  |  * @param command The debug command to be processed. | ||||||
|  |  */ | ||||||
| void processCmdDebug(String command) | void processCmdDebug(String command) | ||||||
| { | { | ||||||
|  |     // Check the received command and execute corresponding actions | ||||||
|     if (command == "help") |     if (command == "help") | ||||||
|         Debug_printHelp(); |         Debug_printHelp(); | ||||||
|     else if (command == "sysinfo") |     else if (command == "sysinfo") | ||||||
| @@ -137,7 +199,9 @@ void processCmdDebug(String command) | |||||||
|     else if (command == "formatPDS") |     else if (command == "formatPDS") | ||||||
|         Debug_formatPersistence(); |         Debug_formatPersistence(); | ||||||
|     else if (command == "checkEE") |     else if (command == "checkEE") | ||||||
|         Debug_CheckEEPOM(); |         Debug_CheckEEPOM(false); | ||||||
|  |     else if (command == "checkEEfix") | ||||||
|  |         Debug_CheckEEPOM(true); | ||||||
|     else if (command == "dumpEE1k") |     else if (command == "dumpEE1k") | ||||||
|         dumpEEPROM(0, 1024); |         dumpEEPROM(0, 1024); | ||||||
|     else if (command == "dumpEE") |     else if (command == "dumpEE") | ||||||
| @@ -150,28 +214,55 @@ void processCmdDebug(String command) | |||||||
|         Debug_dumpPersistance(); |         Debug_dumpPersistance(); | ||||||
|     else if (command == "saveEE") |     else if (command == "saveEE") | ||||||
|         globals.requestEEAction = EE_ALL_SAVE; |         globals.requestEEAction = EE_ALL_SAVE; | ||||||
|     else if (command == "showdtc") |  | ||||||
|         Debug_ShowDTCs(); |  | ||||||
|     else if (command == "dumpGlobals") |     else if (command == "dumpGlobals") | ||||||
|         Debug_dumpGlobals(); |         Debug_dumpGlobals(); | ||||||
|     else if (command == "sdbg") |     else if (command == "sdbg") | ||||||
|         SetDebugportStatus(dbg_Serial, enabled); |         SetDebugportStatus(dbg_Serial, enabled); | ||||||
|  |     else if (command == "dtc_show") | ||||||
|  |         Debug_ShowDTCs(); | ||||||
|  |     else if (command == "dtc_clear") | ||||||
|  |         ClearAllDTC(); | ||||||
|  |     else if (command == "dtc_crit") | ||||||
|  |         MaintainDTC(DTC_FAKE_DTC_CRIT, true, millis()); | ||||||
|  |     else if (command == "dtc_warn") | ||||||
|  |         MaintainDTC(DTC_FAKE_DTC_WARN, true, millis()); | ||||||
|  |     else if (command == "dtc_info") | ||||||
|  |         MaintainDTC(DTC_FAKE_DTC_INFO, true, millis()); | ||||||
|  |     else if (command == "notify_error") | ||||||
|  |         Websocket_PushNotification("Debug Error Notification", error); | ||||||
|  |     else if (command == "notify_warning") | ||||||
|  |         Websocket_PushNotification("Debug Warning Notification", warning); | ||||||
|  |     else if (command == "notify_success") | ||||||
|  |         Websocket_PushNotification("Debug Success Notification", success); | ||||||
|  |     else if (command == "notify_info") | ||||||
|  |         Websocket_PushNotification("Debug Info Notification", info); | ||||||
|     else |     else | ||||||
|         Debug_pushMessage("unknown Command\n"); |         Debug_pushMessage("unknown Command\n"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Formats the Config-EEPROM and resets it to default values. | ||||||
|  |  *        Prints a debug message after formatting. | ||||||
|  |  */ | ||||||
| void Debug_formatCFG() | void Debug_formatCFG() | ||||||
| { | { | ||||||
|     Debug_pushMessage("Formatting Config-EEPROM and reseting to default\n"); |     Debug_pushMessage("Formatting Config-EEPROM and resetting to default\n"); | ||||||
|     FormatConfig_EEPROM(); |     FormatConfig_EEPROM(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Formats the Persistence-EEPROM and resets it to default values. | ||||||
|  |  *        Prints a debug message after formatting. | ||||||
|  |  */ | ||||||
| void Debug_formatPersistence() | void Debug_formatPersistence() | ||||||
| { | { | ||||||
|     Debug_pushMessage("Formatting Persistence-EEPROM and reseting to default\n"); |     Debug_pushMessage("Formatting Persistence-EEPROM and resetting to default\n"); | ||||||
|     FormatPersistence_EEPROM(); |     FormatPersistence_EEPROM(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Prints system information and status to the debug output. | ||||||
|  |  */ | ||||||
| void Debug_printSystemInfo() | void Debug_printSystemInfo() | ||||||
| { | { | ||||||
|     Debug_pushMessage("Hostname: %s\n", globals.DeviceName); |     Debug_pushMessage("Hostname: %s\n", globals.DeviceName); | ||||||
| @@ -190,10 +281,13 @@ void Debug_printSystemInfo() | |||||||
|                                                                         : ideMode == FM_DOUT   ? "DOUT" |                                                                         : ideMode == FM_DOUT   ? "DOUT" | ||||||
|                                                                                                : "UNKNOWN")); |                                                                                                : "UNKNOWN")); | ||||||
|     Debug_pushMessage("OTA-Pass: %s\n", QUOTE(ADMIN_PASSWORD)); |     Debug_pushMessage("OTA-Pass: %s\n", QUOTE(ADMIN_PASSWORD)); | ||||||
|     Debug_pushMessage("Git-Revison: %s\n", constants.GitHash); |     Debug_pushMessage("Git-Revision: %s\n", constants.GitHash); | ||||||
|     Debug_pushMessage("Sw-Version: %d.%02d\n", constants.FW_Version_major, constants.FW_Version_minor); |     Debug_pushMessage("Sw-Version: %d.%02d\n", constants.FW_Version_major, constants.FW_Version_minor); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Dumps the current configuration parameters to the debug output. | ||||||
|  |  */ | ||||||
| void Debug_dumpConfig() | void Debug_dumpConfig() | ||||||
| { | { | ||||||
|     Debug_pushMessage("batteryType: %d\n", ConfigData.batteryType); |     Debug_pushMessage("batteryType: %d\n", ConfigData.batteryType); | ||||||
| @@ -201,6 +295,9 @@ void Debug_dumpConfig() | |||||||
|     Debug_pushMessage("checksum: 0x%08X\n", ConfigData.checksum); |     Debug_pushMessage("checksum: 0x%08X\n", ConfigData.checksum); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Dumps the global variables and their values to the debug output. | ||||||
|  |  */ | ||||||
| void Debug_dumpGlobals() | void Debug_dumpGlobals() | ||||||
| { | { | ||||||
|     Debug_pushMessage("systemStatus: %d\n", globals.systemStatus); |     Debug_pushMessage("systemStatus: %d\n", globals.systemStatus); | ||||||
| @@ -208,12 +305,14 @@ void Debug_dumpGlobals() | |||||||
|     Debug_pushMessage("loadvoltage_mV: %d\n", globals.loadvoltage_mV); |     Debug_pushMessage("loadvoltage_mV: %d\n", globals.loadvoltage_mV); | ||||||
|     Debug_pushMessage("requestEEAction: %d\n", globals.requestEEAction); |     Debug_pushMessage("requestEEAction: %d\n", globals.requestEEAction); | ||||||
|     Debug_pushMessage("DeviceName: %s\n", globals.DeviceName); |     Debug_pushMessage("DeviceName: %s\n", globals.DeviceName); | ||||||
|     Debug_pushMessage("DeviceName_ID: %s\n", globals.DeviceName_ID); |  | ||||||
|     Debug_pushMessage("FlashVersion: %s\n", globals.FlashVersion); |     Debug_pushMessage("FlashVersion: %s\n", globals.FlashVersion); | ||||||
|     Debug_pushMessage("eePersistanceAdress: %d\n", globals.eePersistanceAdress); |     Debug_pushMessage("eePersistanceAdress: %d\n", globals.eePersistanceAdress); | ||||||
|     Debug_pushMessage("hasDTC: %d\n", globals.hasDTC); |     Debug_pushMessage("hasDTC: %d\n", globals.hasDTC); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Dumps the persistence data variables and their values to the debug output. | ||||||
|  |  */ | ||||||
| void Debug_dumpPersistance() | void Debug_dumpPersistance() | ||||||
| { | { | ||||||
|     Debug_pushMessage("writeCycleCounter: %d\n", PersistenceData.writeCycleCounter); |     Debug_pushMessage("writeCycleCounter: %d\n", PersistenceData.writeCycleCounter); | ||||||
| @@ -225,12 +324,21 @@ void Debug_dumpPersistance() | |||||||
|     Debug_pushMessage("PSD Adress: 0x%04X\n", globals.eePersistanceAdress); |     Debug_pushMessage("PSD Adress: 0x%04X\n", globals.eePersistanceAdress); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Prints information related to WiFi to the debug output. | ||||||
|  |  */ | ||||||
| void Debug_printWifiInfo() | void Debug_printWifiInfo() | ||||||
| { | { | ||||||
|  |     Debug_pushMessage("IP Adress: %s\n", WiFi.localIP().toString().c_str()); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Debug_CheckEEPOM() | /** | ||||||
|  |  * @brief Checks the EEPROM data integrity by calculating and comparing checksums. | ||||||
|  |  *        Prints the result to the debug output. | ||||||
|  |  */ | ||||||
|  | void Debug_CheckEEPOM(bool autocorrect) | ||||||
| { | { | ||||||
|  |     // Check PersistenceData EEPROM checksum | ||||||
|     uint32_t checksum = PersistenceData.checksum; |     uint32_t checksum = PersistenceData.checksum; | ||||||
|     PersistenceData.checksum = 0; |     PersistenceData.checksum = 0; | ||||||
|  |  | ||||||
| @@ -245,6 +353,7 @@ void Debug_CheckEEPOM() | |||||||
|  |  | ||||||
|     PersistenceData.checksum = checksum; |     PersistenceData.checksum = checksum; | ||||||
|  |  | ||||||
|  |     // Check ConfigData EEPROM checksum | ||||||
|     checksum = ConfigData.checksum; |     checksum = ConfigData.checksum; | ||||||
|     ConfigData.checksum = 0; |     ConfigData.checksum = 0; | ||||||
|  |  | ||||||
| @@ -257,26 +366,45 @@ void Debug_CheckEEPOM() | |||||||
|         Debug_pushMessage("ConfigData EEPROM Checksum BAD\n"); |         Debug_pushMessage("ConfigData EEPROM Checksum BAD\n"); | ||||||
|     } |     } | ||||||
|     ConfigData.checksum = checksum; |     ConfigData.checksum = checksum; | ||||||
|  |  | ||||||
|  |     uint32_t sanitycheck = ConfigSanityCheck(autocorrect); | ||||||
|  |  | ||||||
|  |     if (sanitycheck == 0) | ||||||
|  |     { | ||||||
|  |         Debug_pushMessage("ConfigData Sanity Check OK\n"); | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |         Debug_pushMessage("ConfigData Sanity Check BAD: %s\n", uint32_to_binary_string(sanitycheck)); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Displays Diagnostic Trouble Codes (DTCs) along with their timestamps, | ||||||
|  |  *        status, and severity in a formatted manner. | ||||||
|  |  */ | ||||||
| void Debug_ShowDTCs() | void Debug_ShowDTCs() | ||||||
| { | { | ||||||
|     char buff_timestamp[16]; // Format: DD-hh:mm:ss:xxx |     char buff_timestamp[16]; // Format: DD-hh:mm:ss:xxx | ||||||
|     char buff_active[9]; |     char buff_active[9]; | ||||||
|  |  | ||||||
|     Debug_pushMessage("\n      timestamp | DTC-Nr. |   status | severity | debugVal\n"); |     // Header for the DTC display | ||||||
|  |     Debug_pushMessage("\n      timestamp | DTC-Nr. |   status | severity\n"); | ||||||
|  |  | ||||||
|  |     // Iterate through DTCStorage and display each entry | ||||||
|     for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++) |     for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++) | ||||||
|     { |     { | ||||||
|         if (DTCStorage[i].Number < DTC_LAST_DTC) |         if (DTCStorage[i].Number < DTC_LAST_DTC) | ||||||
|         { |         { | ||||||
|  |             // Format timestamp | ||||||
|             sprintf(buff_timestamp, "%02d-%02d:%02d:%02d:%03d", |             sprintf(buff_timestamp, "%02d-%02d:%02d:%02d:%03d", | ||||||
|                     DTCStorage[i].timestamp / 86400000,    // Days |                     DTCStorage[i].timestamp / 86400000,    // Days | ||||||
|                     DTCStorage[i].timestamp / 360000 % 24, // Hours |                     DTCStorage[i].timestamp / 360000 % 24, // Hours | ||||||
|                     DTCStorage[i].timestamp / 60000 % 60,  // Minutes |                     DTCStorage[i].timestamp / 60000 % 60,  // Minutes | ||||||
|                     DTCStorage[i].timestamp / 1000 % 60,   // Seconds |                     DTCStorage[i].timestamp / 1000 % 60,   // Seconds | ||||||
|                     DTCStorage[i].timestamp % 1000);       // milliseconds |                     DTCStorage[i].timestamp % 1000);       // Milliseconds | ||||||
|  |  | ||||||
|  |             // Determine DTC status | ||||||
|             if (DTCStorage[i].active == DTC_ACTIVE) |             if (DTCStorage[i].active == DTC_ACTIVE) | ||||||
|                 strcpy(buff_active, "active"); |                 strcpy(buff_active, "active"); | ||||||
|             else if (DTCStorage[i].active == DTC_PREVIOUS) |             else if (DTCStorage[i].active == DTC_PREVIOUS) | ||||||
| @@ -284,19 +412,54 @@ void Debug_ShowDTCs() | |||||||
|             else |             else | ||||||
|                 strcpy(buff_active, "none"); |                 strcpy(buff_active, "none"); | ||||||
|  |  | ||||||
|             Debug_pushMessage("%s   %7d   %8s   %8d   %8d\n", buff_timestamp, DTCStorage[i].Number, buff_active, DTCStorage[i].severity, DTCStorage[i].debugVal); |             // Display DTC information | ||||||
|  |             Debug_pushMessage("%s   %7d   %8s   %8d\n", buff_timestamp, DTCStorage[i].Number, buff_active); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Displays the help commands for debugging through Serial or WebUI. | ||||||
|  |  *        Each command is printed individually in a formatted manner. | ||||||
|  |  */ | ||||||
| void Debug_printHelp() | void Debug_printHelp() | ||||||
| { | { | ||||||
|     char buff[64]; |     char buff[64]; | ||||||
|  |  | ||||||
|     for (unsigned int i = sizeof(helpCmd) / 63; i < sizeof(helpCmd) / 63; i++) |     // Iterate through helpCmd and display each command | ||||||
|  |     for (unsigned int i = 0; i < sizeof(helpCmd) / 63; i++) | ||||||
|     { |     { | ||||||
|  |         // Copy a portion of helpCmd to buff for display | ||||||
|         memcpy_P(buff, (helpCmd + (i * 63)), 63); |         memcpy_P(buff, (helpCmd + (i * 63)), 63); | ||||||
|         buff[63] = 0; |         buff[63] = 0; | ||||||
|  |  | ||||||
|  |         // Display the help command | ||||||
|         Debug_pushMessage(buff); |         Debug_pushMessage(buff); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Convert a uint32_t value to a binary string with nibbles separated by a space. | ||||||
|  |  * | ||||||
|  |  * This function takes a uint32_t value and converts it to a binary string | ||||||
|  |  * representation. The binary string is stored in a static buffer and returned | ||||||
|  |  * as a const char pointer. Each nibble (4 bits) in the binary representation | ||||||
|  |  * is separated by a space. The buffer is overwritten on subsequent calls to | ||||||
|  |  * this function. | ||||||
|  |  * | ||||||
|  |  * @param num The uint32_t value to convert. | ||||||
|  |  * @return A pointer to a const char string containing the binary representation | ||||||
|  |  * of the input number with nibbles separated by a space. | ||||||
|  |  */ | ||||||
|  | const char* uint32_to_binary_string(uint32_t num) { | ||||||
|  |     static char binary_str[65]; // 32 bits + 31 spaces + null terminator | ||||||
|  |     int i, j; | ||||||
|  |     for (i = 31, j = 0; i >= 0; i--, j++) { | ||||||
|  |         binary_str[j] = ((num >> i) & 1) ? '1' : '0'; | ||||||
|  |         if (i % 4 == 0 && i != 0) { | ||||||
|  |             binary_str[++j] = ' '; // Insert space after every nibble | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     binary_str[j] = '\0'; // Null terminator | ||||||
|  |     return binary_str; | ||||||
|  | } | ||||||
| @@ -1,37 +1,61 @@ | |||||||
|  | /** | ||||||
|  |  * @file dtc.cpp | ||||||
|  |  * @brief Implementation of functions related to Diagnostic Trouble Codes (DTCs). | ||||||
|  |  * | ||||||
|  |  * This file contains the implementation of functions that manage the status | ||||||
|  |  * and registration of Diagnostic Trouble Codes in the system. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date 09.01.2024 | ||||||
|  |  */ | ||||||
| #include "dtc.h" | #include "dtc.h" | ||||||
| #include "debugger.h" | #include "debugger.h" | ||||||
|  |  | ||||||
| DTCEntry_s DTCStorage[MAX_DTC_STORAGE]; | DTCEntry_t DTCStorage[MAX_DTC_STORAGE]; | ||||||
|  |  | ||||||
| void MaintainDTC(DTCNums_t DTC_no, DTCSeverity_t DTC_severity, boolean active, uint32_t DebugValue) | // Function implementations... | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Maintains the status of Diagnostic Trouble Codes (DTCs) in the DTCStorage array. | ||||||
|  |  *        Updates the status of existing DTCs or adds new ones based on their activity. | ||||||
|  |  * | ||||||
|  |  * @param DTC_no      The number of the Diagnostic Trouble Code. | ||||||
|  |  * @param active      Indicates whether the DTC is active (true) or inactive (false). | ||||||
|  |  * @param DebugValue  Additional debugging information associated with the DTC. | ||||||
|  |  */ | ||||||
|  | void MaintainDTC(DTCNum_t DTC_no, boolean active, uint32_t DebugValue) | ||||||
| { | { | ||||||
|  |     // Iterate through the existing DTCs in the storage | ||||||
|     for (int i = 0; i < MAX_DTC_STORAGE; i++) |     for (int i = 0; i < MAX_DTC_STORAGE; i++) | ||||||
|     { |     { | ||||||
|  |         // Check if the DTC with the specified number exists | ||||||
|         if (DTCStorage[i].Number == DTC_no) |         if (DTCStorage[i].Number == DTC_no) | ||||||
|         { |         { | ||||||
|  |             // If the DTC is active and was not active before, update its status | ||||||
|             if (active && DTCStorage[i].active != DTC_ACTIVE) |             if (active && DTCStorage[i].active != DTC_ACTIVE) | ||||||
|             { |             { | ||||||
|                 Debug_pushMessage("DTC gone active: %d, DebugVal: %d\n", DTC_no, DebugValue); |                 Debug_pushMessage("DTC gone active: %d, DebugVal: %d\n", DTC_no, DebugValue); | ||||||
|                 DTCStorage[i].timestamp = millis(); |                 DTCStorage[i].timestamp = millis(); | ||||||
|                 DTCStorage[i].active = DTC_ACTIVE; |                 DTCStorage[i].active = DTC_ACTIVE; | ||||||
|                 DTCStorage[i].severity = DTC_severity; |  | ||||||
|                 DTCStorage[i].debugVal = DebugValue; |                 DTCStorage[i].debugVal = DebugValue; | ||||||
|             } |             } | ||||||
|  |             // If the DTC is not active anymore, update its status to previous | ||||||
|             if (!active && DTCStorage[i].active == DTC_ACTIVE) |             if (!active && DTCStorage[i].active == DTC_ACTIVE) | ||||||
|             { |             { | ||||||
|                 Debug_pushMessage("DTC gone previous: %d\n", DTC_no); |                 Debug_pushMessage("DTC gone previous: %d\n", DTC_no); | ||||||
|                 DTCStorage[i].active = DTC_PREVIOUS; |                 DTCStorage[i].active = DTC_PREVIOUS; | ||||||
|             } |             } | ||||||
|             return; |             return; // DTC found and processed, exit the function | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // DTC was not found with upper iteration, but is active |     // DTC was not found in the existing storage, but it is active, | ||||||
|     // so we need to look for free space to store DTC |     // so look for free space to store the new DTC | ||||||
|     if (active == true) |     if (active == true) | ||||||
|     { |     { | ||||||
|         for (int i = 0; i < MAX_DTC_STORAGE; i++) |         for (int i = 0; i < MAX_DTC_STORAGE; i++) | ||||||
|         { |         { | ||||||
|  |             // Check for an empty slot in the storage | ||||||
|             if (DTCStorage[i].Number == DTC_LAST_DTC) |             if (DTCStorage[i].Number == DTC_LAST_DTC) | ||||||
|             { |             { | ||||||
|                 Debug_pushMessage("new DTC registered: %d, DebugVal: %d\n", DTC_no, DebugValue); |                 Debug_pushMessage("new DTC registered: %d, DebugVal: %d\n", DTC_no, DebugValue); | ||||||
| @@ -39,37 +63,59 @@ void MaintainDTC(DTCNums_t DTC_no, DTCSeverity_t DTC_severity, boolean active, u | |||||||
|                 DTCStorage[i].timestamp = millis(); |                 DTCStorage[i].timestamp = millis(); | ||||||
|                 DTCStorage[i].active = DTC_ACTIVE; |                 DTCStorage[i].active = DTC_ACTIVE; | ||||||
|                 DTCStorage[i].debugVal = DebugValue; |                 DTCStorage[i].debugVal = DebugValue; | ||||||
|                 DTCStorage[i].severity = DTC_severity; |                 return; // New DTC registered, exit the function | ||||||
|                 return; |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| void ClearDTC(DTCNums_t DTC_no) | /** | ||||||
|  |  * @brief Clears a specific Diagnostic Trouble Code (DTC) entry. | ||||||
|  |  * | ||||||
|  |  * This function clears the information related to a specific DTC entry, | ||||||
|  |  * setting its status to inactive and timestamp to zero. | ||||||
|  |  * | ||||||
|  |  * @param DTC_no The Diagnostic Trouble Code number to be cleared. | ||||||
|  |  */ | ||||||
|  | void ClearDTC(DTCNum_t DTC_no) | ||||||
| { | { | ||||||
|     for (int i = 0; i < MAX_DTC_STORAGE; i++) |     for (int i = 0; i < MAX_DTC_STORAGE; i++) | ||||||
|     { |     { | ||||||
|         if (DTCStorage[i].Number == DTC_no) |         if (DTCStorage[i].Number == DTC_no) | ||||||
|         { |         { | ||||||
|             DTCStorage[i].Number = DTC_LAST_DTC; |             DTCStorage[i].Number = DTC_LAST_DTC; | ||||||
|             DTCStorage[i].active = DTC_NONE; |             DTCStorage[i].active = DTC_INACTIVE; | ||||||
|             DTCStorage[i].timestamp = 0; |             DTCStorage[i].timestamp = 0; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Clears all Diagnostic Trouble Code (DTC) entries. | ||||||
|  |  * | ||||||
|  |  * This function clears all DTC entries, setting their status to inactive and | ||||||
|  |  * timestamps to zero. | ||||||
|  |  */ | ||||||
| void ClearAllDTC() | void ClearAllDTC() | ||||||
| { | { | ||||||
|     for (int i = 0; i < MAX_DTC_STORAGE; i++) |     for (int i = 0; i < MAX_DTC_STORAGE; i++) | ||||||
|     { |     { | ||||||
|         DTCStorage[i].Number = DTC_LAST_DTC; |         DTCStorage[i].Number = DTC_LAST_DTC; | ||||||
|         DTCStorage[i].active = DTC_NONE; |         DTCStorage[i].active = DTC_INACTIVE; | ||||||
|         DTCStorage[i].timestamp = 0; |         DTCStorage[i].timestamp = 0; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| DTCNums_t getlastDTC(boolean only_active) | /** | ||||||
|  |  * @brief Gets the last recorded Diagnostic Trouble Code (DTC) number. | ||||||
|  |  * | ||||||
|  |  * This function retrieves the DTC number of the last recorded DTC based on the | ||||||
|  |  * timestamp. Optionally, it can filter only active DTCs. | ||||||
|  |  * | ||||||
|  |  * @param only_active If true, considers only active DTCs; otherwise, considers all. | ||||||
|  |  * @return The DTC number of the last recorded DTC or DTC_LAST_DTC if none found. | ||||||
|  |  */ | ||||||
|  | DTCNum_t getlastDTC(boolean only_active) | ||||||
| { | { | ||||||
|     int8_t pointer = -1; |     int8_t pointer = -1; | ||||||
|     uint32_t lasttimestamp = 0; |     uint32_t lasttimestamp = 0; | ||||||
| @@ -89,34 +135,47 @@ DTCNums_t getlastDTC(boolean only_active) | |||||||
|     return pointer >= 0 ? DTCStorage[pointer].Number : DTC_LAST_DTC; |     return pointer >= 0 ? DTCStorage[pointer].Number : DTC_LAST_DTC; | ||||||
| } | } | ||||||
|  |  | ||||||
| DTCNums_t getlastDTC_Severity(boolean only_active, DTCSeverity_t severity) | /** | ||||||
|  |  * @brief Gets the severity level for a specific Diagnostic Trouble Code (DTC). | ||||||
|  |  * | ||||||
|  |  * This function looks up the severity level associated with the provided DTC code | ||||||
|  |  * from the predefined list of DTC definitions. | ||||||
|  |  * | ||||||
|  |  * @param targetCode The DTC code for which to retrieve the severity. | ||||||
|  |  * @return The severity level of the specified DTC or DTC_NONE if not found. | ||||||
|  |  */ | ||||||
|  | DTCSeverity_t getSeverityForDTC(DTCNum_t targetCode) | ||||||
| { | { | ||||||
|     int8_t pointer = -1; |     for (int i = 0; i < DTC_LAST_DTC; i++) | ||||||
|     uint32_t lasttimestamp = 0; |  | ||||||
|  |  | ||||||
|     for (int i = 0; i < MAX_DTC_STORAGE; i++) |  | ||||||
|     { |     { | ||||||
|         if (DTCStorage[i].Number > 0 && DTCStorage[i].timestamp > lasttimestamp) |         if (dtc_definitions[i].code == targetCode) | ||||||
|         { |         { | ||||||
|             if ((only_active == false || DTCStorage[i].active == DTC_ACTIVE) && DTCStorage[i].severity == severity) |             return dtc_definitions[i].severity; | ||||||
|             { |  | ||||||
|                 pointer = i; |  | ||||||
|                 lasttimestamp = DTCStorage[i].timestamp; |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |     return DTC_NONE; | ||||||
|     return pointer >= 0 ? DTCStorage[pointer].Number : DTC_LAST_DTC; |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Processes Diagnostic Trouble Codes (DTCs) and updates system status accordingly. | ||||||
|  |  * | ||||||
|  |  * This function checks for the presence of active DTCs and adjusts the system status | ||||||
|  |  * based on the severity of the most critical DTC. If a critical DTC is detected, | ||||||
|  |  * the system status is set to sysStat_Error, potentially triggering a system shutdown. | ||||||
|  |  * | ||||||
|  |  * @note The function also preserves the original system status when transitioning to an error state | ||||||
|  |  *       and restores it when all DTCs are cleared. | ||||||
|  |  */ | ||||||
| void DTC_Process() | void DTC_Process() | ||||||
| { | { | ||||||
|     static tSystem_Status preserverSysStatusError; |     static tSystem_Status preserverSysStatusError; | ||||||
|  |     DTCNum_t lastDTC = getlastDTC(true); | ||||||
|  |  | ||||||
|     if (getlastDTC(false) < DTC_LAST_DTC) |     if (lastDTC < DTC_LAST_DTC) | ||||||
|     { |     { | ||||||
|         globals.hasDTC = true; |         globals.hasDTC = true; | ||||||
|         if (getlastDTC_Severity(true, DTC_CRITICAL) < DTC_LAST_DTC && globals.systemStatus != sysStat_Shutdown) |  | ||||||
|  |         if (getSeverityForDTC(lastDTC) == DTC_CRITICAL && globals.systemStatus != sysStat_Shutdown) | ||||||
|         { |         { | ||||||
|             if (globals.systemStatus != sysStat_Error) |             if (globals.systemStatus != sysStat_Error) | ||||||
|             { |             { | ||||||
| @@ -124,16 +183,14 @@ void DTC_Process() | |||||||
|             } |             } | ||||||
|             globals.systemStatus = sysStat_Error; |             globals.systemStatus = sysStat_Error; | ||||||
|         } |         } | ||||||
|         else |  | ||||||
|         { |  | ||||||
|             if (globals.systemStatus == sysStat_Error) |  | ||||||
|             { |  | ||||||
|                 globals.systemStatus = preserverSysStatusError; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|     else |     else | ||||||
|     { |     { | ||||||
|         globals.hasDTC = false; |         globals.hasDTC = false; | ||||||
|  |  | ||||||
|  |         if (globals.systemStatus == sysStat_Error) | ||||||
|  |         { | ||||||
|  |             globals.systemStatus = preserverSysStatusError; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
							
								
								
									
										18
									
								
								Software/src/dtc_defs.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								Software/src/dtc_defs.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | |||||||
|  | # No. |                 DTC-Constant |      Severity |                 Title | Description      | ||||||
|  | #-----|------------------------------|---------------|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------- | ||||||
|  |     1;              DTC_BAT_CRITICAL;   DTC_CRITICAL;              Akku leer;  Akku ist komplett leer. Den Akku aufladen! | ||||||
|  |     2;                   DTC_BAT_LOW;       DTC_WARN;           Akku niedrig;  Akku ist unter der Warnschwelle. Den Akku demnächst aufladen | ||||||
|  |     3;           DTC_NO_EEPROM_FOUND;   DTC_CRITICAL;    kein EEPROM erkannt;  Es wurde kein EEPROM gefunden. Dies lässt einen Hardware-Defekt vermuten. | ||||||
|  |     4;            DTC_EEPROM_CFG_BAD;   DTC_CRITICAL;  EEPROM CFG Checksumme;  Die Checksumme der Config-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück | ||||||
|  |     5;            DTC_EEPROM_PDS_BAD;   DTC_CRITICAL;  EEPROM PDS Checksumme;  Die Checksumme der Betriebsdaten-Partition des EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück | ||||||
|  |     6;      DTC_EEPROM_PDSADRESS_BAD;   DTC_CRITICAL;     EEPROM PDS Adresse;  Die Adresse der Betriebsdaten-Partition im EEPROM ist ungültig. Setzen sie den EEPROM-Bereich 'PDS' im Menu 'Wartung' zurück | ||||||
|  |     7;        DTC_EEPROM_VERSION_BAD;   DTC_CRITICAL;  EEPROM Version falsch;  Die Layout-Version des EEPROM stimmt nicht mit der Firmware-Version überein. Setzen sie den EEPROM-Bereich 'CFG' im Menu 'Wartung' zurück | ||||||
|  |     8;             DTC_FLASHFS_ERROR;   DTC_CRITICAL;   Flashspeicher Fehler;  Der Flashspeicher konnte nicht initialisiert werden. Aktualisieren sie Flash & Firmware | ||||||
|  |     9;     DTC_FLASHFS_VERSION_ERROR;   DTC_CRITICAL;    Flashversion falsch;  Die Version des Flashspeicher stimmt nicht mit der Firmware-Version überein. Aktualisieren sie den Flash mit der passenden Update-Datei | ||||||
|  |    10;          DTC_NO_BATMNON_FOUND;   DTC_CRITICAL;  Keine Akkuüberwachung;  Es wurde keine Akkuüberwachung über I2C gefunden, Prüfen sie die Hardware! | ||||||
|  |    11;             DTC_NO_LORA_FOUND;   DTC_CRITICAL; LoRa-Transceiver Error;  Es konnte keine Verbindung zum LoRa-Transceiver hergestellt werden. Prüfen Sie die Hardware auf Defekte | ||||||
|  |    12;         DTC_EEPROM_CFG_SANITY;       DTC_WARN;     Config-Validierung;  Ein oder mehrer Einstellungswerte sind ausserhalb plausibler Werte. Prüfen Sie Ihre Einstellungen | ||||||
|  |    13;     DTC_EEPROM_MIGRATE_FAILED;   DTC_CRITICAL;       EEPROM-Migration;  Es wurde ein altes EEPROm Image erkannt, konnte aber nicht migriert werden. EEPROM manuell zurück setzen und neue Einstellunge speichern. | ||||||
|  |    14;             DTC_FAKE_DTC_INFO;       DTC_INFO;         Dummy-DTC Info;  Ein Dummy-DTC der Schwere "Info" für Debugging-Zwecke | ||||||
|  |    15;             DTC_FAKE_DTC_WARN;       DTC_WARN;      Dummy-DTC Warnung;  Ein Dummy-DTC der Schwere "Warnung" für Debugging-Zwecke | ||||||
|  |    16;             DTC_FAKE_DTC_CRIT;   DTC_CRITICAL;     Dummy-DTC Kritisch;  Ein Dummy-DTC der Schwere "Kritisch" für Debugging-Zwecke | ||||||
| @@ -1,72 +1,103 @@ | |||||||
| #include "eeprom.h" | /** | ||||||
|  |  * @file config.cpp | ||||||
|  |  * @brief Implementation of EEPROM and configuration-related functions. | ||||||
|  |  * | ||||||
|  |  * This file contains functions for managing EEPROM storage and handling configuration data. | ||||||
|  |  * It includes the definitions of configuration structures, EEPROM access, and utility functions. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | #include "eeprom.h" | ||||||
|  | #include "debugger.h" | ||||||
|  | #include "globals.h" | ||||||
|  |  | ||||||
|  | // Instance of I2C_eeprom for EEPROM access | ||||||
| I2C_eeprom ee(I2C_EE_ADDRESS, EEPROM_SIZE_BYTES); | I2C_eeprom ee(I2C_EE_ADDRESS, EEPROM_SIZE_BYTES); | ||||||
|  |  | ||||||
|  | // Configuration and persistence data structures | ||||||
| configData_t ConfigData; | configData_t ConfigData; | ||||||
| persistenceData_t PersistenceData; | persistenceData_t PersistenceData; | ||||||
| bool eeAvailable = false; |  | ||||||
|  |  | ||||||
| bool checkEEPROMavailable(); | // EEPROM version identifier | ||||||
| bool ValidateEEPROM_Version(); | const uint16_t eeVersion = EEPROM_STRUCTURE_REVISION;  | ||||||
| bool MigrateEEPROM(uint8_t fromVersion); |  | ||||||
|  |  | ||||||
|  | // Flag indicating whether EEPROM is available | ||||||
|  | boolean eeAvailable = false; | ||||||
|  |  | ||||||
|  | // Offsets within EEPROM for ConfigData and PersistenceData | ||||||
|  | const uint16_t startofConfigData = 16; | ||||||
|  | const uint16_t startofPersistence = 16 + sizeof(ConfigData) + (sizeof(ConfigData) % 16); | ||||||
|  |  | ||||||
|  | // Function prototype to check EEPROM availability | ||||||
|  | boolean checkEEPROMavailable(); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Initializes EEPROM and checks its availability. | ||||||
|  |  * | ||||||
|  |  * This function initializes the EEPROM using the I2C_eeprom instance and checks if it's available. | ||||||
|  |  */ | ||||||
| void InitEEPROM() | void InitEEPROM() | ||||||
| { | { | ||||||
|  |   ConfigData = ConfigData_defaults; | ||||||
|  |   PersistenceData = {0}; | ||||||
|   ee.begin(); |   ee.begin(); | ||||||
|   eeAvailable = checkEEPROMavailable(); |   checkEEPROMavailable(); | ||||||
|   eeAvailable = ValidateEEPROM_Version(); |  | ||||||
| 	Serial.printf("Initialized EEPROM at Address 0x%02X\n", I2C_EE_ADDRESS); |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Processes EEPROM actions based on the request from the global state. | ||||||
|  |  * | ||||||
|  |  * This function processes EEPROM actions based on the request from the global state. | ||||||
|  |  * It performs actions such as saving, loading, and formatting EEPROM data for both configuration and persistence. | ||||||
|  |  */ | ||||||
| void EEPROM_Process() | void EEPROM_Process() | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |  | ||||||
|     return; |  | ||||||
|  |  | ||||||
|   switch (globals.requestEEAction) |   switch (globals.requestEEAction) | ||||||
|   { |   { | ||||||
|   case EE_CFG_SAVE: |   case EE_CFG_SAVE: | ||||||
|     StoreConfig_EEPROM(); |     StoreConfig_EEPROM(); | ||||||
|     globals.requestEEAction = EE_IDLE; |     globals.requestEEAction = EE_IDLE; | ||||||
|     Serial.println("Stored EEPROM CFG"); |     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; | ||||||
|     Serial.println("Loaded EEPROM CFG"); |     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; | ||||||
|     globals.systemStatus = sysStat_Shutdown; |     GetConfig_EEPROM(); | ||||||
|     Serial.println("Formated EEPROM CFG"); |     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; | ||||||
|     Serial.println("Stored EEPROM PDS"); |     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; | ||||||
|     Serial.println("Loaded EEPROM PDS"); |     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; | ||||||
|     Serial.println("Formated EEPROM PDS"); |     GetPersistence_EEPROM(); | ||||||
|  |     Debug_pushMessage("Formatted EEPROM PDS\n"); | ||||||
|     break; |     break; | ||||||
|   case EE_FORMAT_ALL: |   case EE_FORMAT_ALL: | ||||||
|     FormatConfig_EEPROM(); |     FormatConfig_EEPROM(); | ||||||
|     FormatPersistence_EEPROM(); |     FormatPersistence_EEPROM(); | ||||||
|  |     GetConfig_EEPROM(); | ||||||
|  |     GetPersistence_EEPROM(); | ||||||
|     globals.requestEEAction = EE_IDLE; |     globals.requestEEAction = EE_IDLE; | ||||||
|     Serial.println("Formated EEPROM ALL"); |     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; | ||||||
|     Serial.println("Stored EEPROM ALL"); |     Debug_pushMessage("Stored EEPROM ALL\n"); | ||||||
|     break; |     break; | ||||||
|   case EE_IDLE: |   case EE_IDLE: | ||||||
|   default: |   default: | ||||||
| @@ -74,20 +105,39 @@ void EEPROM_Process() | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Stores the configuration data in EEPROM. | ||||||
|  |  * | ||||||
|  |  * This function calculates the checksum for the configuration data, updates it, and stores it in EEPROM. | ||||||
|  |  * It also performs a sanity check on the configuration and raises a diagnostic trouble code (DTC) if needed. | ||||||
|  |  */ | ||||||
| void StoreConfig_EEPROM() | void StoreConfig_EEPROM() | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |  | ||||||
|     return; |  | ||||||
|  |  | ||||||
|   ConfigData.checksum = 0; |   ConfigData.checksum = 0; | ||||||
|   ConfigData.checksum = Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)); |   ConfigData.checksum = Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)); | ||||||
|  |  | ||||||
|  |   if (!checkEEPROMavailable()) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|   ee.updateBlock(startofConfigData, (uint8_t *)&ConfigData, sizeof(ConfigData)); |   ee.updateBlock(startofConfigData, (uint8_t *)&ConfigData, sizeof(ConfigData)); | ||||||
|  |  | ||||||
|  |   uint32_t ConfigSanityCheckResult = ConfigSanityCheck(false); | ||||||
|  |  | ||||||
|  |   if (ConfigSanityCheckResult > 0) | ||||||
|  |   { | ||||||
|  |     MaintainDTC(DTC_EEPROM_CFG_SANITY, true, ConfigSanityCheckResult); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Retrieves the configuration data from EEPROM. | ||||||
|  |  * | ||||||
|  |  * This function reads the configuration data from EEPROM, performs a checksum validation, | ||||||
|  |  * and conducts a sanity check on the configuration. It raises a diagnostic trouble code (DTC) if needed. | ||||||
|  |  */ | ||||||
| void GetConfig_EEPROM() | void GetConfig_EEPROM() | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |   if (!checkEEPROMavailable()) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   ee.readBlock(startofConfigData, (uint8_t *)&ConfigData, sizeof(ConfigData)); |   ee.readBlock(startofConfigData, (uint8_t *)&ConfigData, sizeof(ConfigData)); | ||||||
| @@ -95,26 +145,24 @@ void GetConfig_EEPROM() | |||||||
|   uint32_t checksum = ConfigData.checksum; |   uint32_t checksum = ConfigData.checksum; | ||||||
|   ConfigData.checksum = 0; |   ConfigData.checksum = 0; | ||||||
|  |  | ||||||
|   if (Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)) != checksum) |   MaintainDTC(DTC_EEPROM_CFG_BAD, (Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)) != checksum)); | ||||||
|   { |  | ||||||
|     MaintainDTC(DTC_EEPROM_CFG_BAD, DTC_CRITICAL, true); |  | ||||||
|   } |  | ||||||
|   ConfigData.checksum = checksum; |   ConfigData.checksum = checksum; | ||||||
|  |  | ||||||
|   uint32_t ConfigSanityCheckResult = ConfigSanityCheck(false); |   uint32_t ConfigSanityCheckResult = ConfigSanityCheck(false); | ||||||
|  |  | ||||||
|   if (ConfigSanityCheckResult > 0) |   MaintainDTC(DTC_EEPROM_CFG_SANITY, (ConfigSanityCheckResult > 0), ConfigSanityCheckResult); | ||||||
|   { |  | ||||||
|     MaintainDTC(DTC_EEPROM_CFG_SANITY, DTC_WARN, true, ConfigSanityCheckResult); |  | ||||||
|     globals.requestEEAction = EE_CFG_SAVE; |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Stores the persistence data in EEPROM. | ||||||
|  |  * | ||||||
|  |  * This function increments the write cycle counter, performs a checksum calculation on the persistence data, | ||||||
|  |  * and stores it in EEPROM. It also handles EEPROM page movement when needed. | ||||||
|  |  */ | ||||||
| void StorePersistence_EEPROM() | void StorePersistence_EEPROM() | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |  | ||||||
|     return; |  | ||||||
|  |  | ||||||
|   if (PersistenceData.writeCycleCounter >= 0xFFF0) |   if (PersistenceData.writeCycleCounter >= 0xFFF0) | ||||||
|     MovePersistencePage_EEPROM(false); |     MovePersistencePage_EEPROM(false); | ||||||
|   else |   else | ||||||
| @@ -123,12 +171,22 @@ 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()) | ||||||
|  |     return; | ||||||
|  |  | ||||||
|   ee.updateBlock(globals.eePersistanceAdress, (uint8_t *)&PersistenceData, sizeof(PersistenceData)); |   ee.updateBlock(globals.eePersistanceAdress, (uint8_t *)&PersistenceData, sizeof(PersistenceData)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Retrieves the persistence data from EEPROM. | ||||||
|  |  * | ||||||
|  |  * This function reads the EEPROM to get the start address of the persistence data. | ||||||
|  |  * If the start address is out of range, it resets and stores defaults. Otherwise, | ||||||
|  |  * it reads from EEPROM and checks if the data is correct. | ||||||
|  |  */ | ||||||
| void GetPersistence_EEPROM() | void GetPersistence_EEPROM() | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |   if (!checkEEPROMavailable()) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   ee.readBlock(0, (uint8_t *)&globals.eePersistanceAdress, sizeof(globals.eePersistanceAdress)); |   ee.readBlock(0, (uint8_t *)&globals.eePersistanceAdress, sizeof(globals.eePersistanceAdress)); | ||||||
| @@ -138,7 +196,7 @@ void GetPersistence_EEPROM() | |||||||
|   { |   { | ||||||
|     MovePersistencePage_EEPROM(true); |     MovePersistencePage_EEPROM(true); | ||||||
|     FormatPersistence_EEPROM(); |     FormatPersistence_EEPROM(); | ||||||
|     MaintainDTC(DTC_EEPROM_PDSADRESS_BAD, DTC_CRITICAL, true); |     MaintainDTC(DTC_EEPROM_PDSADRESS_BAD, true); | ||||||
|   } |   } | ||||||
|   else |   else | ||||||
|   { |   { | ||||||
| @@ -147,45 +205,53 @@ void GetPersistence_EEPROM() | |||||||
|     uint32_t checksum = PersistenceData.checksum; |     uint32_t checksum = PersistenceData.checksum; | ||||||
|     PersistenceData.checksum = 0; |     PersistenceData.checksum = 0; | ||||||
|  |  | ||||||
|     if (Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)) != checksum) |     MaintainDTC(DTC_EEPROM_PDS_BAD, (Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)) != checksum)); | ||||||
|     { |  | ||||||
|       MaintainDTC(DTC_EEPROM_PDS_BAD, DTC_CRITICAL, true); |  | ||||||
|     } |  | ||||||
|     PersistenceData.checksum = checksum; |     PersistenceData.checksum = checksum; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Formats the configuration partition in EEPROM. | ||||||
|  |  * | ||||||
|  |  * This function resets the configuration data to defaults and stores it in EEPROM. | ||||||
|  |  */ | ||||||
| void FormatConfig_EEPROM() | void FormatConfig_EEPROM() | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |   Debug_pushMessage("Formatting Config-Partition\n"); | ||||||
|     return; |  | ||||||
|  |  | ||||||
|   Serial.println("Formatting Config-Partition"); |  | ||||||
|   ConfigData = ConfigData_defaults; |   ConfigData = ConfigData_defaults; | ||||||
|  |   ConfigData.EEPROM_Version = eeVersion; | ||||||
|   StoreConfig_EEPROM(); |   StoreConfig_EEPROM(); | ||||||
|   GetConfig_EEPROM(); |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Formats the persistence partition in EEPROM. | ||||||
|  |  * | ||||||
|  |  * This function resets the persistence data to defaults and stores it in EEPROM. | ||||||
|  |  */ | ||||||
| void FormatPersistence_EEPROM() | void FormatPersistence_EEPROM() | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |   Debug_pushMessage("Formatting Persistance-Partition\n"); | ||||||
|     return; |  | ||||||
|  |  | ||||||
|   Serial.println("Formatting Persistance-Partition"); |  | ||||||
|   PersistenceData = {0}; |   PersistenceData = {0}; | ||||||
|  |   // memset(&PersistenceData, 0, sizeof(PersistenceData)); | ||||||
|   StorePersistence_EEPROM(); |   StorePersistence_EEPROM(); | ||||||
|   GetPersistence_EEPROM(); |  | ||||||
| } | } | ||||||
|  | /** | ||||||
|  |  * @brief Moves the persistence page in EEPROM. | ||||||
|  |  * | ||||||
|  |  * This function adjusts the persistence page address and resets the write cycle counter. | ||||||
|  |  * | ||||||
|  |  * @param reset If true, the function resets the persistence page address to the start of the partition. | ||||||
|  |  */ | ||||||
| void MovePersistencePage_EEPROM(boolean reset) | void MovePersistencePage_EEPROM(boolean reset) | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |   if (!checkEEPROMavailable()) | ||||||
|     return; |     return; | ||||||
|  |  | ||||||
|   globals.eePersistanceAdress = +sizeof(PersistenceData); |   globals.eePersistanceAdress += sizeof(PersistenceData); | ||||||
|   PersistenceData.writeCycleCounter = 0; |   PersistenceData.writeCycleCounter = 0; | ||||||
|  |  | ||||||
|   // check if we reached the End of the EEPROM and Startover at the beginning |   // Check if we reached the end of the EEPROM and start over at the beginning | ||||||
|   if ((globals.eePersistanceAdress + sizeof(PersistenceData)) > ee.getDeviceSize() || reset) |   if ((globals.eePersistanceAdress + sizeof(PersistenceData)) > ee.getDeviceSize() || reset) | ||||||
|   { |   { | ||||||
|     globals.eePersistanceAdress = startofPersistence; |     globals.eePersistanceAdress = startofPersistence; | ||||||
| @@ -194,165 +260,202 @@ void MovePersistencePage_EEPROM(boolean reset) | |||||||
|   ee.updateBlock(0, (uint8_t *)&globals.eePersistanceAdress, sizeof(globals.eePersistanceAdress)); |   ee.updateBlock(0, (uint8_t *)&globals.eePersistanceAdress, sizeof(globals.eePersistanceAdress)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Calculate CRC-32 checksum for a block of data. | ||||||
|  |  * | ||||||
|  |  * 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) | ||||||
| { | { | ||||||
|   if (data == NULL) |   if (data == NULL) | ||||||
|     return 0; |     return 0; | ||||||
|   uint32_t crc, mask; |  | ||||||
|   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++) | ||||||
|     { |     { | ||||||
|       mask = -(crc & 1); |       mask = -(crc & 1); | ||||||
|       crc = (crc >> 1) ^ (0xEDB88320 & mask); |       crc = (crc >> 1) ^ (0xEDB88320 & mask); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return ~crc; |   return ~crc; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Dump a portion of EEPROM contents for debugging. | ||||||
|  |  * | ||||||
|  |  * This function prints the contents of a specified portion of EEPROM in a formatted way. | ||||||
|  |  * | ||||||
|  |  * @param memoryAddress Starting address in EEPROM. | ||||||
|  |  * @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 (eeAvailable == false) |   if (!checkEEPROMavailable()) | ||||||
|     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"); | ||||||
|   Serial.print(PSTR("\nAddress ")); |  | ||||||
|   for (int x = 0; x < BLOCK_TO_LENGTH; x++) |  | ||||||
|     Serial.printf("%3d", x); |  | ||||||
|  |  | ||||||
|  |   // Print column headers | ||||||
|  |   Debug_pushMessage(PSTR("\nAddress ")); | ||||||
|  |   for (int x = 0; x < BLOCK_TO_LENGTH; 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; |     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; | ||||||
|       Serial.printf("  %s", ascii_buf); |       Debug_pushMessage("  %s", ascii_buf); | ||||||
|       Serial.printf("\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); | ||||||
|     Serial.printf(" %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++; | ||||||
|   } |   } | ||||||
|   Serial.println(); |  | ||||||
|  |   // Print a new line at the end of the dump | ||||||
|  |   Debug_pushMessage("\n"); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool checkEEPROMavailable() | /** | ||||||
|  |  * @brief Check if EEPROM is available and connected. | ||||||
|  |  * | ||||||
|  |  * This function checks if the EEPROM is available and connected. If not, it triggers | ||||||
|  |  * a diagnostic trouble code (DTC) indicating the absence of EEPROM. | ||||||
|  |  * | ||||||
|  |  * @return true if EEPROM is available, false otherwise. | ||||||
|  |  */ | ||||||
|  | boolean checkEEPROMavailable() | ||||||
| { | { | ||||||
|  |   // Check if EEPROM is connected | ||||||
|   if (!ee.isConnected()) |   if (!ee.isConnected()) | ||||||
|   { |   { | ||||||
|     MaintainDTC(DTC_NO_EEPROM_FOUND, DTC_CRITICAL, true); |     // Trigger DTC for no EEPROM found | ||||||
|  |     MaintainDTC(DTC_NO_EEPROM_FOUND, true); | ||||||
|     return false; |     return false; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Clear DTC for no EEPROM found since it's available now | ||||||
|  |   MaintainDTC(DTC_NO_EEPROM_FOUND, false); | ||||||
|  |  | ||||||
|  |   // EEPROM is available | ||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Perform sanity check on configuration settings. | ||||||
|  |  * | ||||||
|  |  * This function checks the validity of various configuration settings and returns a bitmask | ||||||
|  |  * indicating which settings need to be reset. If autocorrect is enabled, it resets the settings | ||||||
|  |  * 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) | ||||||
| { | { | ||||||
|   uint32_t setting_reset_bits = 0; |   uint32_t setting_reset_bits = 0; | ||||||
|  |  | ||||||
|   if ((ConfigData.batteryType != BATTERY_LIPO_2S) || (ConfigData.batteryType != BATTERY_LIPO_3S)) |   if (!validateWiFiString(ConfigData.wifi_ap_ssid, sizeof(ConfigData.wifi_ap_ssid))) | ||||||
|   { |   { | ||||||
|     setting_reset_bits = setting_reset_bits | (1 << 0); |     SET_BIT(setting_reset_bits, 1); | ||||||
|     if (autocorrect) |     if (autocorrect) | ||||||
|       ConfigData.batteryType = ConfigData_defaults.batteryType; |       strncpy(ConfigData.wifi_ap_ssid, ConfigData_defaults.wifi_ap_ssid, sizeof(ConfigData.wifi_ap_ssid)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (!validateWiFiString(ConfigData.wifi_ap_password, sizeof(ConfigData.wifi_ap_password))) | ||||||
|  |   { | ||||||
|  |     SET_BIT(setting_reset_bits, 2); | ||||||
|  |     if (autocorrect) | ||||||
|  |       strncpy(ConfigData.wifi_ap_password, ConfigData_defaults.wifi_ap_password, sizeof(ConfigData.wifi_ap_password)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!validateWiFiString(ConfigData.wifi_client_ssid, sizeof(ConfigData.wifi_client_ssid))) | ||||||
|  |   { | ||||||
|  |     SET_BIT(setting_reset_bits, 3); | ||||||
|  |     if (autocorrect) | ||||||
|  |       strncpy(ConfigData.wifi_client_ssid, ConfigData_defaults.wifi_client_ssid, sizeof(ConfigData.wifi_client_ssid)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (!validateWiFiString(ConfigData.wifi_client_password, sizeof(ConfigData.wifi_client_password))) | ||||||
|  |   { | ||||||
|  |     SET_BIT(setting_reset_bits, 4); | ||||||
|  |     if (autocorrect) | ||||||
|  |       strncpy(ConfigData.wifi_client_password, ConfigData_defaults.wifi_client_password, sizeof(ConfigData.wifi_client_password)); | ||||||
|  |   } | ||||||
|  |   // Return the bitmask indicating which settings need to be reset | ||||||
|   return setting_reset_bits; |   return setting_reset_bits; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool ValidateEEPROM_Version() | /** | ||||||
|  |  * @brief Validates whether a given string contains only characters allowed in WiFi SSIDs and passwords. | ||||||
|  |  * | ||||||
|  |  * This function checks each character in the provided string to ensure | ||||||
|  |  * 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. | ||||||
|  |  * @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) | ||||||
| { | { | ||||||
|   if (eeAvailable == false) |   if (string == NULL) | ||||||
|     return false; |     return false; | ||||||
|  |  | ||||||
|   uint8_t EEPROMVersionOnChip = ee.readByte(startofConfigData); |   for (size_t i = 0; i < size; i++) | ||||||
|  |  | ||||||
|   if (EEPROMVersionOnChip < ConfigData_defaults.EEPROM_Version) |  | ||||||
|   { |   { | ||||||
|     Serial.printf("EEPROM Image Version is %d, but %d expected - trying to migrate\n", EEPROMVersionOnChip, ConfigData_defaults.EEPROM_Version); |     char c = string[i]; | ||||||
|     if (!MigrateEEPROM(EEPROMVersionOnChip)) |     if (c == '\0') | ||||||
|     { |     { | ||||||
|       Serial.print("Error\n"); |       // Reached the end of the string, all characters were valid WiFi characters. | ||||||
|       MaintainDTC(DTC_EEPROM_MIGRATE_FAILED, DTC_CRITICAL, true, EEPROMVersionOnChip); |       return true; | ||||||
|  |     } | ||||||
|  |     if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || | ||||||
|  |           (c >= '0' && c <= '9') || c == '!' || c == '"' || c == '#' || | ||||||
|  |           c == '$' || c == '%' || c == '&' || c == '\'' || c == '(' || | ||||||
|  |           c == ')' || c == '*' || c == '+' || c == ',' || c == '-' || | ||||||
|  |           c == '.' || 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; | ||||||
|     } |     } | ||||||
|     else |  | ||||||
|     { |  | ||||||
|       Serial.print("Success\n"); |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
|   return true; |   // If the loop completes without finding a null terminator, the string is invalid. | ||||||
|  |   return false; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool MigrateEEPROM(uint8_t fromVersion) |  | ||||||
| { |  | ||||||
|   uint16_t persistanceMarker_onChip; |  | ||||||
|  |  | ||||||
|   switch (fromVersion) |  | ||||||
|   { |  | ||||||
|  |  | ||||||
|     // Version 1 EEPROM Layout:                             startAdress     size (byte) |  | ||||||
|     //  const uint16_t startofConfigData  =                 16               16 |  | ||||||
|     //  const uint16_t startofPersistence =                 32               24 |  | ||||||
|     // |  | ||||||
|     //  typedef struct |  | ||||||
|     //  { |  | ||||||
|     //    uint8_t EEPROM_Version = 1;                       16                1 |  | ||||||
|     //    batteryType_t batteryType = BATTERY_UNDEFINED;    17                4 |  | ||||||
|     //    bool active_faction_on_reboot = false;            21                1 |  | ||||||
|     //    uint32_t checksum = 0;                            22                4 |  | ||||||
|     //  } configData_t; |  | ||||||
|     // |  | ||||||
|     //  typedef struct                                      offset |  | ||||||
|     //  { |  | ||||||
|     //    uint32_t writeCycleCounter = 0;                   0                 4 |  | ||||||
|     //    uint32_t faction_1_timer = 0;                     4                 4 |  | ||||||
|     //    uint32_t faction_2_timer = 0;                     8                 4 |  | ||||||
|     //    uint32_t faction_3_timer = 0;                     12                4 |  | ||||||
|     //    factions_t activeFaction = NONE;                  16                4 |  | ||||||
|     //    uint32_t checksum = 0;                            20                4 |  | ||||||
|     //  } persistenceData_t; |  | ||||||
|  |  | ||||||
|   case 1: |  | ||||||
|     // Migrate Persistance-Data |  | ||||||
|     ee.readBlock(0, (uint8_t *)&persistanceMarker_onChip, sizeof(uint16_t)); |  | ||||||
|     if (persistanceMarker_onChip < startofPersistence) |  | ||||||
|     { |  | ||||||
|       ee.readBlock(persistanceMarker_onChip + 0, (uint8_t *)&PersistenceData.writeCycleCounter, 4); |  | ||||||
|       ee.readBlock(persistanceMarker_onChip + 4, (uint8_t *)&PersistenceData.faction_1_timer, 4); |  | ||||||
|       ee.readBlock(persistanceMarker_onChip + 8, (uint8_t *)&PersistenceData.faction_2_timer, 4); |  | ||||||
|       ee.readBlock(persistanceMarker_onChip + 12, (uint8_t *)&PersistenceData.faction_3_timer, 4); |  | ||||||
|       ee.readBlock(persistanceMarker_onChip + 16, (uint8_t *)&PersistenceData.activeFaction, 4); |  | ||||||
|       ee.readBlock(persistanceMarker_onChip + 20, (uint8_t *)&PersistenceData.checksum, 4); |  | ||||||
|       MovePersistencePage_EEPROM(true); |  | ||||||
|       StorePersistence_EEPROM(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Migrate Config-Data and set defaults for Values which doesn't exists in this earlier Version |  | ||||||
|     ConfigData.EEPROM_Version = ConfigData_defaults.EEPROM_Version; |  | ||||||
|     strncpy(ConfigData.Faction_1_Name, ConfigData_defaults.Faction_1_Name, sizeof(ConfigData.Faction_1_Name)); |  | ||||||
|     strncpy(ConfigData.Faction_2_Name, ConfigData_defaults.Faction_2_Name, sizeof(ConfigData.Faction_2_Name)); |  | ||||||
|     strncpy(ConfigData.Faction_3_Name, ConfigData_defaults.Faction_3_Name, sizeof(ConfigData.Faction_3_Name)); |  | ||||||
|     ee.readBlock(17, (uint8_t *)&ConfigData.batteryType, 4); |  | ||||||
|     ee.readBlock(21, (uint8_t *)&ConfigData.active_faction_on_reboot, 1); |  | ||||||
|     StoreConfig_EEPROM(); |  | ||||||
|  |  | ||||||
|     return true; |  | ||||||
|     break; |  | ||||||
|  |  | ||||||
|   default: |  | ||||||
|     return false; |  | ||||||
|     break; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -42,12 +42,11 @@ TM1637 disp_FAC_3(GPIO_7SEG_CLK, GPIO_7SEG_EN_FAC3); | |||||||
|  |  | ||||||
| void SevenSeg_Output(); | void SevenSeg_Output(); | ||||||
| void toggleWiFiAP(boolean shutdown = false); | void toggleWiFiAP(boolean shutdown = false); | ||||||
| void SystemShutdown(); | void SystemShutdown(bool restart = false); | ||||||
| void SetBatteryType(batteryType_t type); | void SetBatteryType(batteryType_t type); | ||||||
| void ProcessKeyCombos(bool *btnState); | void ProcessKeyCombos(bool *btnState); | ||||||
| void OverrideDisplay(uint32_t time, const char *message1, const char *message2, const char *message3); | void OverrideDisplay(uint32_t time, const char *message1, const char *message2, const char *message3); | ||||||
| void initGlobals(); | void EEPROMCyclicPDS_callback(); | ||||||
| void maintainSysStat(); |  | ||||||
|  |  | ||||||
| #if defined(FEATURE_ENABLE_UARTLORA) || defined(FEATURE_ENABLE_LORA) | #if defined(FEATURE_ENABLE_UARTLORA) || defined(FEATURE_ENABLE_LORA) | ||||||
| void setMPins_Helper(int pin, int status); | void setMPins_Helper(int pin, int status); | ||||||
| @@ -61,12 +60,12 @@ void tmrCallback_FactionTicker(); | |||||||
| Ticker tmrFactionTicker(tmrCallback_FactionTicker, 1000, 0, MILLIS); | Ticker tmrFactionTicker(tmrCallback_FactionTicker, 1000, 0, MILLIS); | ||||||
| void tmrCallback_InputGetter(); | void tmrCallback_InputGetter(); | ||||||
| Ticker tmrInputGetter(tmrCallback_InputGetter, 250, 0, MILLIS); | Ticker tmrInputGetter(tmrCallback_InputGetter, 250, 0, MILLIS); | ||||||
| void tmrCallback_EEPROMCyclicPDS(); | void EEPROMCyclicPDS_callback(); | ||||||
| Ticker tmrEEPROMCyclicPDS(tmrCallback_EEPROMCyclicPDS, 60000, 0, MILLIS); | Ticker tmrEEPROMCyclicPDS(EEPROMCyclicPDS_callback, 60000, 0, MILLIS); | ||||||
|  |  | ||||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||||
| void tmrCallback_WiFiMaintainConnection(); | void wifiMaintainConnectionTicker_callback(); | ||||||
| Ticker tmrWiFiMaintainConnection(tmrCallback_WiFiMaintainConnection, 1000, 0, MILLIS); | Ticker tmrWiFiMaintainConnection(wifiMaintainConnectionTicker_callback, 1000, 0, MILLIS); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| uint32_t DisplayOverrideFlag = 0; | uint32_t DisplayOverrideFlag = 0; | ||||||
| @@ -81,12 +80,30 @@ void setMPins_Helper(int pin, int status) | |||||||
|  |  | ||||||
| void setup() | void setup() | ||||||
| { | { | ||||||
|  | 	// Set CPU frequency to 80MHz | ||||||
| 	system_update_cpu_freq(SYS_CPU_80MHZ); | 	system_update_cpu_freq(SYS_CPU_80MHZ); | ||||||
| 	strcpy(globals.DeviceName, DEVICE_NAME); |  | ||||||
| 	snprintf(globals.DeviceName_ID, 42, "%s_%08X", globals.DeviceName, ESP.getChipId()); |  | ||||||
| 	WiFi.persistent(false); |  | ||||||
| 	WiFi.disconnect(); |  | ||||||
|  |  | ||||||
|  | 	// Generate a unique device name based on ESP chip ID | ||||||
|  | 	snprintf(globals.DeviceName, 32, HOST_NAME, ESP.getChipId()); | ||||||
|  |  | ||||||
|  | 	// Disable WiFi persistent storage | ||||||
|  | 	WiFi.persistent(false); | ||||||
|  |  | ||||||
|  | 	// Initialize and clear Diagnostic Trouble Code (DTC) storage | ||||||
|  | 	ClearAllDTC(); | ||||||
|  |  | ||||||
|  | #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||||
|  | 	// Configure WiFi settings for client mode if enabled | ||||||
|  | 	WiFi.mode(WIFI_STA); | ||||||
|  | 	WiFi.setHostname(globals.DeviceName); | ||||||
|  | 	wifiMulti.addAP(QUOTE(WIFI_SSID_CLIENT), QUOTE(WIFI_PASSWORD_CLIENT)); | ||||||
|  | 	tmrWiFiMaintainConnection.start(); | ||||||
|  | #else | ||||||
|  | 	// Disable WiFi if WiFi client feature is not enabled | ||||||
|  | 	WiFi.mode(WIFI_OFF); | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | 	// Initialize Serial communication | ||||||
| 	Serial.begin(115200); | 	Serial.begin(115200); | ||||||
| 	Serial.setDebugOutput(false); | 	Serial.setDebugOutput(false); | ||||||
|  |  | ||||||
| @@ -94,11 +111,11 @@ void setup() | |||||||
| 	Serial.print(globals.DeviceName); | 	Serial.print(globals.DeviceName); | ||||||
| 	Serial.print("\nby Hiabuto Defense\n"); | 	Serial.print("\nby Hiabuto Defense\n"); | ||||||
|  |  | ||||||
| 	ClearAllDTC(); // Init DTC-Storage | 	// Initialize EEPROM, load configuration, and persistence data from EEPROM | ||||||
|  |  | ||||||
| 	InitEEPROM(); | 	InitEEPROM(); | ||||||
| 	GetConfig_EEPROM(); | 	GetConfig_EEPROM(); | ||||||
| 	GetPersistence_EEPROM(); | 	GetPersistence_EEPROM(); | ||||||
|  | 	Serial.print("\nEE-Init done"); | ||||||
|  |  | ||||||
| 	if (i2c_io.begin()) | 	if (i2c_io.begin()) | ||||||
| 	{ | 	{ | ||||||
| @@ -131,18 +148,9 @@ void setup() | |||||||
| 	} | 	} | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | 	// Set up OTA updates | ||||||
| 	WiFi.mode(WIFI_STA); |  | ||||||
| 	WiFi.setHostname(globals.DeviceName); |  | ||||||
| 	wifiMulti.addAP(QUOTE(WIFI_CLIENT_SSID), QUOTE(WIFI_CLIENT_PASSWORD)); |  | ||||||
| 	tmrWiFiMaintainConnection.start(); |  | ||||||
| 	Serial.print("WiFi-Client Initialized\n"); |  | ||||||
| #else |  | ||||||
| 	WiFi.mode(WIFI_OFF); |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| 	ArduinoOTA.setPort(8266); | 	ArduinoOTA.setPort(8266); | ||||||
| 	ArduinoOTA.setHostname(globals.DeviceName_ID); | 	ArduinoOTA.setHostname(globals.DeviceName); | ||||||
| 	ArduinoOTA.setPassword(QUOTE(ADMIN_PASSWORD)); | 	ArduinoOTA.setPassword(QUOTE(ADMIN_PASSWORD)); | ||||||
|  |  | ||||||
| 	ArduinoOTA.onStart([]() | 	ArduinoOTA.onStart([]() | ||||||
| @@ -186,15 +194,21 @@ void setup() | |||||||
|                        else if (error == OTA_END_ERROR) |                        else if (error == OTA_END_ERROR) | ||||||
|                          Serial.println("End Failed"); }); |                          Serial.println("End Failed"); }); | ||||||
|  |  | ||||||
|  | 	// Begin OTA updates | ||||||
| 	ArduinoOTA.begin(); | 	ArduinoOTA.begin(); | ||||||
| 	Serial.print("\nOTA-Init done"); | 	Serial.print("\nOTA-Init done"); | ||||||
|  |  | ||||||
|  | 	// Initialize the web user interface | ||||||
| 	initWebUI(); | 	initWebUI(); | ||||||
| 	Serial.print("\nWebUI-Init done"); | 	Serial.print("\nWebUI-Init done"); | ||||||
|  |  | ||||||
|  | 	// Initialize global variables | ||||||
| 	initGlobals(); | 	initGlobals(); | ||||||
| 	Serial.print("\nglobals-Init done"); | 	Serial.print("\nglobals-Init done"); | ||||||
| #ifdef CAPTIVE |  | ||||||
| 	dnsServer.start(53, "*", WiFi.softAPIP()); | 	// Start cyclic EEPROM updates for Persistence Data Structure (PDS) | ||||||
| #endif | 	tmrEEPROMCyclicPDS.start(); | ||||||
|  | 	Serial.print("\nSetup Done\n"); | ||||||
|  |  | ||||||
| 	disp_FAC_1.init(); | 	disp_FAC_1.init(); | ||||||
| 	disp_FAC_1.setBrightness(5); | 	disp_FAC_1.setBrightness(5); | ||||||
| @@ -215,8 +229,6 @@ void setup() | |||||||
|  |  | ||||||
| void loop() | void loop() | ||||||
| { | { | ||||||
| 	maintainSysStat(); |  | ||||||
|  |  | ||||||
| 	tmrEEPROMCyclicPDS.update(); | 	tmrEEPROMCyclicPDS.update(); | ||||||
| 	tmrFactionTicker.update(); | 	tmrFactionTicker.update(); | ||||||
| 	tmrInputGetter.update(); | 	tmrInputGetter.update(); | ||||||
| @@ -234,13 +246,59 @@ void loop() | |||||||
| 	tmrStatusSender.update(); | 	tmrStatusSender.update(); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef CAPTIVE |  | ||||||
| 	dnsServer.processNextRequest(); |  | ||||||
| #endif |  | ||||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||||
|  | 	// Update WiFi connection maintenance ticker if WiFi client feature is enabled | ||||||
| 	tmrWiFiMaintainConnection.update(); | 	tmrWiFiMaintainConnection.update(); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | 	static tSystem_Status lastStatus = sysStat_Error; | ||||||
|  |  | ||||||
|  | 	// Handle different system statuses | ||||||
|  | 	switch (globals.systemStatus) | ||||||
|  | 	{ | ||||||
|  | 	case sysStat_Startup: | ||||||
|  | 		if (lastStatus != globals.systemStatus) | ||||||
|  | 		{ | ||||||
|  | 			strcpy_P(globals.systemStatustxt, PSTR("Startup")); | ||||||
|  | 			lastStatus = globals.systemStatus; | ||||||
|  | 		} | ||||||
|  | 		// Transition to Normal status after startup delay | ||||||
|  | 		if (millis() > STARTUP_DELAY) | ||||||
|  | 		{ | ||||||
|  | 			globals.systemStatus = sysStat_Normal; | ||||||
|  | 			globals.resumeStatus = sysStat_Normal; | ||||||
|  | 		} | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 	case sysStat_Normal: | ||||||
|  | 		if (lastStatus != globals.systemStatus) | ||||||
|  | 		{ | ||||||
|  | 			strcpy_P(globals.systemStatustxt, PSTR("Normal")); | ||||||
|  | 			lastStatus = globals.systemStatus; | ||||||
|  | 		} | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 		if (lastStatus != globals.systemStatus) | ||||||
|  | 		{ | ||||||
|  | 			strcpy_P(globals.systemStatustxt, PSTR("Error")); | ||||||
|  | 			lastStatus = globals.systemStatus; | ||||||
|  | 		} | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 	case sysStat_Shutdown: | ||||||
|  | 		if (lastStatus != globals.systemStatus) | ||||||
|  | 		{ | ||||||
|  | 			strcpy_P(globals.systemStatustxt, PSTR("Shutdown")); | ||||||
|  | 			lastStatus = globals.systemStatus; | ||||||
|  | 		} | ||||||
|  | 		SystemShutdown(false); | ||||||
|  | 		break; | ||||||
|  |  | ||||||
|  | 	default: | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Yield to allow other tasks to run | ||||||
| 	yield(); | 	yield(); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -254,7 +312,7 @@ String macToString(const unsigned char *mac) | |||||||
|  |  | ||||||
| void SevenSeg_Output() | void SevenSeg_Output() | ||||||
| { | { | ||||||
| 	char sevenSegBuff[5] = ""; | 	char sevenSegBuff[9] = ""; | ||||||
|  |  | ||||||
| 	if (DisplayOverrideFlag > millis()) | 	if (DisplayOverrideFlag > millis()) | ||||||
| 	{ | 	{ | ||||||
| @@ -270,9 +328,11 @@ void SevenSeg_Output() | |||||||
| 		if (globals.battery_level < BAT_LOW_PERCENT && millis() % 10000 > 7000) | 		if (globals.battery_level < BAT_LOW_PERCENT && millis() % 10000 > 7000) | ||||||
| 		{ | 		{ | ||||||
| 			if (millis() % 3000 < 1500) | 			if (millis() % 3000 < 1500) | ||||||
| 				snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", globals.battery_level); | 				snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4u", globals.battery_level); | ||||||
| 			else | 			else | ||||||
| 				snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%3d.%1d", (globals.loadvoltage_mV / 1000), ((globals.loadvoltage_mV % 1000) / 100)); | 				snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%3u.%1u", (globals.loadvoltage_mV / 1000), ((globals.loadvoltage_mV % 1000) / 100)); | ||||||
|  |  | ||||||
|  | 			sevenSegBuff[5] = '\0'; // truncate here if for any reason something bigger is in buffer than ecpected | ||||||
|  |  | ||||||
| 			disp_FAC_1.setBrightness(1); | 			disp_FAC_1.setBrightness(1); | ||||||
| 			disp_FAC_1.display(" Bat"); | 			disp_FAC_1.display(" Bat"); | ||||||
| @@ -288,18 +348,21 @@ void SevenSeg_Output() | |||||||
| 			disp_FAC_1.setBrightness(PersistenceData.activeFaction == FACTION_1 ? 5 : 1); | 			disp_FAC_1.setBrightness(PersistenceData.activeFaction == FACTION_1 ? 5 : 1); | ||||||
| 			disp_FAC_1.refresh(); | 			disp_FAC_1.refresh(); | ||||||
| 			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_1_timer / 60); | 			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_1_timer / 60); | ||||||
|  | 			sevenSegBuff[5] = '\0'; // truncate here if for any reason something bigger is in buffer than ecpected | ||||||
| 			disp_FAC_1.display(String(sevenSegBuff), false, false); | 			disp_FAC_1.display(String(sevenSegBuff), false, false); | ||||||
| 			disp_FAC_1.setDp((PersistenceData.activeFaction == FACTION_1) && (millis() % 1000 > 500) ? 0x08 : 0x00); | 			disp_FAC_1.setDp((PersistenceData.activeFaction == FACTION_1) && (millis() % 1000 > 500) ? 0x08 : 0x00); | ||||||
|  |  | ||||||
| 			disp_FAC_2.setBrightness(PersistenceData.activeFaction == FACTION_2 ? 5 : 1); | 			disp_FAC_2.setBrightness(PersistenceData.activeFaction == FACTION_2 ? 5 : 1); | ||||||
| 			disp_FAC_2.refresh(); | 			disp_FAC_2.refresh(); | ||||||
| 			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_2_timer / 60); | 			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_2_timer / 60); | ||||||
|  | 			sevenSegBuff[5] = '\0'; // truncate here if for any reason something bigger is in buffer than ecpected | ||||||
| 			disp_FAC_2.display(String(sevenSegBuff), false, false); | 			disp_FAC_2.display(String(sevenSegBuff), false, false); | ||||||
| 			disp_FAC_2.setDp((PersistenceData.activeFaction == FACTION_2) && (millis() % 1000 > 500) ? 0x08 : 0x00); | 			disp_FAC_2.setDp((PersistenceData.activeFaction == FACTION_2) && (millis() % 1000 > 500) ? 0x08 : 0x00); | ||||||
|  |  | ||||||
| 			disp_FAC_3.setBrightness(PersistenceData.activeFaction == FACTION_3 ? 5 : 1); | 			disp_FAC_3.setBrightness(PersistenceData.activeFaction == FACTION_3 ? 5 : 1); | ||||||
| 			disp_FAC_3.refresh(); | 			disp_FAC_3.refresh(); | ||||||
| 			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_3_timer / 60); | 			snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_3_timer / 60); | ||||||
|  | 			sevenSegBuff[5] = '\0'; // truncate here if for any reason something bigger is in buffer than ecpected | ||||||
| 			disp_FAC_3.display(String(sevenSegBuff), false, false); | 			disp_FAC_3.display(String(sevenSegBuff), false, false); | ||||||
| 			disp_FAC_3.setDp((PersistenceData.activeFaction == FACTION_3) && (millis() % 1000 > 500) ? 0x08 : 0x00); | 			disp_FAC_3.setDp((PersistenceData.activeFaction == FACTION_3) && (millis() % 1000 > 500) ? 0x08 : 0x00); | ||||||
| 		} | 		} | ||||||
| @@ -425,8 +488,8 @@ void tmrCallback_PowerMonitor() | |||||||
| 		break; | 		break; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	MaintainDTC(DTC_BAT_LOW, DTC_WARN, (battery_level < 15 ? true : false), battery_level); | 	MaintainDTC(DTC_BAT_LOW, (battery_level < 15 ? true : false), battery_level); | ||||||
| 	MaintainDTC(DTC_BAT_CRITICAL, DTC_CRITICAL, (battery_level < 5 ? true : false), battery_level); | 	MaintainDTC(DTC_BAT_CRITICAL, (battery_level < 5 ? true : false), battery_level); | ||||||
|  |  | ||||||
| 	// Serial.printf("Battery Level: %d %%\n", globals.battery_level); | 	// Serial.printf("Battery Level: %d %%\n", globals.battery_level); | ||||||
| 	// Serial.printf("Bus Voltage: %f V\n", busvoltage); | 	// Serial.printf("Bus Voltage: %f V\n", busvoltage); | ||||||
| @@ -436,74 +499,131 @@ void tmrCallback_PowerMonitor() | |||||||
| 	// Serial.printf("Power: %f mW\n", power_mW); | 	// Serial.printf("Power: %f mW\n", power_mW); | ||||||
| } | } | ||||||
|  |  | ||||||
| void tmrCallback_EEPROMCyclicPDS() | /** | ||||||
|  |  * @brief Callback function for cyclically storing Persistence Data Structure (PDS) to EEPROM. | ||||||
|  |  * | ||||||
|  |  * This callback function is invoked periodically to store the Persistence Data Structure (PDS) | ||||||
|  |  * to the EEPROM. It ensures that essential data is saved persistently, allowing the system to | ||||||
|  |  * recover its state after power cycles or resets. | ||||||
|  |  */ | ||||||
|  | void EEPROMCyclicPDS_callback() | ||||||
| { | { | ||||||
| 	StorePersistence_EEPROM(); | 	StorePersistence_EEPROM(); | ||||||
| } | } | ||||||
|  |  | ||||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||||
| void tmrCallback_WiFiMaintainConnection() | /** | ||||||
|  |  * @brief Callback function for maintaining WiFi connection and handling connection failures. | ||||||
|  |  * | ||||||
|  |  * This callback function is used by a ticker to periodically check the WiFi connection status. | ||||||
|  |  * If the device is not connected to WiFi, it counts connection failures. If the number of failures | ||||||
|  |  * exceeds a defined threshold, the function triggers the initiation of an Access Point (AP) mode | ||||||
|  |  * using the `toggleWiFiAP` function. | ||||||
|  |  */ | ||||||
|  | void wifiMaintainConnectionTicker_callback() | ||||||
| { | { | ||||||
|  | 	// Static variables to track WiFi connection failure count and maximum allowed failures | ||||||
| 	static uint32_t WiFiFailCount = 0; | 	static uint32_t WiFiFailCount = 0; | ||||||
| 	const uint32_t WiFiFailMax = 20; | 	const uint32_t WiFiFailMax = 20; | ||||||
|  |  | ||||||
|  | 	// Check if the device is connected to WiFi | ||||||
| 	if (wifiMulti.run(connectTimeoutMs) == WL_CONNECTED) | 	if (wifiMulti.run(connectTimeoutMs) == WL_CONNECTED) | ||||||
| 	{ | 	{ | ||||||
| 		return; | 		return; // Exit if connected | ||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
|  | 		// Increment WiFi connection failure count | ||||||
| 		if (WiFiFailCount < WiFiFailMax) | 		if (WiFiFailCount < WiFiFailMax) | ||||||
| 		{ | 		{ | ||||||
| 			WiFiFailCount++; | 			WiFiFailCount++; | ||||||
| 		} | 		} | ||||||
| 		else | 		else | ||||||
| 		{ | 		{ | ||||||
| 			Debug_pushMessage("WiFi not connected! - Start AP"); | 			// Trigger AP mode if the maximum failures are reached | ||||||
|  | 			Debug_pushMessage("WiFi not connected! - Start AP\n"); | ||||||
| 			toggleWiFiAP(); | 			toggleWiFiAP(); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| void toggleWiFiAP(boolean shutdown) | /** | ||||||
|  |  * @brief Toggles the WiFi functionality based on the current status. | ||||||
|  |  * | ||||||
|  |  * This function manages the WiFi state, either turning it off or starting it as an Access Point (AP), | ||||||
|  |  * depending on the current mode. If the WiFi is turned off, it can be started in AP mode with the | ||||||
|  |  * device name and password configured. Additionally, it may stop certain operations related to WiFi | ||||||
|  |  * maintenance or display debug messages based on the defined features. | ||||||
|  |  * | ||||||
|  |  * @param shutdown Flag indicating whether the system is in a shutdown state. | ||||||
|  |  */ | ||||||
|  | void toggleWiFiAP(bool shutdown) | ||||||
| { | { | ||||||
|  | 	// Check if WiFi is currently active | ||||||
| 	if (WiFi.getMode() != WIFI_OFF) | 	if (WiFi.getMode() != WIFI_OFF) | ||||||
| 	{ | 	{ | ||||||
|  | 		// Turn off WiFi | ||||||
| 		WiFi.mode(WIFI_OFF); | 		WiFi.mode(WIFI_OFF); | ||||||
| 		Serial.println("WiFi turned off"); | 		Debug_pushMessage("WiFi turned off\n"); | ||||||
|  |  | ||||||
|  | 		// Stop WiFi maintenance connection ticker if enabled | ||||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||||
| 		tmrWiFiMaintainConnection.stop(); | 		tmrWiFiMaintainConnection.stop(); | ||||||
| #endif | #endif | ||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
| 	{ | 	{ | ||||||
|  | 		// Start WiFi in Access Point (AP) mode | ||||||
| 		WiFi.mode(WIFI_AP); | 		WiFi.mode(WIFI_AP); | ||||||
| 		WiFi.softAPConfig(IPAddress(WIFI_AP_IP_GW), IPAddress(WIFI_AP_IP_GW), IPAddress(255, 255, 255, 0)); | 		WiFi.softAPConfig(IPAddress(WIFI_AP_IP_GW), IPAddress(WIFI_AP_IP_GW), IPAddress(255, 255, 255, 0)); | ||||||
| 		WiFi.softAP(QUOTE(WIFI_AP_SSID), QUOTE(WIFI_AP_PASSWORD)); | 		WiFi.softAP(globals.DeviceName, QUOTE(WIFI_AP_PASSWORD)); | ||||||
|  |  | ||||||
|  | 		// Stop WiFi maintenance connection ticker if enabled and display debug messages | ||||||
| #ifdef FEATURE_ENABLE_WIFI_CLIENT | #ifdef FEATURE_ENABLE_WIFI_CLIENT | ||||||
| 		tmrWiFiMaintainConnection.stop(); | 		tmrWiFiMaintainConnection.stop(); | ||||||
| 		Serial.println("WiFi AP started, stopped Maintain-Timer"); | 		Debug_pushMessage("WiFi AP started, stopped Maintain-Timer\n"); | ||||||
| #else | #else | ||||||
| 		Serial.println("WiFi AP started"); | 		Debug_pushMessage("WiFi AP started\n"); | ||||||
| #endif | #endif | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void SystemShutdown() | /** | ||||||
|  |  * @brief Performs necessary tasks before shutting down and optionally restarts the ESP. | ||||||
|  |  * | ||||||
|  |  * This function initiates a system shutdown, performing tasks such as storing configuration | ||||||
|  |  * and persistence data to EEPROM before shutting down. If a restart is requested, the ESP | ||||||
|  |  * will be restarted; otherwise, the system will enter an indefinite loop. | ||||||
|  |  * | ||||||
|  |  * @param restart Flag indicating whether to restart the ESP after shutdown (default: false). | ||||||
|  |  */ | ||||||
|  | void SystemShutdown(bool restart) | ||||||
| { | { | ||||||
| 	static uint32_t shutdown_delay = 0; | 	static uint32_t shutdown_delay = 0; | ||||||
|  |  | ||||||
|  | 	// Initialize shutdown delay on the first call | ||||||
| 	if (shutdown_delay == 0) | 	if (shutdown_delay == 0) | ||||||
| 	{ | 	{ | ||||||
| 		shutdown_delay = millis() + SHUTDOWN_DELAY_MS; | 		shutdown_delay = millis() + SHUTDOWN_DELAY_MS; | ||||||
| 		Serial.printf("Shutdown requested - Restarting in %d seconds\n", SHUTDOWN_DELAY_MS / 1000); | 		Serial.printf("Shutdown requested - Restarting in %d seconds\n", SHUTDOWN_DELAY_MS / 1000); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// Check if the shutdown delay has elapsed | ||||||
| 	if (shutdown_delay < millis()) | 	if (shutdown_delay < millis()) | ||||||
| 	{ | 	{ | ||||||
| 		StoreConfig_EEPROM(); | 		Webserver_Shutdown(); | ||||||
|  |  | ||||||
|  | 		// Store persistence data to EEPROM | ||||||
| 		StorePersistence_EEPROM(); | 		StorePersistence_EEPROM(); | ||||||
| 		ESP.restart(); |  | ||||||
|  | 		// Perform restart if requested, otherwise enter an indefinite loop | ||||||
|  | 		if (restart) | ||||||
|  | 			ESP.restart(); | ||||||
|  | 		else | ||||||
|  | 			while (1) | ||||||
|  | 				; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -605,7 +725,7 @@ void ProcessKeyCombos(bool *btnState) | |||||||
| void maintainSysStat() | void maintainSysStat() | ||||||
| { | { | ||||||
|  |  | ||||||
| 	static tSystem_Status lastStat = sysStat_null; | 	static tSystem_Status lastStat = sysStat_Startup; | ||||||
|  |  | ||||||
| 	// system Status Transistions | 	// system Status Transistions | ||||||
| 	switch (globals.systemStatus) | 	switch (globals.systemStatus) | ||||||
| @@ -621,7 +741,6 @@ void maintainSysStat() | |||||||
|  |  | ||||||
| 	case sysStat_Error: | 	case sysStat_Error: | ||||||
| 	case sysStat_Normal: | 	case sysStat_Normal: | ||||||
| 	case sysStat_null: |  | ||||||
| 	default: | 	default: | ||||||
| 		break; | 		break; | ||||||
| 	} | 	} | ||||||
| @@ -641,7 +760,6 @@ void maintainSysStat() | |||||||
|  |  | ||||||
| 		case sysStat_Error: | 		case sysStat_Error: | ||||||
| 		case sysStat_Normal: | 		case sysStat_Normal: | ||||||
| 		case sysStat_null: |  | ||||||
| 		default: | 		default: | ||||||
| 			break; | 			break; | ||||||
| 		} | 		} | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								Software/src/struct2json.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								Software/src/struct2json.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | /** | ||||||
|  |  * @file struct2json.cpp | ||||||
|  |  * | ||||||
|  |  * @brief Implementation file for converting structs to JSON objects. | ||||||
|  |  * | ||||||
|  |  * @note This file is auto-generated by a script on 2024-05-30 22:54:25. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   30.05.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #include "struct2json.h" | ||||||
|  |  | ||||||
|  | void generateJsonObject_ConfigData(JsonObject data) | ||||||
|  | { | ||||||
|  |     data["EEPROM_Version"] = ConfigData.EEPROM_Version; | ||||||
|  |     data["batteryType"] = ConfigData.batteryType; | ||||||
|  |     data["active_faction_on_reboot"] = ConfigData.active_faction_on_reboot; | ||||||
|  |     data["Faction_1_Name"] = ConfigData.Faction_1_Name; | ||||||
|  |     data["Faction_2_Name"] = ConfigData.Faction_2_Name; | ||||||
|  |     data["Faction_3_Name"] = ConfigData.Faction_3_Name; | ||||||
|  |     data["wifi_ap_ssid"] = ConfigData.wifi_ap_ssid; | ||||||
|  |     data["wifi_ap_password"] = ConfigData.wifi_ap_password; | ||||||
|  |     data["wifi_client_ssid"] = ConfigData.wifi_client_ssid; | ||||||
|  |     data["wifi_client_password"] = ConfigData.wifi_client_password; | ||||||
|  |     data["wifi_autoconnect"] = ConfigData.wifi_autoconnect; | ||||||
|  |     data["checksum"] = ConfigData.checksum; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | void generateJsonObject_PersistenceData(JsonObject data) | ||||||
|  | { | ||||||
|  |     data["writeCycleCounter"] = PersistenceData.writeCycleCounter; | ||||||
|  |     data["faction_1_timer"] = PersistenceData.faction_1_timer; | ||||||
|  |     data["faction_2_timer"] = PersistenceData.faction_2_timer; | ||||||
|  |     data["faction_3_timer"] = PersistenceData.faction_3_timer; | ||||||
|  |     data["activeFaction"] = PersistenceData.activeFaction; | ||||||
|  |     data["checksum"] = PersistenceData.checksum; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | // CODEGENERATOR_CHECKSUM: 735cd4daf9a46bd773bdf5e6cd5a58d61b0d877196399bc2784a0d0ea7af717d | ||||||
| @@ -1,3 +1,16 @@ | |||||||
|  | /** | ||||||
|  |  * @file webui.cpp | ||||||
|  |  * | ||||||
|  |  * @brief Implementation file for web-based user interface (WebUI) functions in the ChainLube application. | ||||||
|  |  * | ||||||
|  |  * This file contains the implementation of functions related to the initialization and processing of the | ||||||
|  |  * web-based user interface (WebUI). It includes the setup of LittleFS, handling of firmware version checks, | ||||||
|  |  * initialization of mDNS, setup of web server routes, and handling of various HTTP events. | ||||||
|  |  * | ||||||
|  |  * @author Marcel Peterkau | ||||||
|  |  * @date   09.01.2024 | ||||||
|  |  */ | ||||||
|  |  | ||||||
| #include "webui.h" | #include "webui.h" | ||||||
|  |  | ||||||
| AsyncWebServer webServer(80); | AsyncWebServer webServer(80); | ||||||
| @@ -5,9 +18,7 @@ AsyncWebServer webServer(80); | |||||||
| const char *PARAM_MESSAGE = "message"; | const char *PARAM_MESSAGE = "message"; | ||||||
|  |  | ||||||
| String processor(const String &var); | String processor(const String &var); | ||||||
| void WebserverPOST_Callback(AsyncWebServerRequest *request); |  | ||||||
| void WebserverNotFound_Callback(AsyncWebServerRequest *request); | void WebserverNotFound_Callback(AsyncWebServerRequest *request); | ||||||
| void Webserver_Callback(AsyncWebServerRequest *request); |  | ||||||
| void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); | void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); | ||||||
| void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); | void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final); | ||||||
| void WebServerEEJSON_Callback(AsyncWebServerRequest *request); | void WebServerEEJSON_Callback(AsyncWebServerRequest *request); | ||||||
| @@ -17,295 +28,137 @@ AsyncWebSocket webSocket("/ws"); | |||||||
|  |  | ||||||
| void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); | void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); | ||||||
| void Websocket_HandleMessage(void *arg, uint8_t *data, size_t len); | void Websocket_HandleMessage(void *arg, uint8_t *data, size_t len); | ||||||
|  | void Websocket_RefreshClientData_DTCs(uint32_t client_id); | ||||||
|  | void Websocket_RefreshClientData_Status(uint32_t client_id, bool send_mapping = false); | ||||||
|  | void Websocket_RefreshClientData_Static(uint32_t client_id, bool send_mapping = false); | ||||||
|  | void Websocket_HandleButtons(uint8_t *data); | ||||||
|  | void Websocket_HandleSettings(uint8_t *data); | ||||||
|  | void parseWebsocketString(char *data, char *identifierBuffer, size_t identifierBufferSize, char *valueBuffer, size_t valueBufferSize); | ||||||
|  | int findIndexByString(const char *searchString, const char *const *array, int arraySize); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Initializes the web-based user interface (WebUI) for the ChainLube application. | ||||||
|  |  * | ||||||
|  |  * This function sets up the necessary components for the WebUI, including mounting LittleFS, | ||||||
|  |  * performing flash version checks, initializing mDNS, and configuring the web server with | ||||||
|  |  * routes and event handlers. If any errors occur during setup, appropriate diagnostic messages | ||||||
|  |  * are pushed to the debugging system, and potential error conditions are recorded as Diagnostic | ||||||
|  |  * Trouble Codes (DTCs). | ||||||
|  |  * | ||||||
|  |  * @note This function should be called during the initialization phase of the application. | ||||||
|  |  */ | ||||||
| void initWebUI() | void initWebUI() | ||||||
| { | { | ||||||
|  |   // Attempt to mount LittleFS | ||||||
|   if (!LittleFS.begin()) |   if (!LittleFS.begin()) | ||||||
|   { |   { | ||||||
|     Debug_pushMessage("An Error has occurred while mounting LittleFS\n"); |     Debug_pushMessage("An Error has occurred while mounting LittleFS\n"); | ||||||
|     MaintainDTC(DTC_FLASHFS_ERROR, DTC_CRITICAL, true); |     MaintainDTC(DTC_FLASHFS_ERROR, true); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Retrieve the flash version | ||||||
|   GetFlashVersion(globals.FlashVersion, sizeof(globals.FlashVersion)); |   GetFlashVersion(globals.FlashVersion, sizeof(globals.FlashVersion)); | ||||||
|  |  | ||||||
|  |   // Compare the flash version with the required version | ||||||
|   char buffer[6]; |   char buffer[6]; | ||||||
|   snprintf(buffer, sizeof(buffer), "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor); |   snprintf(buffer, sizeof(buffer), "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor); | ||||||
|   if (strcmp(globals.FlashVersion, buffer)) |   if (strcmp(globals.FlashVersion, buffer)) | ||||||
|   { |   { | ||||||
|     MaintainDTC(DTC_FLASHFS_VERSION_ERROR, DTC_WARN, true); |     MaintainDTC(DTC_FLASHFS_VERSION_ERROR, true); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Initialize mDNS and add service | ||||||
|   MDNS.begin(globals.DeviceName); |   MDNS.begin(globals.DeviceName); | ||||||
|   MDNS.addService("http", "tcp", 80); |   MDNS.addService("http", "tcp", 80); | ||||||
|  |  | ||||||
|  |   // Set up WebSocket event handler and attach to web server | ||||||
|   webSocket.onEvent(WebsocketEvent_Callback); |   webSocket.onEvent(WebsocketEvent_Callback); | ||||||
|   webServer.addHandler(&webSocket); |   webServer.addHandler(&webSocket); | ||||||
|  |  | ||||||
|  |   // Serve static files and define routes | ||||||
|   webServer.serveStatic("/static/", LittleFS, "/static/").setCacheControl("max-age=360000"); |   webServer.serveStatic("/static/", LittleFS, "/static/").setCacheControl("max-age=360000"); | ||||||
|  |   webServer.serveStatic("/index.htm", LittleFS, "/index.htm").setCacheControl("max-age=360000"); | ||||||
|   webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) |   webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request) | ||||||
|                { request->redirect("/index.htm"); }); |                { request->redirect("/index.htm"); }); | ||||||
|   webServer.onNotFound(WebserverNotFound_Callback); |   webServer.onNotFound(WebserverNotFound_Callback); | ||||||
|   webServer.on("/index.htm", HTTP_GET, Webserver_Callback); |  | ||||||
|   webServer.on("/post.htm", HTTP_POST, WebserverPOST_Callback); |  | ||||||
|   webServer.on("/eejson", HTTP_GET, WebServerEEJSON_Callback); |   webServer.on("/eejson", HTTP_GET, WebServerEEJSON_Callback); | ||||||
|   webServer.on( |   webServer.on( | ||||||
|       "/doUpdate", HTTP_POST, [](AsyncWebServerRequest *request) {}, WebserverFirmwareUpdate_Callback); |       "/doUpdate", HTTP_POST, [](AsyncWebServerRequest *request) {}, WebserverFirmwareUpdate_Callback); | ||||||
|   webServer.on( |   webServer.on( | ||||||
|       "/eeRestore", HTTP_POST, [](AsyncWebServerRequest *request) {}, WebserverEERestore_Callback); |       "/eeRestore", HTTP_POST, [](AsyncWebServerRequest *request) {}, WebserverEERestore_Callback); | ||||||
|  |  | ||||||
|  |   // Start the web server | ||||||
|   webServer.begin(); |   webServer.begin(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Processes the web server functionality for the ChainLube application. | ||||||
|  |  * | ||||||
|  |  * This function performs periodic processing tasks for the web server, including cleaning up | ||||||
|  |  * WebSocket clients and refreshing client data when WebSocket connections are active. It ensures | ||||||
|  |  * that WebSocket client data related to Diagnostic Trouble Codes (DTCs) and system status is | ||||||
|  |  * updated at regular intervals. | ||||||
|  |  * | ||||||
|  |  * @note This function should be called in the main loop of the application. | ||||||
|  |  */ | ||||||
| void Webserver_Process() | void Webserver_Process() | ||||||
| { | { | ||||||
|  |   static uint32_t previousMillis = 0; | ||||||
|  |  | ||||||
|   webSocket.cleanupClients(); |   webSocket.cleanupClients(); | ||||||
|  |  | ||||||
|  |   if ((webSocket.count() > 0) && (millis() - previousMillis >= 10000)) | ||||||
|  |   { | ||||||
|  |     Websocket_RefreshClientData_DTCs(0); | ||||||
|  |     Websocket_RefreshClientData_Status(0); | ||||||
|  |     previousMillis = millis(); | ||||||
|  |   } | ||||||
| } | } | ||||||
| String processor(const String &var) |  | ||||||
|  | /** | ||||||
|  |  * @brief Shuts down the web server functionality for the ChainLube application. | ||||||
|  |  * | ||||||
|  |  * This function closes all WebSocket connections and terminates the web server. It is intended | ||||||
|  |  * to be called when the application is being shut down or when there is a need to deactivate the | ||||||
|  |  * web server. | ||||||
|  |  * | ||||||
|  |  * @details This function ensures a graceful shutdown of the web server by closing all active | ||||||
|  |  *          WebSocket connections and ending the web server instance. | ||||||
|  |  * | ||||||
|  |  * @note This function should be called before shutting down the application to properly | ||||||
|  |  *       deactivate the web server. | ||||||
|  |  */ | ||||||
|  | void Webserver_Shutdown() | ||||||
| { | { | ||||||
|   if (var == "HOSTNAME") |   if (webSocket.count() > 0) | ||||||
|     return String(globals.DeviceName); |     webSocket.closeAll(); | ||||||
|   if (var == "SYSTEM_STATUS") |   webServer.end(); | ||||||
|     return String(sSystem_Status_txt[globals.systemStatus]); |  | ||||||
|   if (var == "SW_VERSION") |  | ||||||
|   { |  | ||||||
|     char buffer[6]; |  | ||||||
|     snprintf(buffer, sizeof(buffer), "%d.%02d", constants.FW_Version_major, constants.FW_Version_minor); |  | ||||||
|     return String(buffer); |  | ||||||
|   } |  | ||||||
|   if (var == "FS_VERSION") |  | ||||||
|     return String(globals.FlashVersion); |  | ||||||
|   if (var == "GIT_REV") |  | ||||||
|     return String(constants.GitHash); |  | ||||||
|  |  | ||||||
|   if (var == "SHOW_DTC_TABLE") |  | ||||||
|     return globals.systemStatus == sysStat_Error ? "" : "hidden"; |  | ||||||
|   if (var == "BAT_REMAIN_CAPACITY") |  | ||||||
|     return String(globals.battery_level); |  | ||||||
|   if (var == "DEVICENAME") |  | ||||||
|     return String(globals.DeviceName); |  | ||||||
|   if (var == "DEVICENAME_ID") |  | ||||||
|     return String(globals.DeviceName_ID); |  | ||||||
|   if (var == "BATTERY_TYPE") |  | ||||||
|     return String(ConfigData.batteryType); |  | ||||||
|   if (var == "BAT_VOLTAGE") |  | ||||||
|     return String((float)globals.loadvoltage_mV / 1000.0); |  | ||||||
|   if (var == "PERSISTANCE_CHECKSUM") |  | ||||||
|   { |  | ||||||
|     char buffer[7]; |  | ||||||
|     sprintf(buffer, "0x%04X", PersistenceData.checksum); |  | ||||||
|     return String(buffer); |  | ||||||
|   } |  | ||||||
|   if (var == "WRITE_CYCLE_COUNT") |  | ||||||
|     return String(PersistenceData.writeCycleCounter); |  | ||||||
|   if (var == "PERSISTENCE_MARKER") |  | ||||||
|     return String(globals.eePersistanceAdress); |  | ||||||
|   if (var == "EEPROM_VERSION") |  | ||||||
|     return String(ConfigData.EEPROM_Version); |  | ||||||
|   if (var == "CONFIG_CHECKSUM") |  | ||||||
|   { |  | ||||||
|     char buffer[7]; |  | ||||||
|     sprintf(buffer, "0x%04X", ConfigData.checksum); |  | ||||||
|     return String(buffer); |  | ||||||
|   } |  | ||||||
|   if (var == "DTC_TABLE") |  | ||||||
|   { |  | ||||||
|     String temp = ""; |  | ||||||
|     char buff_timestamp[16]; // Format: DD-hh:mm:ss:xxx |  | ||||||
|  |  | ||||||
|     for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++) |  | ||||||
|     { |  | ||||||
|       if (DTCStorage[i].Number < DTC_LAST_DTC) |  | ||||||
|       { |  | ||||||
|         sprintf(buff_timestamp, "%02d-%02d:%02d:%02d:%03d", |  | ||||||
|                 DTCStorage[i].timestamp / 86400000,    // Days |  | ||||||
|                 DTCStorage[i].timestamp / 360000 % 24, // Hours |  | ||||||
|                 DTCStorage[i].timestamp / 60000 % 60,  // Minutes |  | ||||||
|                 DTCStorage[i].timestamp / 1000 % 60,   // Seconds |  | ||||||
|                 DTCStorage[i].timestamp % 1000);       // milliseconds |  | ||||||
|  |  | ||||||
|         temp = temp + "<tr data-dtc=" + String(DTCStorage[i].Number); |  | ||||||
|         temp = temp + " data-debugval=" + String(DTCStorage[i].debugVal) + "><td>" + String(buff_timestamp); |  | ||||||
|         temp = temp + "</td><td>" + String(DTCStorage[i].Number) + "</td><td>"; |  | ||||||
|         temp = temp + "<img src=static/img/"; |  | ||||||
|         switch (DTCStorage[i].severity) |  | ||||||
|         { |  | ||||||
|         case DTC_CRITICAL: |  | ||||||
|           temp = temp + "critical"; |  | ||||||
|           break; |  | ||||||
|         case DTC_WARN: |  | ||||||
|           temp = temp + "warn"; |  | ||||||
|           break; |  | ||||||
|         case DTC_INFO: |  | ||||||
|           temp = temp + "info"; |  | ||||||
|           break; |  | ||||||
|         } |  | ||||||
|         temp = temp + ".png></td><td>"; |  | ||||||
|  |  | ||||||
|         if (DTCStorage[i].active == DTC_ACTIVE) |  | ||||||
|           temp = temp + "active"; |  | ||||||
|         else if (DTCStorage[i].active == DTC_PREVIOUS) |  | ||||||
|           temp = temp + "previous"; |  | ||||||
|         else |  | ||||||
|           temp = temp + "none"; |  | ||||||
|  |  | ||||||
|         temp = temp + "</td></tr>"; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     return temp; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (var == "PLACEHOLDER") |  | ||||||
|     return "placeholder"; |  | ||||||
|  |  | ||||||
|   if (var == "POINTS_FAC_1") |  | ||||||
|   { |  | ||||||
|     char buff[12]; |  | ||||||
|     snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_1_timer / 3600, (PersistenceData.faction_1_timer / 60) % 60, PersistenceData.faction_1_timer % 60); |  | ||||||
|     return String(buff); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (var == "POINTS_FAC_2") |  | ||||||
|   { |  | ||||||
|     char buff[12]; |  | ||||||
|     snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_2_timer / 3600, (PersistenceData.faction_2_timer / 60) % 60, PersistenceData.faction_2_timer % 60); |  | ||||||
|     return String(buff); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (var == "POINTS_FAC_3") |  | ||||||
|   { |  | ||||||
|     char buff[12]; |  | ||||||
|     snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_3_timer / 3600, (PersistenceData.faction_3_timer / 60) % 60, PersistenceData.faction_3_timer % 60); |  | ||||||
|     return String(buff); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (var == "ACTIVE_FACTION") |  | ||||||
|     return String(PersistenceData.activeFaction); |  | ||||||
|  |  | ||||||
|   if (var == "FACTION_1_ACTIVE") |  | ||||||
|     return String(PersistenceData.activeFaction == FACTION_1 ? "bg-primary" : "bg-secondary"); |  | ||||||
|   if (var == "FACTION_2_ACTIVE") |  | ||||||
|     return String(PersistenceData.activeFaction == FACTION_2 ? "bg-primary" : "bg-secondary"); |  | ||||||
|   if (var == "FACTION_3_ACTIVE") |  | ||||||
|     return String(PersistenceData.activeFaction == FACTION_3 ? "bg-primary" : "bg-secondary"); |  | ||||||
|  |  | ||||||
|   if (var == "NAME_FAC_1") |  | ||||||
|     return String(ConfigData.Faction_1_Name); |  | ||||||
|  |  | ||||||
|   if (var == "NAME_FAC_2") |  | ||||||
|     return String(ConfigData.Faction_2_Name); |  | ||||||
|  |  | ||||||
|   if (var == "NAME_FAC_3") |  | ||||||
|     return String(ConfigData.Faction_3_Name); |  | ||||||
|  |  | ||||||
|   if (var == "BATTERY_SELECT_OPTIONS") |  | ||||||
|   { |  | ||||||
|     String temp; |  | ||||||
|     for (uint32_t i = 0; i < BatteryString_Elements; i++) |  | ||||||
|     { |  | ||||||
|       String selected = ConfigData.batteryType == i ? " selected " : ""; |  | ||||||
|       temp = temp + "<option value=\"" + i + "\"" + selected + ">" + BatteryString[i] + "</option>"; |  | ||||||
|     } |  | ||||||
|     return temp; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (var == "FACTIONREBOOT_CHECKED") |  | ||||||
|     return String(ConfigData.active_faction_on_reboot == true ? "checked" : ""); |  | ||||||
|  |  | ||||||
|   if (var == "FACTION_RECOVERY") |  | ||||||
|     return String(ConfigData.active_faction_on_reboot); |  | ||||||
|  |  | ||||||
|   return String(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Webserver_Callback(AsyncWebServerRequest *request) |  | ||||||
| { |  | ||||||
|   request->send(LittleFS, "/index.htm", "text/html", false, processor); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void WebserverPOST_Callback(AsyncWebServerRequest *request) |  | ||||||
| { |  | ||||||
|   request->send(LittleFS, "/post.htm", "text/html", false, processor); |  | ||||||
|  |  | ||||||
|   Debug_pushMessage("POST:\n"); |  | ||||||
|   int paramsNr = request->params(); |  | ||||||
|   for (int i = 0; i < paramsNr; i++) |  | ||||||
|   { |  | ||||||
|     AsyncWebParameter *p = request->getParam(i); |  | ||||||
|     Debug_pushMessage("%s : %s\n", p->name().c_str(), p->value().c_str()); |  | ||||||
|  |  | ||||||
|     // begin: POST Form Maintenance |  | ||||||
|     if (p->name() == "reset_ee_btn") |  | ||||||
|     { |  | ||||||
|       if (request->hasParam("reset_ee_pds", true)) |  | ||||||
|       { |  | ||||||
|         AsyncWebParameter *param = request->getParam("reset_ee_pds", true); |  | ||||||
|         if (param->value() == "on") |  | ||||||
|           globals.requestEEAction = globals.requestEEAction == EE_CFG_FORMAT ? EE_FORMAT_ALL : EE_PDS_FORMAT; |  | ||||||
|       } |  | ||||||
|       if (request->hasParam("reset_ee_cfg", true)) |  | ||||||
|       { |  | ||||||
|         AsyncWebParameter *param = request->getParam("reset_ee_cfg", true); |  | ||||||
|         if (param->value() == "on") |  | ||||||
|           globals.requestEEAction = globals.requestEEAction == EE_PDS_FORMAT ? EE_FORMAT_ALL : EE_CFG_FORMAT; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     if (p->name() == "reboot") |  | ||||||
|     { |  | ||||||
|       globals.systemStatus = sysStat_Shutdown; |  | ||||||
|     } |  | ||||||
|     if (p->name() == "resetpoints") |  | ||||||
|     { |  | ||||||
|       PersistenceData.faction_1_timer = 0; |  | ||||||
|       PersistenceData.faction_2_timer = 0; |  | ||||||
|       PersistenceData.faction_3_timer = 0; |  | ||||||
|       PersistenceData.activeFaction = NONE; |  | ||||||
|       globals.requestEEAction == EE_PDS_SAVE; |  | ||||||
|     } |  | ||||||
|     // end: POST Form Maintenance |  | ||||||
|  |  | ||||||
|     // begin: POST Form Settings |  | ||||||
|     if (p->name() == "battery_select") |  | ||||||
|     { |  | ||||||
|       batteryType_t temp = (batteryType_t)p->value().toInt(); |  | ||||||
|       ConfigData.batteryType = temp; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (request->hasParam("factionreboot_cont", true)) |  | ||||||
|     { |  | ||||||
|       AsyncWebParameter *param = request->getParam("factionreboot_cont", true); |  | ||||||
|       if (param->value() == "on") |  | ||||||
|         ConfigData.active_faction_on_reboot = true; |  | ||||||
|     } |  | ||||||
|     else |  | ||||||
|     { |  | ||||||
|       ConfigData.active_faction_on_reboot = false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (p->name() == "faction_1_name") |  | ||||||
|     { |  | ||||||
|       strncpy(ConfigData.Faction_1_Name, p->value().c_str(), sizeof(ConfigData.Faction_1_Name)); |  | ||||||
|     } |  | ||||||
|     if (p->name() == "faction_2_name") |  | ||||||
|     { |  | ||||||
|       strncpy(ConfigData.Faction_2_Name, p->value().c_str(), sizeof(ConfigData.Faction_2_Name)); |  | ||||||
|     } |  | ||||||
|     if (p->name() == "faction_3_name") |  | ||||||
|     { |  | ||||||
|       strncpy(ConfigData.Faction_3_Name, p->value().c_str(), sizeof(ConfigData.Faction_3_Name)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (p->name() == "settingssave") |  | ||||||
|       globals.requestEEAction = EE_CFG_SAVE; |  | ||||||
|     // end: POST Form Settings |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Callback function for handling HTTP 404 (Not Found) errors on the web server. | ||||||
|  |  * | ||||||
|  |  * This function is invoked when an HTTP request results in a 404 error (Not Found). It sends | ||||||
|  |  * a simple "Not found" text response with an HTTP status code of 404. | ||||||
|  |  * | ||||||
|  |  * @param request Pointer to the AsyncWebServerRequest object representing the HTTP request. | ||||||
|  |  */ | ||||||
| void WebserverNotFound_Callback(AsyncWebServerRequest *request) | void WebserverNotFound_Callback(AsyncWebServerRequest *request) | ||||||
| { | { | ||||||
|   request->send(404, "text/html", "Not found"); |   request->send(404, "text/html", "Not found"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Reads the flash version information from a file in LittleFS. | ||||||
|  |  * | ||||||
|  |  * This function reads the flash version information stored in a file named "version" in the | ||||||
|  |  * LittleFS filesystem. It opens the file, reads the content until a carriage return ('\r') is | ||||||
|  |  * encountered, and stores the result in the provided buffer. The buffer is null-terminated. | ||||||
|  |  * | ||||||
|  |  * @param buff Pointer to the buffer where the flash version information will be stored. | ||||||
|  |  * @param buff_size Size of the buffer. | ||||||
|  |  */ | ||||||
| void GetFlashVersion(char *buff, size_t buff_size) | void GetFlashVersion(char *buff, size_t buff_size) | ||||||
| { | { | ||||||
|   File this_file = LittleFS.open("version", "r"); |   File this_file = LittleFS.open("version", "r"); | ||||||
| @@ -323,12 +176,27 @@ void GetFlashVersion(char *buff, size_t buff_size) | |||||||
|   this_file.close(); |   this_file.close(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Callback function for handling firmware updates via the web server. | ||||||
|  |  * | ||||||
|  |  * This function is invoked during the firmware update process when a new firmware file | ||||||
|  |  * is received. It handles the update process using the ESPAsyncHTTPUpdate library. The update | ||||||
|  |  * process involves checking the firmware type, initializing the update, writing data, and finalizing | ||||||
|  |  * the update. If the update is successful, it triggers a system shutdown. | ||||||
|  |  * | ||||||
|  |  * @param request   Pointer to the AsyncWebServerRequest object. | ||||||
|  |  * @param filename  The name of the file being updated. | ||||||
|  |  * @param index     The index of the file being updated. | ||||||
|  |  * @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 WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) | void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) | ||||||
| { | { | ||||||
|  |  | ||||||
|   if (!index) |   if (!index) | ||||||
|   { |   { | ||||||
|     Debug_pushMessage("Update"); |     Debug_pushMessage("Update\n"); | ||||||
|     size_t content_len = request->contentLength(); |     size_t content_len = request->contentLength(); | ||||||
|     int cmd = (filename.indexOf(".fs") > -1) ? U_FS : U_FLASH; |     int cmd = (filename.indexOf(".fs") > -1) ? U_FS : U_FLASH; | ||||||
|     Update.runAsync(true); |     Update.runAsync(true); | ||||||
| @@ -365,6 +233,21 @@ void WebserverFirmwareUpdate_Callback(AsyncWebServerRequest *request, const Stri | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Callback function for handling EEPROM restore via the web server. | ||||||
|  |  * | ||||||
|  |  * This function is invoked during the EEPROM restore process when a new EEPROM file | ||||||
|  |  * is received. It handles the restore process by reading the data from the received file, | ||||||
|  |  * 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) | void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &filename, size_t index, uint8_t *data, size_t len, bool final) | ||||||
| { | { | ||||||
|   bool ee_done = false; |   bool ee_done = false; | ||||||
| @@ -381,7 +264,7 @@ void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &f | |||||||
|       buffer = (char *)malloc(1536); |       buffer = (char *)malloc(1536); | ||||||
|       read_ptr = 0; |       read_ptr = 0; | ||||||
|       if (buffer == NULL) |       if (buffer == NULL) | ||||||
|         Debug_pushMessage("malloc() failed for EEPROM-Restore"); |         Debug_pushMessage("malloc() failed for EEPROM-Restore\n"); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -396,8 +279,8 @@ void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &f | |||||||
|     if (buffer != NULL) |     if (buffer != NULL) | ||||||
|     { |     { | ||||||
|       Serial.print(buffer); |       Serial.print(buffer); | ||||||
|       StaticJsonDocument<1536> doc; |       JsonDocument json; | ||||||
|       error = deserializeJson(doc, 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()); | ||||||
| @@ -405,18 +288,22 @@ void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &f | |||||||
|       else |       else | ||||||
|       { |       { | ||||||
|  |  | ||||||
|         ConfigData.batteryType = (batteryType_t)doc["config"]["batteryType"].as<uint32_t>(); |         ConfigData.batteryType = (batteryType_t)json["config"]["batteryType"].as<int>(); | ||||||
|         ConfigData.EEPROM_Version = doc["config"]["EEPROM_Version"].as<uint32_t>(); |         ConfigData.active_faction_on_reboot = json["config"]["active_faction_on_reboot"].as<bool>(); | ||||||
|         strncpy(ConfigData.Faction_1_Name, doc["config"]["Faction_1_Name"].as<String>().c_str(), sizeof(ConfigData.Faction_1_Name)); |         strncpy(ConfigData.Faction_1_Name, json["config"]["Faction_1_Name"].as<const char *>(), sizeof(ConfigData.Faction_1_Name)); | ||||||
|         strncpy(ConfigData.Faction_2_Name, doc["config"]["Faction_2_Name"].as<String>().c_str(), sizeof(ConfigData.Faction_2_Name)); |         strncpy(ConfigData.Faction_2_Name, json["config"]["Faction_2_Name"].as<const char *>(), sizeof(ConfigData.Faction_2_Name)); | ||||||
|         strncpy(ConfigData.Faction_3_Name, doc["config"]["Faction_3_Name"].as<String>().c_str(), sizeof(ConfigData.Faction_3_Name)); |         strncpy(ConfigData.Faction_3_Name, json["config"]["Faction_3_Name"].as<const char *>(), sizeof(ConfigData.Faction_3_Name)); | ||||||
|  |         strncpy(ConfigData.wifi_ap_ssid, json["config"]["wifi_ap_ssid"].as<const char *>(), sizeof(ConfigData.wifi_ap_ssid)); | ||||||
|  |         strncpy(ConfigData.wifi_ap_password, json["config"]["wifi_ap_password"].as<const char *>(), sizeof(ConfigData.wifi_ap_password)); | ||||||
|  |         strncpy(ConfigData.wifi_client_ssid, json["config"]["wifi_client_ssid"].as<const char *>(), sizeof(ConfigData.wifi_client_ssid)); | ||||||
|  |         strncpy(ConfigData.wifi_client_password, json["config"]["wifi_client_password"].as<const char *>(), sizeof(ConfigData.wifi_client_password)); | ||||||
|  |  | ||||||
|         PersistenceData.writeCycleCounter = doc["persis"]["writeCycleCounter"].as<uint16_t>(); |         PersistenceData.writeCycleCounter = json["persis"]["writeCycleCounter"].as<uint16_t>(); | ||||||
|         PersistenceData.activeFaction = (factions_t)doc["persis"]["activeFaction"].as<uint32_t>(); |         PersistenceData.activeFaction = (Factions_t)json["persis"]["activeFaction"].as<int>(); | ||||||
|         PersistenceData.faction_1_timer = doc["persis"]["faction_1_timer"].as<uint32_t>(); |         PersistenceData.faction_1_timer = json["persis"]["faction_1_timer"].as<uint32_t>(); | ||||||
|         PersistenceData.faction_2_timer = doc["persis"]["faction_2_timer"].as<uint32_t>(); |         PersistenceData.faction_2_timer = json["persis"]["faction_2_timer"].as<uint32_t>(); | ||||||
|         PersistenceData.faction_3_timer = doc["persis"]["faction_3_timer"].as<uint32_t>(); |         PersistenceData.faction_3_timer = json["persis"]["faction_3_timer"].as<uint32_t>(); | ||||||
|         PersistenceData.checksum = doc["persis"]["checksum"].as<uint32_t>(); |         PersistenceData.checksum = json["persis"]["checksum"].as<uint32_t>(); | ||||||
|  |  | ||||||
|         ee_done = true; |         ee_done = true; | ||||||
|       } |       } | ||||||
| @@ -431,55 +318,44 @@ void WebserverEERestore_Callback(AsyncWebServerRequest *request, const String &f | |||||||
|  |  | ||||||
|     if (ee_done) |     if (ee_done) | ||||||
|     { |     { | ||||||
|       Debug_pushMessage("Update complete"); |       Debug_pushMessage("Update complete\n"); | ||||||
|       globals.systemStatus = sysStat_Shutdown; |       globals.systemStatus = sysStat_Shutdown; | ||||||
|     } |     } | ||||||
|     else |  | ||||||
|     { |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Callback function for handling EEPROM JSON request via the web server. | ||||||
|  |  * | ||||||
|  |  * This function is invoked when a request for EEPROM JSON data is received. It constructs a JSON | ||||||
|  |  * response containing information about the firmware, configuration, and persistence data. | ||||||
|  |  * | ||||||
|  |  * @param request Pointer to the AsyncWebServerRequest object. | ||||||
|  |  */ | ||||||
| void WebServerEEJSON_Callback(AsyncWebServerRequest *request) | void WebServerEEJSON_Callback(AsyncWebServerRequest *request) | ||||||
| { | { | ||||||
|   AsyncResponseStream *response = request->beginResponseStream("application/json"); |   AsyncResponseStream *response = request->beginResponseStream("application/json"); | ||||||
|   DynamicJsonDocument json(1024); |   JsonDocument json; | ||||||
|   JsonObject fwinfo = json.createNestedObject("info"); |   JsonObject info = json["info"].to<JsonObject>(); | ||||||
|  |  | ||||||
|   char buffer[16]; |   char buffer[16]; | ||||||
|  |  | ||||||
|   fwinfo["DeviceName"] = globals.DeviceName; |   info["DeviceName"] = globals.DeviceName; | ||||||
|   sprintf(buffer, "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor); |   sprintf(buffer, "%d.%02d", constants.Required_Flash_Version_major, constants.Required_Flash_Version_minor); | ||||||
|   fwinfo["FW-Version"] = buffer; |   info["FW-Version"] = buffer; | ||||||
|   fwinfo["FS-Version"] = globals.FlashVersion; |   info["FS-Version"] = globals.FlashVersion; | ||||||
|   snprintf_P(buffer, sizeof(buffer), "%s", constants.GitHash); |   snprintf_P(buffer, sizeof(buffer), "%s", constants.GitHash); | ||||||
|   fwinfo["Git-Hash"] = buffer; |   info["Git-Hash"] = buffer; | ||||||
|  |  | ||||||
|   JsonObject config = json.createNestedObject("config"); |   JsonObject config = json["config"].to<JsonObject>(); | ||||||
|  |   generateJsonObject_ConfigData(config); | ||||||
|   config["EEPROM_Version"] = ConfigData.EEPROM_Version; |   JsonObject persis = json["persis"].to<JsonObject>(); | ||||||
|   config["batteryType"] = ConfigData.batteryType; |   generateJsonObject_PersistenceData(persis); | ||||||
|   config["Faction_1_Name"] = ConfigData.Faction_1_Name; |  | ||||||
|   config["Faction_2_Name"] = ConfigData.Faction_2_Name; |  | ||||||
|   config["Faction_3_Name"] = ConfigData.Faction_3_Name; |  | ||||||
|   sprintf(buffer, "0x%08X", ConfigData.checksum); |  | ||||||
|   config["checksum"] = buffer; |  | ||||||
|  |  | ||||||
|   JsonObject eepart = json.createNestedObject("eepart"); |  | ||||||
|  |  | ||||||
|  |   JsonObject eepart = json["eepart"].to<JsonObject>(); | ||||||
|   sprintf(buffer, "0x%04X", globals.eePersistanceAdress); |   sprintf(buffer, "0x%04X", globals.eePersistanceAdress); | ||||||
|   eepart["PersistanceAddress"] = buffer; |   eepart["PersistanceAddress"] = buffer; | ||||||
|  |  | ||||||
|   JsonObject persis = json.createNestedObject("persis"); |  | ||||||
|  |  | ||||||
|   persis["writeCycleCounter"] = PersistenceData.writeCycleCounter; |  | ||||||
|   persis["activeFaction"] = PersistenceData.activeFaction; |  | ||||||
|   persis["faction_1_timer"] = PersistenceData.faction_1_timer; |  | ||||||
|   persis["faction_2_timer"] = PersistenceData.faction_2_timer; |  | ||||||
|   persis["faction_3_timer"] = PersistenceData.faction_3_timer; |  | ||||||
|   sprintf(buffer, "0x%08X", PersistenceData.checksum); |  | ||||||
|   persis["checksum"] = buffer; |  | ||||||
|  |  | ||||||
|   serializeJsonPretty(json, *response); |   serializeJsonPretty(json, *response); | ||||||
|  |  | ||||||
|   response->addHeader("Content-disposition", "attachment; filename=backup.ee.json"); |   response->addHeader("Content-disposition", "attachment; filename=backup.ee.json"); | ||||||
| @@ -487,12 +363,28 @@ void WebServerEEJSON_Callback(AsyncWebServerRequest *request) | |||||||
|   request->send(response); |   request->send(response); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Callback function for handling WebSocket events. | ||||||
|  |  * | ||||||
|  |  * This function is invoked when events occur in the WebSocket communication, such as client connection, | ||||||
|  |  * disconnection, reception of data, and others. It dispatches the events to the appropriate handlers. | ||||||
|  |  * | ||||||
|  |  * @param server Pointer to the AsyncWebSocket object. | ||||||
|  |  * @param client Pointer to the AsyncWebSocketClient object representing the WebSocket client. | ||||||
|  |  * @param type Type of WebSocket event. | ||||||
|  |  * @param arg Event-specific argument. | ||||||
|  |  * @param data Pointer to the received data (if applicable). | ||||||
|  |  * @param len Length of the received data. | ||||||
|  |  */ | ||||||
| void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) | void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) | ||||||
| { | { | ||||||
|   switch (type) |   switch (type) | ||||||
|   { |   { | ||||||
|   case WS_EVT_CONNECT: |   case WS_EVT_CONNECT: | ||||||
|     Debug_pushMessage("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); |     Debug_pushMessage("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str()); | ||||||
|  |     Websocket_RefreshClientData_Status(client->id(), true); | ||||||
|  |     Websocket_RefreshClientData_Static(client->id(), true); | ||||||
|  |     Websocket_RefreshClientData_DTCs(client->id()); | ||||||
|     break; |     break; | ||||||
|   case WS_EVT_DISCONNECT: |   case WS_EVT_DISCONNECT: | ||||||
|     Debug_pushMessage("WebSocket client #%u disconnected\n", client->id()); |     Debug_pushMessage("WebSocket client #%u disconnected\n", client->id()); | ||||||
| @@ -506,31 +398,368 @@ void WebsocketEvent_Callback(AsyncWebSocket *server, AsyncWebSocketClient *clien | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Handles WebSocket messages received from clients. | ||||||
|  |  * | ||||||
|  |  * This function processes WebSocket messages, such as starting or stopping debugging, | ||||||
|  |  * and provides appropriate responses. | ||||||
|  |  * | ||||||
|  |  * @param arg Pointer to the WebSocket frame information. | ||||||
|  |  * @param data Pointer to the received data. | ||||||
|  |  * @param len Length of the received data. | ||||||
|  |  */ | ||||||
| void Websocket_HandleMessage(void *arg, uint8_t *data, size_t len) | void Websocket_HandleMessage(void *arg, uint8_t *data, size_t len) | ||||||
| { | { | ||||||
|   AwsFrameInfo *info = (AwsFrameInfo *)arg; |   AwsFrameInfo *info = (AwsFrameInfo *)arg; | ||||||
|   if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) |   if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT) | ||||||
|   { |   { | ||||||
|     data[len] = 0; |     data[len] = 0; | ||||||
|  |     Debug_pushMessage("Websocket-Message (len: %d): %s\n", len, (char *)data); | ||||||
|  |  | ||||||
|     Debug_pushMessage("Got WebSocket Message: %s \n", (char *)data); |     if (strncmp((char *)data, "btn-", strlen("btn-")) == 0) | ||||||
|  |  | ||||||
|     if (strcmp((char *)data, "start") == 0) |  | ||||||
|     { |     { | ||||||
|       SetDebugportStatus(dbg_Webui, enabled); |       Websocket_HandleButtons(data + strlen("btn-")); | ||||||
|     } |     } | ||||||
|     else if (strcmp((char *)data, "stop") == 0) |     else if (strncmp((char *)data, "set-", strlen("set-")) == 0) | ||||||
|     { |     { | ||||||
|       SetDebugportStatus(dbg_Webui, disabled); |       Websocket_HandleSettings(data + strlen("set-")); | ||||||
|     } |     } | ||||||
|     else if (strcmp((char *)data, "foo") == 0) |     else | ||||||
|     { |     { | ||||||
|       Debug_pushMessage("Got WebSocket Message 'foo' from client\n"); |       Debug_pushMessage("Got unknown Websocket-Message '%s' from client\n", (char *)data); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Handle button commands received via WebSocket. | ||||||
|  |  * | ||||||
|  |  * This function parses a WebSocket string representing button commands, extracts | ||||||
|  |  * the identifier and value components, and performs corresponding actions based on | ||||||
|  |  * the received commands. | ||||||
|  |  * | ||||||
|  |  * @param data The WebSocket data containing button commands. | ||||||
|  |  */ | ||||||
|  | void Websocket_HandleButtons(uint8_t *data) | ||||||
|  | { | ||||||
|  |   char identifier[32]; | ||||||
|  |   char value[32]; | ||||||
|  |  | ||||||
|  |   parseWebsocketString((char *)data, identifier, sizeof(identifier), value, sizeof(value)); | ||||||
|  |  | ||||||
|  |   if (strcmp(identifier, "debugstart") == 0) | ||||||
|  |   { | ||||||
|  |     SetDebugportStatus(dbg_Webui, enabled); | ||||||
|  |   } | ||||||
|  |   else if (strcmp(identifier, "debugstop") == 0) | ||||||
|  |   { | ||||||
|  |     SetDebugportStatus(dbg_Webui, disabled); | ||||||
|  |   } | ||||||
|  |   else if (strcmp(identifier, "settingssave") == 0) | ||||||
|  |   { | ||||||
|  |     globals.requestEEAction = EE_CFG_SAVE; | ||||||
|  |   } | ||||||
|  |   else if (strcmp(identifier, "reboot") == 0) | ||||||
|  |   { | ||||||
|  |     globals.systemStatus = sysStat_Shutdown; | ||||||
|  |   } | ||||||
|  |   else | ||||||
|  |   { | ||||||
|  |     Debug_pushMessage("Got unknown Button-id '%s' from ws-client\n", identifier); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Handle settings commands received via WebSocket. | ||||||
|  |  * | ||||||
|  |  * This function parses a WebSocket string representing settings commands, extracts | ||||||
|  |  * the identifier and value components, and updates the system settings accordingly. | ||||||
|  |  * | ||||||
|  |  * @param data The WebSocket data containing settings commands. | ||||||
|  |  */ | ||||||
|  | void Websocket_HandleSettings(uint8_t *data) | ||||||
|  | { | ||||||
|  |   char identifier[32]; | ||||||
|  |   char value[63]; | ||||||
|  |  | ||||||
|  |   parseWebsocketString((char *)data, identifier, sizeof(identifier), value, sizeof(value)); | ||||||
|  |  | ||||||
|  |   if (strcmp(identifier, "wifi-ssid") == 0) | ||||||
|  |   { | ||||||
|  |     strncpy(ConfigData.wifi_client_ssid, value, sizeof(ConfigData.wifi_client_ssid)); | ||||||
|  |   } | ||||||
|  |   else if (strcmp(identifier, "wifi-password") == 0) | ||||||
|  |   { | ||||||
|  |     strncpy(ConfigData.wifi_client_password, value, sizeof(ConfigData.wifi_client_password)); | ||||||
|  |   } | ||||||
|  |   else | ||||||
|  |   { | ||||||
|  |     Debug_pushMessage("Got unknown Settings-id and value '%s' from ws-client\n", identifier); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Pushes live debug messages to all WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * This function sends a live debug message to all connected WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * @param Message The debug message to be sent. | ||||||
|  |  */ | ||||||
| void Websocket_PushLiveDebug(String Message) | void Websocket_PushLiveDebug(String Message) | ||||||
| { | { | ||||||
|   webSocket.textAll(Message + "\n"); |   webSocket.textAll("DEBUG:" + Message); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Refreshes client data related to Diagnostic Trouble Codes (DTCs) on WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * This function constructs a DTC-related string and sends it to a specific WebSocket client or | ||||||
|  |  * broadcasts it to all connected WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * @param client_id The ID of the WebSocket client to which the data should be sent. If 0, the data | ||||||
|  |  * will be broadcasted to all connected clients. | ||||||
|  |  */ | ||||||
|  | void Websocket_RefreshClientData_DTCs(uint32_t client_id) | ||||||
|  | { | ||||||
|  |   String temp = "DTC:"; | ||||||
|  |  | ||||||
|  |   // Build DTC-String | ||||||
|  |   if (globals.hasDTC != true) | ||||||
|  |   { | ||||||
|  |     temp.concat(String(DTC_NO_DTC) + ";"); | ||||||
|  |   } | ||||||
|  |   else | ||||||
|  |   { | ||||||
|  |     for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++) | ||||||
|  |     { | ||||||
|  |       if (DTCStorage[i].Number < DTC_LAST_DTC) | ||||||
|  |       { | ||||||
|  |         temp.concat(String(DTCStorage[i].timestamp) + ","); | ||||||
|  |         temp.concat(String(DTCStorage[i].Number) + ","); | ||||||
|  |         temp.concat(String(getSeverityForDTC(DTCStorage[i].Number)) + ","); | ||||||
|  |         temp.concat(String(DTCStorage[i].active) + ","); | ||||||
|  |         temp.concat(String(DTCStorage[i].debugVal) + ";"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if (client_id > 0) | ||||||
|  |   { | ||||||
|  |     webSocket.text(client_id, temp); | ||||||
|  |   } | ||||||
|  |   else | ||||||
|  |   { | ||||||
|  |     webSocket.textAll(temp); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Refreshes client data related to system status and relevant parameters on WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * This function constructs a status-related string and sends it to a specific WebSocket client or | ||||||
|  |  * broadcasts it to all connected WebSocket clients. It also sends a mapping of the status parameters. | ||||||
|  |  * | ||||||
|  |  * @param client_id The ID of the WebSocket client to which the data should be sent. If 0, the data | ||||||
|  |  * will be broadcasted to all connected clients. | ||||||
|  |  * @param send_mapping Flag indicating whether to send the parameter mapping to the client(s). | ||||||
|  |  */ | ||||||
|  | void Websocket_RefreshClientData_Status(uint32_t client_id, bool send_mapping) | ||||||
|  | { | ||||||
|  |  | ||||||
|  |   if (send_mapping) | ||||||
|  |   { | ||||||
|  |     const char mapping[] = "MAPPING_STATUS:" | ||||||
|  |                            "systemstatus;" | ||||||
|  |                            "activefaction;" | ||||||
|  |                            "time_faction1;" | ||||||
|  |                            "time_faction2;" | ||||||
|  |                            "time_faction3;"; | ||||||
|  |  | ||||||
|  |     if (client_id > 0) | ||||||
|  |       webSocket.text(client_id, mapping); | ||||||
|  |     else | ||||||
|  |       webSocket.textAll(mapping); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   String temp = "STATUS:"; | ||||||
|  |  | ||||||
|  |   temp.concat(String(globals.systemStatustxt) + ";"); | ||||||
|  |   temp.concat(String(PersistenceData.activeFaction) + ";"); | ||||||
|  |   temp.concat(String(PersistenceData.faction_1_timer) + ";"); | ||||||
|  |   temp.concat(String(PersistenceData.faction_2_timer) + ";"); | ||||||
|  |   temp.concat(String(PersistenceData.faction_3_timer) + ";"); | ||||||
|  |  | ||||||
|  |   if (client_id > 0) | ||||||
|  |   { | ||||||
|  |     webSocket.text(client_id, temp); | ||||||
|  |   } | ||||||
|  |   else | ||||||
|  |   { | ||||||
|  |     webSocket.textAll(temp); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Refreshes client data related to static configuration parameters on WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * This function constructs a static configuration-related string and sends it to a specific WebSocket client or | ||||||
|  |  * broadcasts it to all connected WebSocket clients. It also sends a mapping of the static configuration parameters. | ||||||
|  |  * | ||||||
|  |  * @param client_id The ID of the WebSocket client to which the data should be sent. If 0, the data | ||||||
|  |  * will be broadcasted to all connected clients. | ||||||
|  |  * @param send_mapping Flag indicating whether to send the parameter mapping to the client(s). | ||||||
|  |  */ | ||||||
|  | void Websocket_RefreshClientData_Static(uint32_t client_id, bool send_mapping) | ||||||
|  | { | ||||||
|  |  | ||||||
|  |   if (send_mapping) | ||||||
|  |   { | ||||||
|  |     const char mapping[] = "MAPPING_STATIC:" | ||||||
|  |                            "active_faction_on_reboot;" | ||||||
|  |                            "batteryType;" | ||||||
|  |                            "name_faction1;" | ||||||
|  |                            "name_faction2;" | ||||||
|  |                            "name_faction3;" | ||||||
|  |                            "wifi-ssid;" | ||||||
|  |                            "wifi-pass;"; | ||||||
|  |  | ||||||
|  |     if (client_id > 0) | ||||||
|  |       webSocket.text(client_id, mapping); | ||||||
|  |     else | ||||||
|  |       webSocket.textAll(mapping); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   String temp = "STATIC:"; | ||||||
|  |  | ||||||
|  |   temp.concat(String(ConfigData.active_faction_on_reboot) + ";"); | ||||||
|  |   temp.concat(String(ConfigData.batteryType) + ";"); | ||||||
|  |   temp.concat(String(ConfigData.Faction_1_Name) + ";"); | ||||||
|  |   temp.concat(String(ConfigData.Faction_2_Name) + ";"); | ||||||
|  |   temp.concat(String(ConfigData.Faction_3_Name) + ";"); | ||||||
|  |   temp.concat(String(ConfigData.wifi_client_ssid) + ";"); | ||||||
|  |   temp.concat(String(ConfigData.wifi_client_password) + ";"); | ||||||
|  |  | ||||||
|  |   if (client_id > 0) | ||||||
|  |   { | ||||||
|  |     webSocket.text(client_id, temp); | ||||||
|  |   } | ||||||
|  |   else | ||||||
|  |   { | ||||||
|  |     webSocket.textAll(temp); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Parse a WebSocket string into identifier and value components. | ||||||
|  |  * | ||||||
|  |  * This function takes a WebSocket string, separates it into identifier and value | ||||||
|  |  * components using the ":" delimiter, and stores them in the specified buffers. | ||||||
|  |  * If no ":" is found, the entire string is considered as the value, and the | ||||||
|  |  * identifier buffer is set to an empty string. | ||||||
|  |  * | ||||||
|  |  * @param data The WebSocket string to parse. | ||||||
|  |  * @param identifierBuffer The buffer to store the identifier component. | ||||||
|  |  * @param identifierBufferSize The size of the identifier buffer. | ||||||
|  |  * @param valueBuffer The buffer to store the value component. | ||||||
|  |  * @param valueBufferSize The size of the value buffer. | ||||||
|  |  */ | ||||||
|  | void parseWebsocketString(char *data, char *identifierBuffer, size_t identifierBufferSize, | ||||||
|  |                           char *valueBuffer, size_t valueBufferSize) | ||||||
|  | { | ||||||
|  |   // Zerlegen des Strings anhand des Trennzeichens ":" | ||||||
|  |   char *token = strtok(data, ":"); | ||||||
|  |  | ||||||
|  |   // Falls der erste Teil des Strings vorhanden ist | ||||||
|  |   if (token != NULL) | ||||||
|  |   { | ||||||
|  |     // Kopieren des ersten Teils in den Buffer für Identifier | ||||||
|  |     strncpy(identifierBuffer, token, identifierBufferSize - 1); | ||||||
|  |     identifierBuffer[identifierBufferSize - 1] = '\0'; // Null-Terminierung sicherstellen | ||||||
|  |  | ||||||
|  |     // Weitere Aufrufe von strtok, um den nächsten Teil zu erhalten | ||||||
|  |     token = strtok(NULL, ":"); | ||||||
|  |  | ||||||
|  |     // Falls der zweite Teil des Strings vorhanden ist | ||||||
|  |     if (token != NULL) | ||||||
|  |     { | ||||||
|  |       // Kopieren des zweiten Teils in den Buffer für Value | ||||||
|  |       strncpy(valueBuffer, token, valueBufferSize - 1); | ||||||
|  |       valueBuffer[valueBufferSize - 1] = '\0'; // Null-Terminierung sicherstellen | ||||||
|  |     } | ||||||
|  |     else | ||||||
|  |     { | ||||||
|  |       // Kein zweiter Teil vorhanden, setzen Sie den Buffer für Value auf leer | ||||||
|  |       valueBuffer[0] = '\0'; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   else | ||||||
|  |   { | ||||||
|  |     // Der erste Teil des Strings fehlt, setzen Sie den Buffer für Identifier auf leer | ||||||
|  |     identifierBuffer[0] = '\0'; | ||||||
|  |  | ||||||
|  |     // Der gesamte String wird als Value betrachtet | ||||||
|  |     strncpy(valueBuffer, data, valueBufferSize - 1); | ||||||
|  |     valueBuffer[valueBufferSize - 1] = '\0'; // Null-Terminierung sicherstellen | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Find the index of a string in an array. | ||||||
|  |  * | ||||||
|  |  * This function searches for the given string in the provided array and returns | ||||||
|  |  * the index of the first occurrence. If the string is not found, it returns -1. | ||||||
|  |  * | ||||||
|  |  * @param searchString The string to search for in the array. | ||||||
|  |  * @param array The array of strings to search within. | ||||||
|  |  * @param arraySize The size of the array. | ||||||
|  |  * | ||||||
|  |  * @return The index of the first occurrence of the string in the array, | ||||||
|  |  *         or -1 if the string is not found. | ||||||
|  |  */ | ||||||
|  | int findIndexByString(const char *searchString, const char *const *array, int arraySize) | ||||||
|  | { | ||||||
|  |   // Durchlaufe das Array und vergleiche jeden String | ||||||
|  |   for (int i = 0; i < arraySize; ++i) | ||||||
|  |   { | ||||||
|  |     if (strcmp(array[i], searchString) == 0) | ||||||
|  |     { | ||||||
|  |       // String gefunden, gib den Index zurück | ||||||
|  |       return i; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   // String nicht gefunden, gib -1 zurück | ||||||
|  |   return -1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @brief Pushes a notification to all WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * This function sends a live debug message to all connected WebSocket clients. | ||||||
|  |  * | ||||||
|  |  * @param Message The debug message to be sent. | ||||||
|  |  * @param type The type of notification (info, success, warning, error). | ||||||
|  |  *             - Use NotificationType_t::info for informational messages. | ||||||
|  |  *             - Use NotificationType_t::success for successful operation messages. | ||||||
|  |  *             - Use NotificationType_t::warning for warning messages. | ||||||
|  |  *             - Use NotificationType_t::error for error messages. | ||||||
|  |  */ | ||||||
|  | void Websocket_PushNotification(String Message, NotificationType_t type) | ||||||
|  | { | ||||||
|  |   String typeString = ""; | ||||||
|  |   switch (type) | ||||||
|  |   { | ||||||
|  |   case info: | ||||||
|  |     typeString = "info"; | ||||||
|  |     break; | ||||||
|  |   case success: | ||||||
|  |     typeString = "success"; | ||||||
|  |     break; | ||||||
|  |   case warning: | ||||||
|  |     typeString = "warning"; | ||||||
|  |     break; | ||||||
|  |   case error: | ||||||
|  |     typeString = "danger"; | ||||||
|  |     break; | ||||||
|  |   } | ||||||
|  |   webSocket.textAll("NOTIFY:" + typeString + ";" + Message); | ||||||
|  |   Debug_pushMessage("Sending Notification to WebUI: %s\n", typeString); | ||||||
| } | } | ||||||
		Reference in New Issue
	
	Block a user