diff --git a/README.md b/README.md index 23ab8f0..e8fbbbe 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,21 @@ This firmware uses [Espressif's IDF](https://github.com/espressif/esp-idf) 1. Load the `Tools -> SerialNINAPassthrough` example sketch on to the board 1. Use `esptool` to flash the compiled firmware +### Building with docker + +As an alternative for building we can use the docker image from espressif idf, we can do that as follows: + +``` +docker run -v $PWD:/data espressif/idf:v3.3.1 -- sh -c 'cd /data && make' +``` + +You can also flash the firmware with the following snippet: + +``` +DEVICE=/dev/ttyACM0 +docker run --device=$DEVICE -v $PWD:/data espressif/idf:v3.3.1 -- sh -c 'cd /data && make flash ESPPORT=$DEVICE' +``` + ## Notes If updating the NINA firmware for an **Arduino UNO WiFi Rev. 2** or **Arduino Nano RP2040** board via [SerialNINAPassthrough](https://github.com/arduino-libraries/WiFiNINA/blob/master/examples/Tools/SerialNINAPassthrough/SerialNINAPassthrough.ino) sketch, then the `esptool` invocation needs to be changed slightly: ```diff diff --git a/main/CommandHandler.cpp b/main/CommandHandler.cpp index e9cac07..8935812 100644 --- a/main/CommandHandler.cpp +++ b/main/CommandHandler.cpp @@ -967,7 +967,7 @@ int setEnt(const uint8_t command[], uint8_t response[]) char password[128 + 1]; char identity[128 + 1]; const char* rootCA; - + memset(username, 0x00, sizeof(username)); memset(password, 0x00, sizeof(password)); memset(identity, 0x00, sizeof(identity)); @@ -2070,6 +2070,440 @@ int socket_getpeername(const uint8_t command[], uint8_t response[]) return 14; } +/* + * Preferences API + */ +#include "Preferences.h" + +Preferences preferences; +const char PREF_TAG[] = "preferences"; + +int pref_begin(const uint8_t command[], uint8_t response[]) +{ + + //[0] CMD_START < 0xE0 > + //[1] Command < 1 byte > + //[2] N args < 1 byte > + //[3] store_name size < 1 byte > + //[4..n] store_name < n byte > + //[n+1] readonly < 1 byte > + //[n+2] partition label size < 1 byte > + //[n+3..n+m] partition label < m byte > + + uint8_t nargs = command[2]; + char store_name[32]; + char partition_label[32]; + const uint8_t* partition_label_ptr = nullptr; + bool readonly=false; + + // command_ptr points to the next argument, in this case + // it points to the length of store_name string + const uint8_t* command_ptr = &command[3]; + + if(nargs < 1 && nargs > 3) { + ESP_LOGE(PREF_TAG, "Prefrences begin wrong number of arguments"); + response[4] = 255; + goto error; + } + + memset(store_name, 0x00, sizeof(store_name)); + memcpy(store_name, command_ptr+1, *command_ptr); + store_name[*command_ptr] = '\0'; + + // move the pointer to the next argument, by adding the length + // of store_name string + command_ptr += *command_ptr + 1; + + if(nargs > 1) { + command_ptr++; // the first byte contains the length (that is 1) of the next byte + readonly = *command_ptr; + command_ptr++; + } + + if(nargs > 2) { + memset(partition_label, 0x00, sizeof(partition_label)); + memcpy(partition_label, command_ptr+1, *command_ptr); + partition_label[*command_ptr] = '\0'; + + partition_label_ptr = command_ptr; + } + + response[4] = preferences.begin(store_name, readonly, (char*)partition_label_ptr) ? 0 : 1; + +error: + response[2] = 1; // number of parameters + response[3] = 1; // length of first parameter + + return 6; +} + +int pref_end(const uint8_t command[], uint8_t response[]) +{ + //[0] CMD_START < 0xE0 > + //[1] Command < 1 byte > + + preferences.end(); + + response[2] = 1; // number of parameters + response[3] = 1; // length of first parameter + response[4] = 1; + + return 6; +} + +int pref_clear(const uint8_t command[], uint8_t response[]) +{ + //[0] CMD_START < 0xE0 > + //[1] Command < 1 byte > + + response[2] = 1; // number of parameters + response[3] = 1; // length of first parameter + response[4] = preferences.clear() ? 0 : 1; // result of Preferences clear operation + + // response has to start ad position 2, and has to take into account + // 0xee that is put after the function being called + return 6; +} + +int pref_remove(const uint8_t command[], uint8_t response[]) +{ + //[0] CMD_START < 0xE0 > + //[1] Command < 1 byte > + //[2] N args < 1 byte > + //[3] key size < 1 byte > + //[4..n] key < n byte > + + uint8_t nargs = command[2]; + char key[16]; + + // command_ptr points to the next argument, in this case + // it points to the length of key string + const uint8_t* command_ptr = &command[3]; + + if(nargs != 1) { + ESP_LOGE(PREF_TAG, "Prefrences remove wrong number of arguments"); + response[4] = 255; + goto error; + } + + memset(key, 0x00, sizeof(key)); + memcpy(key, command_ptr+1, *command_ptr); + key[*command_ptr] = '\0'; + + response[4] = preferences.remove(key) ? 0 : 1; // result of Preferences end operation +error: + response[2] = 1; // number of parameters + response[3] = 1; // length of first parameter + + // response has to start ad position 2, and has to take into account + // 0xee that is put after the function being called + return 6; +} + +int pref_len(const uint8_t command[], uint8_t response[]) +{ + //[0] CMD_START < 0xE0 > + //[1] Command < 1 byte > + //[2] N args < 1 byte > + //[3] key size < 1 byte > + //[4..n] key < n byte > + + uint8_t nargs = command[2]; + char key[16]; + + // command_ptr points to the next argument, in this case + // it points to the length of key string + const uint8_t* command_ptr = &command[3]; + + // restricting the return as 32 bit integer as it is enough + uint32_t len = 0; + + if(nargs != 1) { + ESP_LOGE(PREF_TAG, "Prefrences length wrong number of arguments"); + response[2] = 1; + response[3] = 1; + response[4] = 255; + return 6; + } + + memset(key, 0x00, sizeof(key)); + memcpy(key, command_ptr+1, *command_ptr); + key[*command_ptr] = '\0'; + + len = preferences.getBytesLength(key); + + response[2] = 1; // number of parameters + response[3] = 4; // length of first parameter + + // write the result in big endian into the response buffer + response[4] = (len >> 0) & 0xff; + response[5] = (len >> 8) & 0xff; + response[6] = (len >> 16) & 0xff; + response[7] = (len >> 24) & 0xff; + + return 9; +} + +int pref_stat(const uint8_t command[], uint8_t response[]) +{ + //[0] CMD_START < 0xE0 > + //[1] Command < 1 byte > + + // restricting the return as 32 bit integer as it is enough + uint32_t res = 0; + + res = preferences.freeEntries(); + + response[2] = 1; // number of parameters + response[3] = 4; // length of first parameter + + // write the result in big endian into the response buffer + response[4] = (res >> 0) & 0xff; + response[5] = (res >> 8) & 0xff; + response[6] = (res >> 16) & 0xff; + response[7] = (res >> 24) & 0xff; + + return 9; +} + +int pref_put(const uint8_t command[], uint8_t response[]) +{ + //[0] CMD_START < 0xE0 > + //[1] Command < 1 byte > + //[2] N args < 1 byte > + //[3] key size < 1 byte > + //[4..n] key < n byte > + //[n] type < 1 byte > + //[n+1] len < 1 byte > + //[n+2..sizeof(type)] < sizeof(type) + // value or len byte > + + uint8_t nargs = command[2]; + char key[16]; + uint16_t len; + + // we are going to store the value (if not array type) in a 64 bit integer, because it is easier to handle + uint64_t value=0; + + // command_ptr points to the next argument, in this case + // it points to the length of key string + const uint8_t* command_ptr = &command[3]; + + // restricting the return as 32 bit integer as it is enough + size_t res = 0; + + if(nargs != 3) { + ESP_LOGE(PREF_TAG, "Prefrences put wrong number of arguments"); + response[2] = 1; + response[3] = 1; + response[4] = 255; + return 6; + } + + memset(key, 0x00, sizeof(key)); + memcpy(key, command_ptr+1, *command_ptr); + key[*command_ptr] = '\0'; + + // next argument + command_ptr += *command_ptr + 1; + + command_ptr++; // The first byte contains the length of the parameter, which is 1 + PreferenceType type = (PreferenceType)*command_ptr; + command_ptr++; + + // extract length + len = command_ptr[0]<<8 | command_ptr[1]; + command_ptr+=2; + + // extract value convert from bigendian, TODO not forr array types + for(uint8_t i=0; i(value)); + break; + case PT_U8: + res = preferences.putUChar(key, static_cast(value)); + break; + case PT_I16: + res = preferences.putShort(key, static_cast(value)); + break; + case PT_U16: + res = preferences.putUShort(key, static_cast(value)); + break; + case PT_I32: + res = preferences.putInt(key, static_cast(value)); + break; + case PT_U32: + res = preferences.putUInt(key, static_cast(value)); + break; + case PT_I64: + res = preferences.putLong64(key, static_cast(value)); + break; + case PT_U64: + res = preferences.putULong64(key, static_cast(value)); + break; + case PT_STR: + // for simplicity we send the string null terminated from the client side + res = preferences.putString(key, (const char*)command_ptr); + break; + case PT_BLOB: + ets_printf("put bytes \n"); + res = preferences.putBytes(key, command_ptr, len); + break; + case PT_INVALID: + default: + ESP_LOGE(PREF_TAG, "Prefrences put invalid type"); + response[2] = 1; + response[3] = 1; + response[4] = 254; + return 6; + } + + response[2] = 1; // response nargs + response[3] = 4; // length of first parameter + + response[4] = (res >> 0) & 0xff; + response[5] = (res >> 8) & 0xff; + response[6] = (res >> 16) & 0xff; + response[7] = (res >> 24) & 0xff; + + return 9; +} + +int pref_get(const uint8_t command[], uint8_t response[]) +{ + //[0] CMD_START < 0xE0 > + //[1] Command < 1 byte > + //[2] N args < 1 byte > + //[3] key size < 1 byte > + //[4..n] key < n byte > + //[n] type < 1 byte > + + uint8_t nargs = command[2]; + char key[16]; + + // command_ptr points to the next argument, in this case + // it points to the length of key string + const uint8_t* command_ptr = &command[3]; + + // restricting the return as 32 bit integer as it is enough + uint32_t res_size = 0; + + // all the kind of values can fit in a 64 bit integer + uint32_t res=0; + + if(nargs != 2) { + ESP_LOGE(PREF_TAG, "Prefrences put wrong number of arguments"); + response[2] = 1; + response[3] = 0; + return 5; + } + + memset(key, 0x00, sizeof(key)); + memcpy(key, command_ptr+1, *command_ptr); + key[*command_ptr] = '\0'; + + // next argument + command_ptr += *command_ptr + 1; + + command_ptr++; // The first byte contains the length of the parameter, which is 1 + PreferenceType type = static_cast(*command_ptr); + + command_ptr++; + + switch(type) { + case PT_I8: + res = static_cast(preferences.getChar(key)); + res_size = 1; + break; + case PT_U8: + res = static_cast(preferences.getUChar(key)); + res_size = 1; + break; + case PT_I16: + res = static_cast(preferences.getShort(key)); + res_size = 2; + break; + case PT_U16: + res_size = 2; + res = static_cast(preferences.getUShort(key)); + break; + case PT_I32: + res_size = 4; + res = static_cast(preferences.getInt(key)); + break; + case PT_U32: + res_size = 4; + res = static_cast(preferences.getUInt(key)); + break; + case PT_I64: + res_size = 8; + res = static_cast(preferences.getLong64(key)); + break; + case PT_U64: + res_size = 8; + res = static_cast(preferences.getULong64(key)); + break; + case PT_STR: + res_size = preferences.getString(key, (char*) &response[5], SPI_MAX_DMA_LEN - 8); + goto array_return; + case PT_BLOB: + res_size = preferences.getBytes(key, &response[5], SPI_MAX_DMA_LEN - 8); + goto array_return; + case PT_INVALID: + default: + ESP_LOGE(PREF_TAG, "Prefrences put invalid type"); + response[2] = 1; + response[3] = 0; + return 5; + } + + // fill the response buffer + for(uint8_t i=0; i> ((res_size-i-1) << 3)) & 0xff; + } + +array_return: + + response[2] = 1; // the number of parameters + + // the next 2 bytes are the size of the returned value. Since the client api support length with only 2 bytes + // we can return string and blobs up to that size + response[3] = (res_size >> 8) & 0xff; // readParamLen16 wants little endian length + response[4] = (res_size >> 0) & 0xff; + + return 6 + res_size; +} + +int pref_getType(const uint8_t command[], uint8_t response[]) { + //[0] CMD_START < 0xE0 > + //[1] Command < 1 byte > + //[2] N args < 1 byte > + //[3] key size < 1 byte > + //[4..n] key < n byte > + + uint8_t nargs = command[2]; + char key[16]; + + // command_ptr points to the next argument, in this case + // it points to the length of key string + const uint8_t* command_ptr = &command[3]; + + memset(key, 0x00, sizeof(key)); + memcpy(key, command_ptr+1, *command_ptr); + key[*command_ptr] = '\0'; + + + response[2] = 1; // response nargs + response[3] = 1; // response nargs + response[4] = preferences.getType(key); + + return 6; +} + typedef int (*CommandHandlerType)(const uint8_t command[], uint8_t response[]); const CommandHandlerType commandHandlers[] = { @@ -2088,8 +2522,20 @@ const CommandHandlerType commandHandlers[] = { // 0x40 -> 0x4f setEnt, NULL, NULL, NULL, sendDataTcp, getDataBufTcp, insertDataBuf, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, - // 0x50 -> 0x5f - setPinMode, setDigitalWrite, setAnalogWrite, getDigitalRead, getAnalogRead, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + // 0x50 -> 0x54 + setPinMode, setDigitalWrite, setAnalogWrite, getDigitalRead, getAnalogRead, + + // KVStore functions 0x55 -> 0x87 + pref_begin, // 0x55 + pref_end, // 0x56 + pref_clear, // 0x57 + pref_remove, // 0x58 + pref_len, // 0x59 + pref_stat, // 0x5A + pref_put, // 0x5B + pref_get, // 0x5C + pref_getType, // 0x5D + NULL, NULL, // 0x60 -> 0x6f writeFile, readFile, deleteFile, existsFile, downloadFile, applyOTA, renameFile, downloadOTA, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, @@ -2239,7 +2685,7 @@ void CommandHandlerClass::handleWiFiDisconnect() // close all non-listening sockets for (int i = 0; i < CONFIG_LWIP_MAX_SOCKETS; i++) { - struct sockaddr_in addr; + struct sockaddr_in addr; size_t addrLen = sizeof(addr); int socket = LWIP_SOCKET_OFFSET + i; diff --git a/main/Preferences.cpp b/main/Preferences.cpp new file mode 100644 index 0000000..53f576f --- /dev/null +++ b/main/Preferences.cpp @@ -0,0 +1,535 @@ +// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Preferences.h" + +#include "nvs.h" +#include "nvs_flash.h" +#include "esp_log.h" + +const char * nvs_errors[] = { "OTHER", "NOT_INITIALIZED", "NOT_FOUND", "TYPE_MISMATCH", "READ_ONLY", "NOT_ENOUGH_SPACE", "INVALID_NAME", "INVALID_HANDLE", "REMOVE_FAILED", "KEY_TOO_LONG", "PAGE_FULL", "INVALID_STATE", "INVALID_LENGTH"}; +#define nvs_error(e) (((e)>ESP_ERR_NVS_BASE)?nvs_errors[(e)&~(ESP_ERR_NVS_BASE)]:nvs_errors[0]) + +Preferences::Preferences() + :_handle(0) + ,_started(false) + ,_readOnly(false) +{} + +Preferences::~Preferences(){ + end(); +} + +bool Preferences::begin(const char * name, bool readOnly, const char* partition_label){ + if(_started){ + return false; + } + _readOnly = readOnly; + esp_err_t err = ESP_OK; + if (partition_label != NULL) { + err = nvs_flash_init_partition(partition_label); + if (err) { + ESP_LOGE(__FILE__, "nvs_flash_init_partition failed: %s", nvs_error(err)); + return false; + } + err = nvs_open_from_partition(partition_label, name, readOnly ? NVS_READONLY : NVS_READWRITE, &_handle); + } else { + err = nvs_open(name, readOnly ? NVS_READONLY : NVS_READWRITE, &_handle); + } + if(err){ + ESP_LOGE(__FILE__, "nvs_open failed: %s", nvs_error(err)); + return false; + } + _started = true; + return true; +} + +void Preferences::end(){ + if(!_started){ + return; + } + nvs_close(_handle); + _started = false; +} + +/* + * Clear all keys in opened preferences + * */ + +bool Preferences::clear(){ + if(!_started || _readOnly){ + return false; + } + esp_err_t err = nvs_erase_all(_handle); + if(err){ + ESP_LOGE(__FILE__, "nvs_erase_all fail: %s", nvs_error(err)); + return false; + } + err = nvs_commit(_handle); + if(err){ + ESP_LOGE(__FILE__, "nvs_commit fail: %s", nvs_error(err)); + return false; + } + return true; +} + +/* + * Remove a key + * */ + +bool Preferences::remove(const char * key){ + if(!_started || !key || _readOnly){ + return false; + } + esp_err_t err = nvs_erase_key(_handle, key); + if(err){ + ESP_LOGE(__FILE__, "nvs_erase_key fail: %s %s", key, nvs_error(err)); + return false; + } + err = nvs_commit(_handle); + if(err){ + ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err)); + return false; + } + return true; +} + +/* + * Put a key value + * */ + +size_t Preferences::putChar(const char* key, int8_t value){ + if(!_started || !key || _readOnly){ + return 0; + } + esp_err_t err = nvs_set_i8(_handle, key, value); + if(err){ + ESP_LOGE(__FILE__, "nvs_set_i8 fail: %s %s", key, nvs_error(err)); + return 0; + } + err = nvs_commit(_handle); + if(err){ + ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err)); + return 0; + } + return 1; +} + +size_t Preferences::putUChar(const char* key, uint8_t value){ + if(!_started || !key || _readOnly){ + return 0; + } + esp_err_t err = nvs_set_u8(_handle, key, value); + if(err){ + ESP_LOGE(__FILE__, "nvs_set_u8 fail: %s %s", key, nvs_error(err)); + return 0; + } + err = nvs_commit(_handle); + if(err){ + ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err)); + return 0; + } + return 1; +} + +size_t Preferences::putShort(const char* key, int16_t value){ + if(!_started || !key || _readOnly){ + return 0; + } + esp_err_t err = nvs_set_i16(_handle, key, value); + if(err){ + ESP_LOGE(__FILE__, "nvs_set_i16 fail: %s %s", key, nvs_error(err)); + return 0; + } + err = nvs_commit(_handle); + if(err){ + ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err)); + return 0; + } + return 2; +} + +size_t Preferences::putUShort(const char* key, uint16_t value){ + if(!_started || !key || _readOnly){ + return 0; + } + esp_err_t err = nvs_set_u16(_handle, key, value); + if(err){ + ESP_LOGE(__FILE__, "nvs_set_u16 fail: %s %s", key, nvs_error(err)); + return 0; + } + err = nvs_commit(_handle); + if(err){ + ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err)); + return 0; + } + return 2; +} + +size_t Preferences::putInt(const char* key, int32_t value){ + if(!_started || !key || _readOnly){ + return 0; + } + esp_err_t err = nvs_set_i32(_handle, key, value); + if(err){ + ESP_LOGE(__FILE__, "nvs_set_i32 fail: %s %s", key, nvs_error(err)); + return 0; + } + err = nvs_commit(_handle); + if(err){ + ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err)); + return 0; + } + return 4; +} + +size_t Preferences::putUInt(const char* key, uint32_t value){ + if(!_started || !key || _readOnly){ + return 0; + } + esp_err_t err = nvs_set_u32(_handle, key, value); + if(err){ + ESP_LOGE(__FILE__, "nvs_set_u32 fail: %s %s", key, nvs_error(err)); + return 0; + } + err = nvs_commit(_handle); + if(err){ + ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err)); + return 0; + } + return 4; +} + +size_t Preferences::putLong(const char* key, int32_t value){ + return putInt(key, value); +} + +size_t Preferences::putULong(const char* key, uint32_t value){ + return putUInt(key, value); +} + +size_t Preferences::putLong64(const char* key, int64_t value){ + if(!_started || !key || _readOnly){ + return 0; + } + esp_err_t err = nvs_set_i64(_handle, key, value); + if(err){ + ESP_LOGE(__FILE__, "nvs_set_i64 fail: %s %s", key, nvs_error(err)); + return 0; + } + err = nvs_commit(_handle); + if(err){ + ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err)); + return 0; + } + return 8; +} + +size_t Preferences::putULong64(const char* key, uint64_t value){ + if(!_started || !key || _readOnly){ + return 0; + } + esp_err_t err = nvs_set_u64(_handle, key, value); + if(err){ + ESP_LOGE(__FILE__, "nvs_set_u64 fail: %s %s", key, nvs_error(err)); + return 0; + } + err = nvs_commit(_handle); + if(err){ + ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err)); + return 0; + } + return 8; +} + +size_t Preferences::putFloat(const char* key, const float_t value){ + return putBytes(key, (void*)&value, sizeof(float_t)); +} + +size_t Preferences::putDouble(const char* key, const double_t value){ + return putBytes(key, (void*)&value, sizeof(double_t)); +} + +size_t Preferences::putBool(const char* key, const bool value){ + return putUChar(key, (uint8_t) (value ? 1 : 0)); +} + +size_t Preferences::putString(const char* key, const char* value){ + if(!_started || !key || !value || _readOnly){ + return 0; + } + esp_err_t err = nvs_set_str(_handle, key, value); + if(err){ + ESP_LOGE(__FILE__, "nvs_set_str fail: %s %s", key, nvs_error(err)); + return 0; + } + err = nvs_commit(_handle); + if(err){ + ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err)); + return 0; + } + return strlen(value); +} + +size_t Preferences::putString(const char* key, const String value){ + return putString(key, value.c_str()); +} + +size_t Preferences::putBytes(const char* key, const void* value, size_t len){ + if(!_started || !key || !value || !len || _readOnly){ + return 0; + } + esp_err_t err = nvs_set_blob(_handle, key, value, len); + if(err){ + ESP_LOGE(__FILE__, "nvs_set_blob fail: %s %s", key, nvs_error(err)); + return 0; + } + err = nvs_commit(_handle); + if(err){ + ESP_LOGE(__FILE__, "nvs_commit fail: %s %s", key, nvs_error(err)); + return 0; + } + return len; +} + +PreferenceType Preferences::getType(const char* key) { + if(!_started || !key || strlen(key)>15){ + return PT_INVALID; + } + int8_t mt1; uint8_t mt2; int16_t mt3; uint16_t mt4; + int32_t mt5; uint32_t mt6; int64_t mt7; uint64_t mt8; + size_t len = 0; + if(nvs_get_i8(_handle, key, &mt1) == ESP_OK) return PT_I8; + if(nvs_get_u8(_handle, key, &mt2) == ESP_OK) return PT_U8; + if(nvs_get_i16(_handle, key, &mt3) == ESP_OK) return PT_I16; + if(nvs_get_u16(_handle, key, &mt4) == ESP_OK) return PT_U16; + if(nvs_get_i32(_handle, key, &mt5) == ESP_OK) return PT_I32; + if(nvs_get_u32(_handle, key, &mt6) == ESP_OK) return PT_U32; + if(nvs_get_i64(_handle, key, &mt7) == ESP_OK) return PT_I64; + if(nvs_get_u64(_handle, key, &mt8) == ESP_OK) return PT_U64; + if(nvs_get_str(_handle, key, NULL, &len) == ESP_OK) return PT_STR; + if(nvs_get_blob(_handle, key, NULL, &len) == ESP_OK) return PT_BLOB; + return PT_INVALID; +} + +bool Preferences::isKey(const char* key) { + return getType(key) != PT_INVALID; +} + +/* + * Get a key value + * */ + +int8_t Preferences::getChar(const char* key, const int8_t defaultValue){ + int8_t value = defaultValue; + if(!_started || !key){ + return value; + } + esp_err_t err = nvs_get_i8(_handle, key, &value); + if(err){ + ESP_LOGV(__FILE__, "nvs_get_i8 fail: %s %s", key, nvs_error(err)); + } + return value; +} + +uint8_t Preferences::getUChar(const char* key, const uint8_t defaultValue){ + uint8_t value = defaultValue; + if(!_started || !key){ + return value; + } + esp_err_t err = nvs_get_u8(_handle, key, &value); + if(err){ + ESP_LOGV(__FILE__, "nvs_get_u8 fail: %s %s", key, nvs_error(err)); + } + return value; +} + +int16_t Preferences::getShort(const char* key, const int16_t defaultValue){ + int16_t value = defaultValue; + if(!_started || !key){ + return value; + } + esp_err_t err = nvs_get_i16(_handle, key, &value); + if(err){ + ESP_LOGV(__FILE__, "nvs_get_i16 fail: %s %s", key, nvs_error(err)); + } + return value; +} + +uint16_t Preferences::getUShort(const char* key, const uint16_t defaultValue){ + uint16_t value = defaultValue; + if(!_started || !key){ + return value; + } + esp_err_t err = nvs_get_u16(_handle, key, &value); + if(err){ + ESP_LOGV(__FILE__, "nvs_get_u16 fail: %s %s", key, nvs_error(err)); + } + return value; +} + +int32_t Preferences::getInt(const char* key, const int32_t defaultValue){ + int32_t value = defaultValue; + if(!_started || !key){ + return value; + } + esp_err_t err = nvs_get_i32(_handle, key, &value); + if(err){ + ESP_LOGV(__FILE__, "nvs_get_i32 fail: %s %s", key, nvs_error(err)); + } + return value; +} + +uint32_t Preferences::getUInt(const char* key, const uint32_t defaultValue){ + uint32_t value = defaultValue; + if(!_started || !key){ + return value; + } + esp_err_t err = nvs_get_u32(_handle, key, &value); + if(err){ + ESP_LOGV(__FILE__, "nvs_get_u32 fail: %s %s", key, nvs_error(err)); + } + return value; +} + +int32_t Preferences::getLong(const char* key, const int32_t defaultValue){ + return getInt(key, defaultValue); +} + +uint32_t Preferences::getULong(const char* key, const uint32_t defaultValue){ + return getUInt(key, defaultValue); +} + +int64_t Preferences::getLong64(const char* key, const int64_t defaultValue){ + int64_t value = defaultValue; + if(!_started || !key){ + return value; + } + esp_err_t err = nvs_get_i64(_handle, key, &value); + if(err){ + ESP_LOGV(__FILE__, "nvs_get_i64 fail: %s %s", key, nvs_error(err)); + } + return value; +} + +uint64_t Preferences::getULong64(const char* key, const uint64_t defaultValue){ + uint64_t value = defaultValue; + if(!_started || !key){ + return value; + } + esp_err_t err = nvs_get_u64(_handle, key, &value); + if(err){ + ESP_LOGV(__FILE__, "nvs_get_u64 fail: %s %s", key, nvs_error(err)); + } + return value; +} + +float_t Preferences::getFloat(const char* key, const float_t defaultValue) { + float_t value = defaultValue; + getBytes(key, (void*) &value, sizeof(float_t)); + return value; +} + +double_t Preferences::getDouble(const char* key, const double_t defaultValue) { + double_t value = defaultValue; + getBytes(key, (void*) &value, sizeof(double_t)); + return value; +} + +bool Preferences::getBool(const char* key, const bool defaultValue) { + return getUChar(key, defaultValue ? 1 : 0) == 1; +} + +size_t Preferences::getString(const char* key, char* value, const size_t maxLen){ + size_t len = 0; + if(!_started || !key || !value || !maxLen){ + return 0; + } + esp_err_t err = nvs_get_str(_handle, key, NULL, &len); + if(err){ + ESP_LOGE(__FILE__, "nvs_get_str len fail: %s %s", key, nvs_error(err)); + return 0; + } + if(len > maxLen){ + ESP_LOGE(__FILE__, "not enough space in value: %u < %u", maxLen, len); + return 0; + } + err = nvs_get_str(_handle, key, value, &len); + if(err){ + ESP_LOGE(__FILE__, "nvs_get_str fail: %s %s", key, nvs_error(err)); + return 0; + } + return len; +} + +String Preferences::getString(const char* key, const String defaultValue){ + char * value = NULL; + size_t len = 0; + if(!_started || !key){ + return String(defaultValue); + } + esp_err_t err = nvs_get_str(_handle, key, value, &len); + if(err){ + ESP_LOGE(__FILE__, "nvs_get_str len fail: %s %s", key, nvs_error(err)); + return String(defaultValue); + } + char buf[len]; + value = buf; + err = nvs_get_str(_handle, key, value, &len); + if(err){ + ESP_LOGE(__FILE__, "nvs_get_str fail: %s %s", key, nvs_error(err)); + return String(defaultValue); + } + return String(buf); +} + +size_t Preferences::getBytesLength(const char* key){ + size_t len = 0; + if(!_started || !key){ + return 0; + } + esp_err_t err = nvs_get_blob(_handle, key, NULL, &len); + if(err){ + ESP_LOGE(__FILE__, "nvs_get_blob len fail: %s %s", key, nvs_error(err)); + return 0; + } + return len; +} + +size_t Preferences::getBytes(const char* key, void * buf, size_t maxLen){ + size_t len = getBytesLength(key); + if(!len || !buf || !maxLen){ + return len; + } + if(len > maxLen){ + ESP_LOGE(__FILE__, "not enough space in buffer: %u < %u", maxLen, len); + return 0; + } + esp_err_t err = nvs_get_blob(_handle, key, buf, &len); + if(err){ + ESP_LOGE(__FILE__, "nvs_get_blob fail: %s %s", key, nvs_error(err)); + return 0; + } + return len; +} + +size_t Preferences::freeEntries() { + nvs_stats_t nvs_stats; + esp_err_t err = nvs_get_stats(NULL, &nvs_stats); + if(err){ + ESP_LOGE(__FILE__, "Failed to get nvs statistics"); + return 0; + } + return nvs_stats.free_entries; +} diff --git a/main/Preferences.h b/main/Preferences.h new file mode 100644 index 0000000..5dbcbd4 --- /dev/null +++ b/main/Preferences.h @@ -0,0 +1,77 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _PREFERENCES_H_ +#define _PREFERENCES_H_ + +#include "Arduino.h" + +typedef enum { + PT_I8, PT_U8, PT_I16, PT_U16, PT_I32, PT_U32, PT_I64, PT_U64, PT_STR, PT_BLOB, PT_INVALID +} PreferenceType; + +class Preferences { + protected: + uint32_t _handle; + bool _started; + bool _readOnly; + public: + Preferences(); + ~Preferences(); + + bool begin(const char * name, bool readOnly=false, const char* partition_label=NULL); + void end(); + + bool clear(); + bool remove(const char * key); + + size_t putChar(const char* key, int8_t value); + size_t putUChar(const char* key, uint8_t value); + size_t putShort(const char* key, int16_t value); + size_t putUShort(const char* key, uint16_t value); + size_t putInt(const char* key, int32_t value); + size_t putUInt(const char* key, uint32_t value); + size_t putLong(const char* key, int32_t value); + size_t putULong(const char* key, uint32_t value); + size_t putLong64(const char* key, int64_t value); + size_t putULong64(const char* key, uint64_t value); + size_t putFloat(const char* key, float_t value); + size_t putDouble(const char* key, double_t value); + size_t putBool(const char* key, bool value); + size_t putString(const char* key, const char* value); + size_t putString(const char* key, String value); + size_t putBytes(const char* key, const void* value, size_t len); + + bool isKey(const char* key); + PreferenceType getType(const char* key); + int8_t getChar(const char* key, int8_t defaultValue = 0); + uint8_t getUChar(const char* key, uint8_t defaultValue = 0); + int16_t getShort(const char* key, int16_t defaultValue = 0); + uint16_t getUShort(const char* key, uint16_t defaultValue = 0); + int32_t getInt(const char* key, int32_t defaultValue = 0); + uint32_t getUInt(const char* key, uint32_t defaultValue = 0); + int32_t getLong(const char* key, int32_t defaultValue = 0); + uint32_t getULong(const char* key, uint32_t defaultValue = 0); + int64_t getLong64(const char* key, int64_t defaultValue = 0); + uint64_t getULong64(const char* key, uint64_t defaultValue = 0); + float_t getFloat(const char* key, float_t defaultValue = NAN); + double_t getDouble(const char* key, double_t defaultValue = NAN); + bool getBool(const char* key, bool defaultValue = false); + size_t getString(const char* key, char* value, size_t maxLen); + String getString(const char* key, String defaultValue = String()); + size_t getBytesLength(const char* key); + size_t getBytes(const char* key, void * buf, size_t maxLen); + size_t freeEntries(); +}; + +#endif