From 7b5b4653e14643ac8035ac4d48eb05f855b401c4 Mon Sep 17 00:00:00 2001 From: ryokusei Date: Sun, 17 Feb 2019 17:40:51 -0300 Subject: [PATCH] Adapt IniFile to SPIFFS Changed source code to work with SPIFFS --- README.md | 8 + examples/IniFileExample/IniFileExample.ino | 143 ++++++ examples/IniFileExample/README.md | 29 ++ examples/IniFileExample/net.ini | 68 +++ keywords.txt | 33 ++ library.properties | 9 + src/SPIFFSIniFile.cpp | 520 +++++++++++++++++++++ src/SPIFFSIniFile.h | 192 ++++++++ 8 files changed, 1002 insertions(+) create mode 100644 README.md create mode 100644 examples/IniFileExample/IniFileExample.ino create mode 100644 examples/IniFileExample/README.md create mode 100644 examples/IniFileExample/net.ini create mode 100644 keywords.txt create mode 100644 library.properties create mode 100644 src/SPIFFSIniFile.cpp create mode 100644 src/SPIFFSIniFile.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..9cb7c6b --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# SPIFFSIniFile + +SPIFFSIniFile is an Arduino library for reading ini files via SPIFFS. +This is just a modification of this library: [IniFile](https://github.com/stevemarple/IniFile) + +In case of any questions regarding how this library works, please refer to the previous link. + +All credits go to Steven Marple. All I did was adapt the source to work on SPIFFS aiming at providing this feature for ESP8266 and ESP32 platforms. \ No newline at end of file diff --git a/examples/IniFileExample/IniFileExample.ino b/examples/IniFileExample/IniFileExample.ino new file mode 100644 index 0000000..9e7134e --- /dev/null +++ b/examples/IniFileExample/IniFileExample.ino @@ -0,0 +1,143 @@ +#include + +#include +#include +#include + +// The select pin used for the SD card +//#define SD_SELECT 4 +#define SD_SELECT 22 +#define ETHERNET_SELECT 10 + +void printErrorMessage(uint8_t e, bool eol = true) +{ + switch (e) { + case IniFile::errorNoError: + Serial.print("no error"); + break; + case IniFile::errorFileNotFound: + Serial.print("file not found"); + break; + case IniFile::errorFileNotOpen: + Serial.print("file not open"); + break; + case IniFile::errorBufferTooSmall: + Serial.print("buffer too small"); + break; + case IniFile::errorSeekError: + Serial.print("seek error"); + break; + case IniFile::errorSectionNotFound: + Serial.print("section not found"); + break; + case IniFile::errorKeyNotFound: + Serial.print("key not found"); + break; + case IniFile::errorEndOfFile: + Serial.print("end of file"); + break; + case IniFile::errorUnknownError: + Serial.print("unknown error"); + break; + default: + Serial.print("unknown error value"); + break; + } + if (eol) + Serial.println(); +} + +void setup() +{ + // Configure all of the SPI select pins as outputs and make SPI + // devices inactive, otherwise the earlier init routines may fail + // for devices which have not yet been configured. + pinMode(SD_SELECT, OUTPUT); + digitalWrite(SD_SELECT, HIGH); // disable SD card + + pinMode(ETHERNET_SELECT, OUTPUT); + digitalWrite(ETHERNET_SELECT, HIGH); // disable Ethernet + + const size_t bufferLen = 80; + char buffer[bufferLen]; + + const char *filename = "/net.ini"; + Serial.begin(9600); + SPI.begin(); + if (!SD.begin(SD_SELECT)) + while (1) + Serial.println("SD.begin() failed"); + + IniFile ini(filename); + if (!ini.open()) { + Serial.print("Ini file "); + Serial.print(filename); + Serial.println(" does not exist"); + // Cannot do anything else + while (1) + ; + } + Serial.println("Ini file exists"); + + // Check the file is valid. This can be used to warn if any lines + // are longer than the buffer. + if (!ini.validate(buffer, bufferLen)) { + Serial.print("ini file "); + Serial.print(ini.getFilename()); + Serial.print(" not valid: "); + printErrorMessage(ini.getError()); + // Cannot do anything else + while (1) + ; + } + + // Fetch a value from a key which is present + if (ini.getValue("network", "mac", buffer, bufferLen)) { + Serial.print("section 'network' has an entry 'mac' with value "); + Serial.println(buffer); + } + else { + Serial.print("Could not read 'mac' from section 'network', error was "); + printErrorMessage(ini.getError()); + } + + // Try fetching a value from a missing key (but section is present) + if (ini.getValue("network", "nosuchkey", buffer, bufferLen)) { + Serial.print("section 'network' has an entry 'nosuchkey' with value "); + Serial.println(buffer); + } + else { + Serial.print("Could not read 'nosuchkey' from section 'network', error was "); + printErrorMessage(ini.getError()); + } + + // Try fetching a key from a section which is not present + if (ini.getValue("nosuchsection", "nosuchkey", buffer, bufferLen)) { + Serial.print("section 'nosuchsection' has an entry 'nosuchkey' with value "); + Serial.println(buffer); + } + else { + Serial.print("Could not read 'nosuchkey' from section 'nosuchsection', error was "); + printErrorMessage(ini.getError()); + } + + // Fetch a boolean value + bool allowPut; // variable where result will be stored + bool found = ini.getValue("/upload", "allow put", buffer, bufferLen, allowPut); + if (found) { + Serial.print("The value of 'allow put' in section '/upload' is "); + // Print value, converting boolean to a string + Serial.println(allowPut ? "TRUE" : "FALSE"); + } + else { + Serial.print("Could not get the value of 'allow put' in section '/upload': "); + printErrorMessage(ini.getError()); + } +} + + +void loop() +{ + + +} diff --git a/examples/IniFileExample/README.md b/examples/IniFileExample/README.md new file mode 100644 index 0000000..867fedd --- /dev/null +++ b/examples/IniFileExample/README.md @@ -0,0 +1,29 @@ +# IniFile example + +## Instructions for use + + * Copy the `net.ini` file to the root directory of your (micro)SD card. + * Modify the `IniFileExample.ino` file so that `SD_SELECT` defines + the correct pin number. + * Compile and upload the sketch. + + +## Expected output + +It may take a few seconds from the sketch starting before anything is +printed to the serial port, be patient. If the sketch runs correctly +the output should appear as below: + + + Ini file exists + section 'network' has an entry 'mac' with value 01:23:45:67:89:AB + Could not read 'nosuchkey' from section 'network', error was key not found + Could not read 'nosuchkey' from section 'nosuchsection', error was section not found + The value of 'allow put' in section '/upload' is TRUE + + +If the SD card is missing or cannot be read the sketch will print: + + SD.begin() failed + + diff --git a/examples/IniFileExample/net.ini b/examples/IniFileExample/net.ini new file mode 100644 index 0000000..3400d29 --- /dev/null +++ b/examples/IniFileExample/net.ini @@ -0,0 +1,68 @@ +; Semi-colon comment +[network] +mac = 01:23:45:67:89:AB + +# hash comment, leading spaces below + gateway = 192.168.1.1 + +# extraneous spaces before and after key and value + ip = 192.168.1.2 + +hosts allow = example.com + +# A similarly-named section +[network2] +mac = ee:ee:ee:ee:ee:ee +subnet mask=255.255.255.0 + +; Test extra whitespace in keys and value +hosts allow = sloppy.example.com + +[misc] + +string = 123456789012345678901234567890123456789001234567890 +string2 = a string with spaces in it + +; ini file for WwwServerExample + +[mime types] +default = text/plain +htm = text/html +bin = application/octet-stream +pdf = application/pdf + +[/] +; no access to root of SD filesystem +handler = default +error document 403 = /errordoc/403.htm + +[/www.ini] +handler = default + +[/data] +handler = default + +[/data/private] +; Block access to this directory +handler = prohibit +error document 403 = /data/private/403.htm + +[/data/noaccess.txt] +; Block access to this file +handler = prohibit + +[/status] +; built-in status handler +handler = status + +[/cgi] +; User-defined handler +handler = cgi + +[/src] +; A redirect +handler = temporary redirect +location = http://github.com/stevemarple/WwwServer + +[/upload] +allow put = true diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..98f6a9d --- /dev/null +++ b/keywords.txt @@ -0,0 +1,33 @@ +####################################### +# Syntax Coloring Map For SPIFFSIniFile +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### +SPIFFSIniFile KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### +clearError KEYWORD2 +close KEYWORD2 +isOpen KEYWORD2 +getCaseSensitive KEYWORD2 +getError KEYWORD2 +getFilename KEYWORD2 +getIPAddress KEYWORD2 +getMACAddress KEYWORD2 +getMode KEYWORD2 +getValue KEYWORD2 +isCommentChar KEYWORD2 +open KEYWORD2 +readLine KEYWORD2 +removeTrailingWhiteSpace KEYWORD2 +setCaseSensitive KEYWORD2 +skipWhiteSpace KEYWORD2 +validate KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..0d94e81 --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=SPIFFSIniFile +version=1.0.0 +author=Yuri Lopes, Steve Marple +maintainer=Yuri Lopes +sentence=Library to read and parse .ini files on ESP8266 and ESP32 platforms +paragraph=SPIFFSIniFile is a library to read and parse .ini files as used by Microsoft Windows. SPIFFSIniFile is designed to use minimal memory requirements, and the only buffer used is one supplied by the user, thus the user remains in charge of memory usage. GNU LGPL v3 +category=Other +url=https://github.com/yurilopes/SPIFFSIniFile +architectures=esp8266,esp32 \ No newline at end of file diff --git a/src/SPIFFSIniFile.cpp b/src/SPIFFSIniFile.cpp new file mode 100644 index 0000000..8b620a4 --- /dev/null +++ b/src/SPIFFSIniFile.cpp @@ -0,0 +1,520 @@ +#include "SPIFFSIniFile.h" + +#include + +const uint8_t SPIFFSIniFile::maxFilenameLen = SPIFFSINI_FILE_MAX_FILENAME_LEN; + +SPIFFSIniFile::SPIFFSIniFile(const char* filename, char* mode, + bool caseSensitive) +{ + if (strlen(filename) <= maxFilenameLen) + strcpy(_filename, filename); + else + _filename[0] = '\0'; + _mode = mode; + _caseSensitive = caseSensitive; +} + +SPIFFSIniFile::~SPIFFSIniFile() +{ + if (_file) + _file.close(); +} + +bool SPIFFSIniFile::validate(char* buffer, size_t len) const +{ + uint32_t pos = 0; + error_t err; + while ((err = readLine(_file, buffer, len, pos)) == errorNoError) + ; + if (err == errorEndOfFile) { + _error = errorNoError; + return true; + } + else { + _error = err; + return false; + } +} + +bool SPIFFSIniFile::getValue(const char* section, const char* key, + char* buffer, size_t len, SPIFFSIniFileState &state) const +{ + bool done = false; + if (!_file) { + _error = errorFileNotOpen; + return true; + } + + switch (state.getValueState) { + case SPIFFSIniFileState::funcUnset: + state.getValueState = (section == NULL ? SPIFFSIniFileState::funcFindKey + : SPIFFSIniFileState::funcFindSection); + state.readLinePosition = 0; + break; + + case SPIFFSIniFileState::funcFindSection: + if (findSection(section, buffer, len, state)) { + if (_error != errorNoError) + return true; + state.getValueState = SPIFFSIniFileState::funcFindKey; + } + break; + + case SPIFFSIniFileState::funcFindKey: + char *cp; + if (findKey(section, key, buffer, len, &cp, state)) { + if (_error != errorNoError) + return true; + // Found key line in correct section + cp = skipWhiteSpace(cp); + removeTrailingWhiteSpace(cp); + + // Copy from cp to buffer, but the strings overlap so strcpy is out + while (*cp != '\0') + *buffer++ = *cp++; + *buffer = '\0'; + return true; + } + break; + + default: + // How did this happen? + _error = errorUnknownError; + done = true; + break; + } + + return done; +} + +bool SPIFFSIniFile::getValue(const char* section, const char* key, + char* buffer, size_t len) const +{ + SPIFFSIniFileState state; + while (!getValue(section, key, buffer, len, state)) + ; + return _error == errorNoError; +} + + +bool SPIFFSIniFile::getValue(const char* section, const char* key, + char* buffer, size_t len, char *value, size_t vlen) const +{ + if (!getValue(section, key, buffer, len)) + return false; // error + if (strlen(buffer) >= vlen) + return false; + strcpy(value, buffer); + return true; +} + + +// For true accept: true, yes, 1 +// For false accept: false, no, 0 +bool SPIFFSIniFile::getValue(const char* section, const char* key, + char* buffer, size_t len, bool& val) const +{ + if (!getValue(section, key, buffer, len)) + return false; // error + + if (strcasecmp(buffer, "true") == 0 || + strcasecmp(buffer, "yes") == 0 || + strcasecmp(buffer, "1") == 0) { + val = true; + return true; + } + if (strcasecmp(buffer, "false") == 0 || + strcasecmp(buffer, "no") == 0 || + strcasecmp(buffer, "0") == 0) { + val = false; + return true; + } + return false; // does not match any known strings +} + +bool SPIFFSIniFile::getValue(const char* section, const char* key, + char* buffer, size_t len, int& val) const +{ + if (!getValue(section, key, buffer, len)) + return false; // error + + val = atoi(buffer); + return true; +} + +bool SPIFFSIniFile::getValue(const char* section, const char* key, \ + char* buffer, size_t len, uint16_t& val) const +{ + long longval; + bool r = getValue(section, key, buffer, len, longval); + if (r) + val = uint16_t(longval); + return r; +} + +bool SPIFFSIniFile::getValue(const char* section, const char* key, + char* buffer, size_t len, long& val) const +{ + if (!getValue(section, key, buffer, len)) + return false; // error + + val = atol(buffer); + return true; +} + +bool SPIFFSIniFile::getValue(const char* section, const char* key, + char* buffer, size_t len, unsigned long& val) const +{ + if (!getValue(section, key, buffer, len)) + return false; // error + + char *endptr; + unsigned long tmp = strtoul(buffer, &endptr, 10); + if (endptr == buffer) + return false; // no conversion + if (*endptr == '\0') { + val = tmp; + return true; // valid conversion + } + // buffer has trailing non-numeric characters, and since the buffer + // already had whitespace removed discard the entire results + return false; +} + + +bool SPIFFSIniFile::getValue(const char* section, const char* key, + char* buffer, size_t len, float & val) const +{ + if (!getValue(section, key, buffer, len)) + return false; // error + + char *endptr; + float tmp = strtod(buffer, &endptr); + if (endptr == buffer) + return false; // no conversion + if (*endptr == '\0') { + val = tmp; + return true; // valid conversion + } + // buffer has trailing non-numeric characters, and since the buffer + // already had whitespace removed discard the entire results + return false; +} + + +bool SPIFFSIniFile::getIPAddress(const char* section, const char* key, + char* buffer, size_t len, uint8_t* ip) const +{ + // Need 16 chars minimum: 4 * 3 digits, 3 dots and a null character + if (len < 16) + return false; + + if (!getValue(section, key, buffer, len)) + return false; // error + + int i = 0; + char* cp = buffer; + ip[0] = ip[1] = ip[2] = ip[3] = 0; + while (*cp != '\0' && i < 4) { + if (*cp == '.') { + ++i; + ++cp; + continue; + } + if (isdigit(*cp)) { + ip[i] *= 10; + ip[i] += (*cp - '0'); + } + else { + ip[0] = ip[1] = ip[2] = ip[3] = 0; + return false; + } + ++cp; + } + return true; +} + + +#if defined(ARDUINO) && ARDUINO >= 100 +bool SPIFFSIniFile::getIPAddress(const char* section, const char* key, + char* buffer, size_t len, IPAddress& ip) const +{ + // Need 16 chars minimum: 4 * 3 digits, 3 dots and a null character + if (len < 16) + return false; + + if (!getValue(section, key, buffer, len)) + return false; // error + + int i = 0; + char* cp = buffer; + ip = IPAddress(0, 0, 0, 0); + while (*cp != '\0' && i < 4) { + if (*cp == '.') { + ++i; + ++cp; + continue; + } + if (isdigit(*cp)) { + ip[i] *= 10; + ip[i] += (*cp - '0'); + } + else { + ip = IPAddress(0, 0, 0, 0); + return false; + } + ++cp; + } + return true; +} +#endif + +bool SPIFFSIniFile::getMACAddress(const char* section, const char* key, + char* buffer, size_t len, uint8_t mac[6]) const +{ + // Need 18 chars: 6 * 2 hex digits, 5 : or - and a null char + if (len < 18) + return false; + + if (!getValue(section, key, buffer, len)) + return false; // error + + int i = 0; + char* cp = buffer; + memset(mac, 0, 6); + + while (*cp != '\0' && i < 6) { + if (*cp == ':' || *cp == '-') { + ++i; + ++cp; + continue; + } + if (isdigit(*cp)) { + mac[i] *= 16; // working in hex! + mac[i] += (*cp - '0'); + } + else { + if (isxdigit(*cp)) { + mac[i] *= 16; // working in hex! + mac[i] += (toupper(*cp) - 55); // convert A to 0xA, F to 0xF + } + else { + memset(mac, 0, 6); + return false; + } + } + ++cp; + } + return true; +} + +//int8_t SPIFFSIniFile::readLine(File &file, char *buffer, size_t len, uint32_t &pos) +SPIFFSIniFile::error_t SPIFFSIniFile::readLine(File &file, char *buffer, size_t len, uint32_t &pos) +{ + if (!file) + return errorFileNotOpen; + + if (len < 3) + return errorBufferTooSmall; + + if (!file.seek(pos)) + return errorSeekError; + + size_t bytesRead = file.readBytes(buffer, len); + if (!bytesRead) { + buffer[0] = '\0'; + //return 1; // done + return errorEndOfFile; + } + + for (size_t i = 0; i < bytesRead && i < len-1; ++i) { + // Test for '\n' with optional '\r' too + // if (endOfLineTest(buffer, len, i, '\n', '\r') + + if (buffer[i] == '\n' || buffer[i] == '\r') { + char match = buffer[i]; + char otherNewline = (match == '\n' ? '\r' : '\n'); + // end of line, discard any trailing character of the other sort + // of newline + buffer[i] = '\0'; + + if (buffer[i+1] == otherNewline) + ++i; + pos += (i + 1); // skip past newline(s) + //return (i+1 == bytesRead && !file.available()); + return errorNoError; + } + } + if (!file.available()) { + // end of file without a newline + buffer[bytesRead] = '\0'; + // return 1; //done + return errorEndOfFile; + } + + buffer[len-1] = '\0'; // terminate the string + return errorBufferTooSmall; +} + +bool SPIFFSIniFile::isCommentChar(char c) +{ + return (c == ';' || c == '#'); +} + +char* SPIFFSIniFile::skipWhiteSpace(char* str) +{ + char *cp = str; + while (isspace(*cp)) + ++cp; + return cp; +} + +void SPIFFSIniFile::removeTrailingWhiteSpace(char* str) +{ + char *cp = str + strlen(str) - 1; + while (cp >= str && isspace(*cp)) + *cp-- = '\0'; +} + +bool SPIFFSIniFile::findSection(const char* section, char* buffer, size_t len, + SPIFFSIniFileState &state) const +{ + if (section == NULL) { + _error = errorSectionNotFound; + return true; + } + + error_t err = SPIFFSIniFile::readLine(_file, buffer, len, state.readLinePosition); + + if (err != errorNoError && err != errorEndOfFile) { + // Signal to caller to stop looking and any error value + _error = err; + return true; + } + + char *cp = skipWhiteSpace(buffer); + //if (isCommentChar(*cp)) + //return (done ? errorSectionNotFound : 0); + if (isCommentChar(*cp)) { + // return (err == errorEndOfFile ? errorSectionNotFound : errorNoError); + if (err == errorSectionNotFound) { + _error = err; + return true; + } + else + return false; // Continue searching + } + + if (*cp == '[') { + // Start of section + ++cp; + cp = skipWhiteSpace(cp); + char *ep = strchr(cp, ']'); + if (ep != NULL) { + *ep = '\0'; // make ] be end of string + removeTrailingWhiteSpace(cp); + if (_caseSensitive) { + if (strcmp(cp, section) == 0) { + _error = errorNoError; + return true; + } + } + else { + if (strcasecmp(cp, section) == 0) { + _error = errorNoError; + return true; + } + } + } + } + + // Not a valid section line + //return (done ? errorSectionNotFound : 0); + if (err == errorEndOfFile) { + _error = errorSectionNotFound; + return true; + } + + return false; +} + +// From the current file location look for the matching key. If +// section is non-NULL don't look in the next section +bool SPIFFSIniFile::findKey(const char* section, const char* key, + char* buffer, size_t len, char** keyptr, + SPIFFSIniFileState &state) const +{ + if (key == NULL || *key == '\0') { + _error = errorKeyNotFound; + return true; + } + + error_t err = SPIFFSIniFile::readLine(_file, buffer, len, state.readLinePosition); + if (err != errorNoError && err != errorEndOfFile) { + _error = err; + return true; + } + + char *cp = skipWhiteSpace(buffer); + // if (isCommentChar(*cp)) + // return (done ? errorKeyNotFound : 0); + if (isCommentChar(*cp)) { + if (err == errorEndOfFile) { + _error = errorKeyNotFound; + return true; + } + else + return false; // Continue searching + } + + if (section && *cp == '[') { + // Start of a new section + _error = errorKeyNotFound; + return true; + } + + // Find '=' + char *ep = strchr(cp, '='); + if (ep != NULL) { + *ep = '\0'; // make = be the end of string + removeTrailingWhiteSpace(cp); + if (_caseSensitive) { + if (strcmp(cp, key) == 0) { + *keyptr = ep + 1; + _error = errorNoError; + return true; + } + } + else { + if (strcasecmp(cp, key) == 0) { + *keyptr = ep + 1; + _error = errorNoError; + return true; + } + } + } + + // Not the valid key line + if (err == errorEndOfFile) { + _error = errorKeyNotFound; + return true; + } + return false; +} + +bool SPIFFSIniFile::getCaseSensitive(void) const +{ + return _caseSensitive; +} + +void SPIFFSIniFile::setCaseSensitive(bool cs) +{ + _caseSensitive = cs; +} + +SPIFFSIniFileState::SPIFFSIniFileState() +{ + readLinePosition = 0; + getValueState = funcUnset; +} diff --git a/src/SPIFFSIniFile.h b/src/SPIFFSIniFile.h new file mode 100644 index 0000000..72ca8a1 --- /dev/null +++ b/src/SPIFFSIniFile.h @@ -0,0 +1,192 @@ +#ifndef _SPIFFSINIFILE_H +#define _SPIFFSINIFILE_H + +#define SPIFFSINIFILE_VERSION "1.0.0" + +// Maximum length for filename, excluding NULL char +#define SPIFFSINI_FILE_MAX_FILENAME_LEN 31 + +#include "FS.h" +#include "IPAddress.h" + +class SPIFFSIniFileState; + +class SPIFFSIniFile { +public: + enum error_t { + errorNoError = 0, + errorFileNotFound, + errorFileNotOpen, + errorBufferTooSmall, + errorSeekError, + errorSectionNotFound, + errorKeyNotFound, + errorEndOfFile, + errorUnknownError, + }; + + static const uint8_t maxFilenameLen; + + // Create an SPIFFSIniFile object. It isn't opened until open() is called on it. + SPIFFSIniFile(const char* filename, char* mode = "r", + bool caseSensitive = false); + ~SPIFFSIniFile(); + + inline bool open(void); // Returns true if open succeeded + inline void close(void); + + inline bool isOpen(void) const; + + inline error_t getError(void) const; + inline void clearError(void) const; + // Get the file mode (FILE_READ/FILE_WRITE) + inline char* getMode(void) const; + + // Get the filename asscoiated with the ini file object + inline const char* getFilename(void) const; + + bool validate(char* buffer, size_t len) const; + + // Get value from the file, but split into many short tasks. Return + // value: false means continue, true means stop. Call getError() to + // find out if any error + bool getValue(const char* section, const char* key, + char* buffer, size_t len, SPIFFSIniFileState &state) const; + + // Get value, as one big task. Return = true means value is present + // in buffer + bool getValue(const char* section, const char* key, + char* buffer, size_t len) const; + + // Get the value as a string, storing the result in a new buffer + // (not the working buffer) + bool getValue(const char* section, const char* key, + char* buffer, size_t len, char *value, size_t vlen) const; + + // Get a boolean value + bool getValue(const char* section, const char* key, + char* buffer, size_t len, bool& b) const; + + // Get an integer value + bool getValue(const char* section, const char* key, + char* buffer, size_t len, int& val) const; + + // Get a uint16_t value + bool getValue(const char* section, const char* key, + char* buffer, size_t len, uint16_t& val) const; + + // Get a long value + bool getValue(const char* section, const char* key, + char* buffer, size_t len, long& val) const; + + bool getValue(const char* section, const char* key, + char* buffer, size_t len, unsigned long& val) const; + + // Get a float value + bool getValue(const char* section, const char* key, + char* buffer, size_t len, float& val) const; + + bool getIPAddress(const char* section, const char* key, + char* buffer, size_t len, uint8_t* ip) const; + +#if defined(ARDUINO) && ARDUINO >= 100 + bool getIPAddress(const char* section, const char* key, + char* buffer, size_t len, IPAddress& ip) const; +#endif + + bool getMACAddress(const char* section, const char* key, + char* buffer, size_t len, uint8_t mac[6]) const; + + // Utility function to read a line from a file, make available to all + //static int8_t readLine(File &file, char *buffer, size_t len, uint32_t &pos); + static error_t readLine(File &file, char *buffer, size_t len, uint32_t &pos); + static bool isCommentChar(char c); + static char* skipWhiteSpace(char* str); + static void removeTrailingWhiteSpace(char* str); + + bool getCaseSensitive(void) const; + void setCaseSensitive(bool cs); + +protected: + // True means stop looking, false means not yet found + bool findSection(const char* section, char* buffer, size_t len, + SPIFFSIniFileState &state) const; + bool findKey(const char* section, const char* key, char* buffer, + size_t len, char** keyptr, SPIFFSIniFileState &state) const; + + +private: + char _filename[SPIFFSINI_FILE_MAX_FILENAME_LEN]; + char* _mode; + mutable error_t _error; + mutable File _file; + bool _caseSensitive; +}; + +bool SPIFFSIniFile::open(void) +{ + if (_file) + _file.close(); + _file = SPIFFS.open(_filename, _mode); + if (isOpen()) { + _error = errorNoError; + return true; + } + else { + _error = errorFileNotFound; + return false; + } +} + +void SPIFFSIniFile::close(void) +{ + if (_file) + _file.close(); +} + +bool SPIFFSIniFile::isOpen(void) const +{ + return (_file == true); +} + +SPIFFSIniFile::error_t SPIFFSIniFile::getError(void) const +{ + return _error; +} + +void SPIFFSIniFile::clearError(void) const +{ + _error = errorNoError; +} + +char* SPIFFSIniFile::getMode(void) const +{ + return _mode; +} + +const char* SPIFFSIniFile::getFilename(void) const +{ + return _filename; +} + + + +class SPIFFSIniFileState { +public: + SPIFFSIniFileState(); + +private: + enum {funcUnset = 0, + funcFindSection, + funcFindKey, + }; + + uint32_t readLinePosition; + uint8_t getValueState; + + friend class SPIFFSIniFile; +}; + + +#endif +