/** * @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); // Configuration and persistence data structures configData_t ConfigData; persistenceData_t PersistenceData; // EEPROM version identifier const uint16_t eeVersion = EEPROM_STRUCTURE_REVISION; // 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() { ConfigData = ConfigData_defaults; PersistenceData = {0}; ee.begin(); checkEEPROMavailable(); } /** * @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() { switch (globals.requestEEAction) { case EE_CFG_SAVE: StoreConfig_EEPROM(); globals.requestEEAction = EE_IDLE; Debug_pushMessage("Stored EEPROM CFG\n"); break; case EE_CFG_LOAD: GetConfig_EEPROM(); globals.requestEEAction = EE_IDLE; Debug_pushMessage("Loaded EEPROM CFG\n"); break; case EE_CFG_FORMAT: FormatConfig_EEPROM(); globals.requestEEAction = EE_IDLE; GetConfig_EEPROM(); Debug_pushMessage("Formatted EEPROM CFG\n"); break; case EE_PDS_SAVE: StorePersistence_EEPROM(); globals.requestEEAction = EE_IDLE; Debug_pushMessage("Stored EEPROM PDS\n"); break; case EE_PDS_LOAD: GetPersistence_EEPROM(); globals.requestEEAction = EE_IDLE; Debug_pushMessage("Loaded EEPROM PDS\n"); break; case EE_PDS_FORMAT: FormatPersistence_EEPROM(); globals.requestEEAction = EE_IDLE; GetPersistence_EEPROM(); Debug_pushMessage("Formatted EEPROM PDS\n"); break; case EE_FORMAT_ALL: FormatConfig_EEPROM(); FormatPersistence_EEPROM(); GetConfig_EEPROM(); GetPersistence_EEPROM(); globals.requestEEAction = EE_IDLE; Debug_pushMessage("Formatted EEPROM ALL\n"); break; case EE_ALL_SAVE: StorePersistence_EEPROM(); StoreConfig_EEPROM(); globals.requestEEAction = EE_IDLE; Debug_pushMessage("Stored EEPROM ALL\n"); break; case EE_IDLE: default: globals.requestEEAction = EE_IDLE; } } /** * @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() { // Berechnung der Prüfsumme ConfigData.checksum = 0; ConfigData.checksum = Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)); // Überprüfung, ob der EEPROM verfügbar ist if (!checkEEPROMavailable()) return; // Byteweise in den EEPROM schreiben uint8_t *dataPtr = (uint8_t *)&ConfigData; for (uint16_t i = 0; i < sizeof(ConfigData); i++) { ee.writeByte(startofConfigData + i, dataPtr[i]); } // Sanity Check der Konfiguration 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() { if (!checkEEPROMavailable()) return; ee.readBlock(startofConfigData, (uint8_t *)&ConfigData, sizeof(ConfigData)); uint32_t checksum = ConfigData.checksum; ConfigData.checksum = 0; MaintainDTC(DTC_EEPROM_CFG_BAD, (Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)) != checksum)); ConfigData.checksum = checksum; uint32_t ConfigSanityCheckResult = ConfigSanityCheck(false); MaintainDTC(DTC_EEPROM_CFG_SANITY, (ConfigSanityCheckResult > 0), ConfigSanityCheckResult); } /** * @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() { if (PersistenceData.writeCycleCounter >= 0xFFF0) MovePersistencePage_EEPROM(false); else PersistenceData.writeCycleCounter++; PersistenceData.checksum = 0; PersistenceData.checksum = Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)); if (!checkEEPROMavailable()) return; // Byteweise in den EEPROM schreiben uint8_t *dataPtr = (uint8_t *)&PersistenceData; for (uint16_t i = 0; i < sizeof(PersistenceData); i++) { ee.writeByte(globals.eePersistanceAdress + i, dataPtr[i]); } } /** * @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() { if (!checkEEPROMavailable()) return; ee.readBlock(0, (uint8_t *)&globals.eePersistanceAdress, sizeof(globals.eePersistanceAdress)); // if we got the StartAdress of Persistance and it's out of Range - we Reset it and store defaults // otherwise we Read from eeprom and check if everything is correct if (globals.eePersistanceAdress < startofPersistence || globals.eePersistanceAdress > ee.getDeviceSize()) { MovePersistencePage_EEPROM(true); FormatPersistence_EEPROM(); MaintainDTC(DTC_EEPROM_PDSADRESS_BAD, true); } else { ee.readBlock(globals.eePersistanceAdress, (uint8_t *)&PersistenceData, sizeof(PersistenceData)); uint32_t checksum = PersistenceData.checksum; PersistenceData.checksum = 0; MaintainDTC(DTC_EEPROM_PDS_BAD, (Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)) != 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() { Debug_pushMessage("Formatting Config-Partition\n"); ConfigData = ConfigData_defaults; ConfigData.EEPROM_Version = eeVersion; StoreConfig_EEPROM(); } /** * @brief Formats the persistence partition in EEPROM. * * This function resets the persistence data to defaults and stores it in EEPROM. */ void FormatPersistence_EEPROM() { Debug_pushMessage("Formatting Persistance-Partition\n"); PersistenceData = {0}; // memset(&PersistenceData, 0, sizeof(PersistenceData)); StorePersistence_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) { if (!checkEEPROMavailable()) return; globals.eePersistanceAdress += sizeof(PersistenceData); PersistenceData.writeCycleCounter = 0; // Check if we reached the end of the EEPROM and start over at the beginning if ((globals.eePersistanceAdress + sizeof(PersistenceData)) > ee.getDeviceSize() || reset) { globals.eePersistanceAdress = startofPersistence; } 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) { if (data == NULL) return 0; uint32_t crc = 0xFFFFFFFF; uint32_t mask; while (len--) { crc ^= *data++; for (uint8_t k = 0; k < 8; k++) { mask = -(crc & 1); crc = (crc >> 1) ^ (0xEDB88320 & mask); } } 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) { #define BLOCK_TO_LENGTH 16 if (!checkEEPROMavailable()) return; char ascii_buf[BLOCK_TO_LENGTH + 1] = {0}; sprintf(ascii_buf, "%*s", BLOCK_TO_LENGTH, "ASCII"); // 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; 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++) { int blockpoint = memoryAddress % BLOCK_TO_LENGTH; // Print ASCII representation header for each block if (blockpoint == 0) { if (i > 0) // Ensure we don't print an empty ASCII buffer on the first iteration { ascii_buf[BLOCK_TO_LENGTH] = 0; Debug_pushMessage(" %s", ascii_buf); } Debug_pushMessage("\n0x%05X:", memoryAddress); memset(ascii_buf, ' ', BLOCK_TO_LENGTH); // Clear the ASCII buffer with spaces } // Read and print each byte uint8_t byte = ee.readByte(memoryAddress); ascii_buf[blockpoint] = (byte >= 0x20 && byte <= 0x7E) ? byte : '.'; Debug_pushMessage(" %02X", byte); memoryAddress++; } // Print remaining ASCII buffer ascii_buf[BLOCK_TO_LENGTH] = 0; Debug_pushMessage(" %s\n", ascii_buf); // Final ASCII line } /** * @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()) { // Trigger DTC for no EEPROM found MaintainDTC(DTC_NO_EEPROM_FOUND, true); return false; } // Clear DTC for no EEPROM found since it's available now MaintainDTC(DTC_NO_EEPROM_FOUND, false); // EEPROM is available 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 setting_reset_bits = 0; if (!validateWiFiString(ConfigData.wifi_ap_ssid, sizeof(ConfigData.wifi_ap_ssid))) { SET_BIT(setting_reset_bits, 1); if (autocorrect) 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; } /** * @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 (string == NULL) return false; for (size_t i = 0; i < size; i++) { char c = string[i]; if (c == '\0') { // Reached the end of the string, all characters were valid WiFi characters. 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; } } // If the loop completes without finding a null terminator, the string is invalid. return false; } /** * @brief Write sequential numbers to a portion of EEPROM. * * This function writes sequential numbers starting from 0 to a specified portion of EEPROM. * If the number reaches 255, it wraps around and starts again from 1. * * @param memoryAddress Starting address in EEPROM. * @param length Number of bytes to write. */ void writeSequentialToEEPROM(uint16_t memoryAddress, uint16_t length) { if (!checkEEPROMavailable()) return; uint8_t value = 0; for (uint16_t i = 0; i < length; i++) { ee.writeByte(memoryAddress + i, value); value = (value == 255) ? 1 : value + 1; } } /** * @brief Write 0 to a portion of EEPROM. * * This function writes 0 to a specified portion of EEPROM. * * @param memoryAddress Starting address in EEPROM. * @param length Number of bytes to write. */ void writeZeroToEEPROM(uint16_t memoryAddress, uint16_t length) { if (!checkEEPROMavailable()) return; for (uint16_t i = 0; i < length; i++) { ee.writeByte(memoryAddress + i, 0); } }