#include #include #define KEY_VALUE_SIZE 50 // #define DEFAULT_VALUES_COUNT 2 #define KEY_VALUE_DELIMITER "__" // #define MAX_2BYTE_CHARS_IN_KV_PAIRS 6 class KeyValuePairs { // Arrays for storing key-value pairs String default_key_values; String runtime_key_values; // Default values initialization string String defaults_for_initialization = "__vorwärm_temperatur __27" "__nachlauf_temperatur __18"; public: // new version of 11.08.24 KeyValuePairs() { /* Initialize LittleFS */ if (!LittleFS.begin()) { // Error condition while (true); // Endless loop } File file = LittleFS.open("/custom_key_value_pairs.txt", "r"); /* File already exists */ if (file) { /* Read and store file-content to runtime_key_values */ runtime_key_values = file.readString(); file.close(); /* File is not existing */ } else { /* Create the file with empty values */ runtime_key_values = create_custom_KV_file(); } /* Set default_key_values from defaults_for_initialization */ default_key_values = defaults_for_initialization; /* Check if key-values fit into KEY_VALUE_SIZE */ if (!checkKeyValueSections(default_key_values)) { // Error condition if any section exceeds allowed size while (true); // Endless loop } } // new version of 12.08.24 /** @class KeyValuePairs ** * @brief This class provides an interface for managing key-value pairs * stored in a LittleFS filesystem on an embedded device, such as * an ESP8266. The class allows for storing, retrieving, and updating * key-value pairs with predefined sizes and ensures data integrity * by adhering to storage constraints. * * The key-value pairs are stored in a file named `/custom_key_value_pairs.txt` * within the LittleFS filesystem. This class handles both the default * initialization of key-value pairs and runtime modifications. It also includes * safety checks to ensure that each key-value section adheres to a defined * maximum size (`KEY_VALUE_SIZE`). * * @details * - Default key-value pairs are initialized from a predefined string, * `defaults_for_initialization`. * - The runtime key-value pairs are loaded from or written to a LittleFS file. * - The class includes error handling that halts execution in case of * critical errors, such as file system initialization failure or * key-value size violations. * * Usage Example: * @code * KeyValuePairs kvPairs; * kvPairs.setValue("vorwärm_temperatur", "30"); * String value = kvPairs.getValue("vorwärm_temperatur"); * Serial.println(value); // Outputs "30" * @endcode */ String getValue(String key) { key.trim(); // Trim the key argument /* Check existence of key in default_key_values */ int trueKeyStartPos = default_key_values.indexOf(KEY_VALUE_DELIMITER + key); if (trueKeyStartPos == -1) { // Error condition if key is not found while (true); // Endless loop } String lengthOfDelimiter = KEY_VALUE_DELIMITER ; int sectionEndPos = trueKeyStartPos + (KEY_VALUE_SIZE - lengthOfDelimiter.length()) ; int p_valueStartPos = default_key_values.lastIndexOf(' ', sectionEndPos - 1) ; String value = default_key_values.substring(p_valueStartPos + 1, sectionEndPos) ; value.trim(); /* Extract value by removing the delimiter from the left */ value = value.substring(lengthOfDelimiter.length()); return value; } // new version of 11.08.24 // Method to set value // Updated version of setValue method void setValue(String key, String value) { key.trim(); value.trim(); /* Is key existing? */ if (default_key_values.indexOf(KEY_VALUE_DELIMITER + key) == -1) { // Error condition if key is not found while (true); // Endless loop } String p_key = KEY_VALUE_DELIMITER + key ; String p_value = KEY_VALUE_DELIMITER + value ; int p_keyPosition = default_key_values.indexOf(p_key) ; int kvSectionStart = p_keyPosition ; int spacesInBetween = KEY_VALUE_SIZE - p_key.length() - p_value.length() ; String spaces = String(' ', spacesInBetween) ; String sectionToWrite = p_key + spaces + p_value ; /* Check if key-values fit into KEY_VALUE_SIZE */ if (!checkKeyValueSections(sectionToWrite)) { // Error condition if section size exceeds allowed size while (true); // Endless loop } // Replace the relevant portion in runtime_key_values runtime_key_values = runtime_key_values.substring(0, kvSectionStart) + sectionToWrite + runtime_key_values.substring(kvSectionStart + KEY_VALUE_SIZE); // Overwrite the corresponding section in the file custom_key_value_pairs.txt directly File file = LittleFS.open("/custom_key_value_pairs.txt", "r+"); if (file) { file.seek(kvSectionStart); // Seek to the position file.print(sectionToWrite); // Write the updated section file.close(); } else { // Error condition while (true); // Endless loop } } private: // new version of 12.08.24 String create_custom_KV_file() { /* Create file with values = "" and return created string */ String lengthOfDelimiter = KEY_VALUE_DELIMITER; String contentToWrite = ""; File file = LittleFS.open("/custom_key_value_pairs.txt", "w"); if (file) { int numPairs = defaults_for_initialization.length() / KEY_VALUE_SIZE; for (int i = 0; i < numPairs; i++) { // Extract the key-value line from defaults_for_initialization String keyValueLine = defaults_for_initialization.substring( i * KEY_VALUE_SIZE, (i + 1) * KEY_VALUE_SIZE ); /* Key-length and key including the prepended delimiter */ int p_keyLength = keyValueLine.indexOf(' '); String p_keyPart = keyValueLine.substring(0, p_keyLength); /* No. of spaces available between p_keyLength and the (empty) value "" */ int spacesBetween = KEY_VALUE_SIZE - p_keyLength - lengthOfDelimiter.length(); /* Format the value part with the required spaces, "__" and the empty value */ String valuePart = KEY_VALUE_DELIMITER + String(' ', spacesBetween) + ""; /* Combine the key, spaces and value part to form a complete key-val section */ String sectionToWrite = p_keyPart + String(' ', spacesBetween) + valuePart; /* Append the section to contentToWrite */ contentToWrite += sectionToWrite; } file.print(contentToWrite); file.close(); } else { // Error condition while (true); // Endless loop } return contentToWrite; } // new version of 12.08.24 /* Use function in case of writing events only to prevent exceeding the predefined space available for key-value sections */ int countTwoByteChars(const String& str) { int count = 0; for (unsigned int i = 0; i < str.length(); i++) { // Changed 'int i' to 'unsigned int i' if ((str[i] & 0xE0) == 0xC0) { count++; } } return count; } /* Check if key-values fit into KEY_VALUE_SIZE */ bool checkKeyValueSections(const String& keyValueString) { int length = keyValueString.length(); int numSections = length / KEY_VALUE_SIZE; // Check if the division is exact if (length % KEY_VALUE_SIZE != 0) { // Error condition if length is not a multiple of KEY_VALUE_SIZE while (true); // Endless loop } for (int i = 0; i < numSections; i++) { /* Extract the current key-value section */ String section = keyValueString.substring(i * KEY_VALUE_SIZE, (i + 1) * KEY_VALUE_SIZE); /* Compute actual length of the section, accounting for two-byte characters */ int actualLength = section.length(); int twoByteCharCount = countTwoByteChars(section); /* Adjust length considering two-byte characters */ int adjustedLength = actualLength + twoByteCharCount; /* Check if the adjusted length exceeds KEY_VALUE_SIZE */ if (adjustedLength > KEY_VALUE_SIZE) { return false; // Section size exceeds the allowed size } } return true; // All sections are within the allowed size } }; void setup() { // Initialize serial communication for debugging Serial.begin(9600); // Create an instance of KeyValuePairs KeyValuePairs kvPairs; // Example of setting and getting values kvPairs.setValue("vorwärm_temperatur", "30"); String value = kvPairs.getValue("vorwärm_temperatur"); Serial.println(value); // Should print "30" } void loop() { // Empty loop }