From b929f390df1e3511c2f7ca4868e6d76ac782ae50 Mon Sep 17 00:00:00 2001 From: Phil Bowles Date: Wed, 19 Feb 2020 20:55:41 +0100 Subject: [PATCH] v0.3.5 --- README.md | 3 +- changelog.txt | 5 ++ docs/h4stor.md | 89 +++++++++++++++++++ .../H4P_PersistentStorage.ino | 29 ++++++ keywords.txt | 14 ++- library.properties | 2 +- src/H4PCommon.h | 4 +- src/H4P_LocalLogger.cpp | 2 - src/H4P_MQTT.h | 2 - src/H4P_PersistentStorage.cpp | 89 +++++++++++++++++++ src/H4P_PersistentStorage.h | 66 ++++++++++++++ src/H4P_SerialCmd.cpp | 3 + src/H4P_UPNPCommon.cpp | 3 +- src/H4P_WiFi.h | 5 +- src/H4Plugins.h | 1 + 15 files changed, 304 insertions(+), 13 deletions(-) create mode 100644 docs/h4stor.md create mode 100644 examples/H4P_PersistentStorage/H4P_PersistentStorage.ino create mode 100644 src/H4P_PersistentStorage.cpp create mode 100644 src/H4P_PersistentStorage.h diff --git a/README.md b/README.md index b9476dee..c6ecbc5a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ *All plugins depend upon the presence of the [H4 library](https://github.com/philbowles/H4), which must be installed first.* --- -Version **0.3.4** [Release Notes](changelog.txt) **MUST UPGRADE TO [H4 library](https://github.com/philbowles/H4) v0.4.1 first!** +Version **0.3.5** [Release Notes](changelog.txt) **MUST UPGRADE TO [H4 library](https://github.com/philbowles/H4) v0.4.1 first!** ![H4PluginsFF](/assets/h4plugins.jpg) @@ -104,6 +104,7 @@ When you think that H4Plugins also has "plug and play" rotary encoder handling, * [**H4P_UPNPSwitch**](docs/h4upnp.md): Extends [H4P_BinarySwitch](docs/h4onof.md) into full UPNP device with Alexa voice control * [**H4P_UPNPThing**](docs/xxx.md): Extends [H4P_BinaryThing](docs/xxx.md) into full UPNP device with Alexa voice control **NEW in v0.3.4** * [**H4P_ThreeFunctionButton**](docs/h43fnb.md): Multi-function physical control on/off,reboot,factory reset depending on hold time. Binds to xSwitch or xThing +* [**H4P_PersistentStorage**](docs/h4stor.md): Save name/value pairs across reboots (requires SPIFFS) **NEW in v0.3.5** ## Diagnostic / Development tools: diff --git a/changelog.txt b/changelog.txt index 59f717d4..5c12716e 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +2020/02/19 0.3.5 + breaking changes: + h4/factory moved to serialcmd from wifi // update docs! + changed + persistentStorage plugin 2020/02/18 0.3.4 breaking changes: 1. _hasName removed from H4: MUST upgrade to 0.4.1 diff --git a/docs/h4stor.md b/docs/h4stor.md new file mode 100644 index 00000000..bc31963b --- /dev/null +++ b/docs/h4stor.md @@ -0,0 +1,89 @@ +![H4P Flyer](/assets/DiagLogo.jpg) +# Persistent Storage (short name="stor") + +## Adds persistent storage across reboots to H4 Universal Scheduler/Timer (ESP8266 / ESP32 only) + +*All plugins depend upon the presence of the [H4 library](https://github.com/philbowles/H4), which must be installed first.* + +--- +# What does it do? + +Provides a mechanism for storing named / value pairs which are persisted across reboots (via SPIFFS) and automatically available on next boot. + +All saved values will be lost on factory reset. + +--- + +# Usage + +```cpp +#include +H4_USE_PLUGINS +H4P_SerialCmd h4sc; +H4P_persistentStorage h4ps(... +``` + +## Dependencies + +H4P_SerialCmd + +Board must be compiled with an amount of SPIFSS space + +## Commands Added + +* h4/show/stor +* h4/stor/clear (erases all stores values) +* h4/stor/get/x (payload x = name of a stored value) +* h4/stor/set/x,y (payload x = name of a stored value,y=new value) + +## Callbacks + +```cpp +void onChange(string name,string value) // called after stored item changes +``` + +## Unloadable + +NO: + +--- + +# API + +H4P_PersistentStorage overloads the [] operator, so that + +* `h4ps["myvalue"]="some value";` +* `string mv=h4ps["myvalue"];` + +are both valid. + +```cpp +// constructor: +// f = (optional) name of callback function when any named item changes value +H4P_PersistentStorage(H4P_FN_PSCHANGE f=nullptr); + +void clear(); // erase all stored values +void dec(const string& name); // decrement stored int value: no effect on string value +bool exists(const string& name); // true if name has a stored value +string getstring(const string& name); // get named string value +int getint(const string& name); // get named int value +void inc(const string& name) // decrement stored int value: no effect on string value +void setstring(const string& name,const string& value); // set name = value +void setint(const string& name,int value); // set name = value for integers +void showStore(){ for(auto const& p:psRam); // displays all name/value pairs +``` + +[Example code](../examples/H4P_persistentStorage/H4P_persistentStorage.ino) + +---- +(c) 2020 Phil Bowles h4plugins@gmail.com + +* [Youtube channel (instructional videos)](https://www.youtube.com/channel/UCYi-Ko76_3p9hBUtleZRY6g) +* [Blog](https://8266iot.blogspot.com) +* [Facebook Esparto Support / Discussion](https://www.facebook.com/groups/esparto8266/) +* [Facebook H4 Support / Discussion](https://www.facebook.com/groups/444344099599131/) +* [Facebook General ESP8266 / ESP32](https://www.facebook.com/groups/2125820374390340/) +* [Facebook ESP8266 Programming Questions](https://www.facebook.com/groups/esp8266questions/) +* [Facebook IOT with ESP8266 (moderator)}](https://www.facebook.com/groups/1591467384241011/) +* [Facebook ESP Developers (moderator)](https://www.facebook.com/groups/ESP8266/) +* [Support me on Patreon](https://patreon.com/esparto) diff --git a/examples/H4P_PersistentStorage/H4P_PersistentStorage.ino b/examples/H4P_PersistentStorage/H4P_PersistentStorage.ino new file mode 100644 index 00000000..17309388 --- /dev/null +++ b/examples/H4P_PersistentStorage/H4P_PersistentStorage.ino @@ -0,0 +1,29 @@ +#include +H4_USE_PLUGINS + +H4 h4(115200); //auto-start Serial @ 115200, default Q size=20 + +void onChange(const string& name,const string& value){ + Serial.printf("ITEM %s changed to %s\n",CSTR(name),CSTR(value)); +} +H4P_SerialCmd h4sc; +H4P_PersistentStorage h4ps(onChange); + +void h4setup() { + h4ps.setstring("secret","life, the universe and everything"); + h4ps["peasy"]="easy"; //can also set string values like this + string easy=h4ps["peasy"]; // or get them like this + easy=h4ps.getstring("peasy"); // and this + Serial.printf("Using H4P_PersistentStorage is %s\n",CSTR(h4ps["peasy"])); + + if(h4ps.exists("answer")){ + Serial.printf("What is the secret of %s?\n",CSTR(h4ps["secret"])); + h4ps.setint("answer",42); // no short way to handle integers + Serial.println("send h4/reboot to find out"); + } + else { + Serial.printf("Apparently the secret of %s is %d\n",CSTR(h4ps["secret"]),h4ps.getint("answer")); + h4ps.inc("answer"); // increments the value: can also dec it + Serial.println("send h4/reboot repeatedly to see increasingly WRONG answer"); + } +} diff --git a/keywords.txt b/keywords.txt index 07157f8d..c89e503e 100644 --- a/keywords.txt +++ b/keywords.txt @@ -23,6 +23,7 @@ H4P_BinaryThing KEYWORD1 H4P_UPNPSwitch KEYWORD1 H4P_UPNPThing KEYWORD1 H4P_ThreeFunctionButton KEYWORD1 +H4P_PersistentStorage KEYWORD1 H4P_CmdErrors KEYWORD1 H4P_QueueWarn KEYWORD1 H4P_TaskSniffer KEYWORD1 @@ -36,6 +37,15 @@ H4P_MQTTHeapLogger KEYWORD1 # Methods and Functions ( KEYWORD2) ####################################### +showStore KEYWORD2 +dec KEYWORD2 +inc KEYWORD2 +exists KEYWORD2 +getstring KEYWORD2 +getint KEYWORD2 +setstring KEYWORD2 +setint KEYWORD2 +clear KEYWORD2 flashLED KEYWORD2 flashMorse KEYWORD2 flashMorseText KEYWORD2 @@ -122,4 +132,6 @@ H4_CMD_NOT_NUMERIC LITERAL1 H4_CMD_OUT_OF_BOUNDS LITERAL1 H4_CMD_NAME_UNKNOWN LITERAL1 H4_CMD_PAYLOAD_FORMAT LITERAL1 -H4_CMD_PROHIBITED LITERAL1 \ No newline at end of file +H4_CMD_PROHIBITED LITERAL1 + +H4P_FN_PSCHANGE LITERAL1 \ No newline at end of file diff --git a/library.properties b/library.properties index 36a37ba5..065402b6 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=H4Plugins -version=0.3.4 +version=0.3.5 author=Phil Bowles maintainer=Phil Bowles sentence=Adds GPIO handling, WiFi, MQTT, Webserver to H4 timer/scheduler diff --git a/src/H4PCommon.h b/src/H4PCommon.h index fe453121..5d965f37 100644 --- a/src/H4PCommon.h +++ b/src/H4PCommon.h @@ -30,7 +30,7 @@ SOFTWARE. #ifndef H4P_HO #define H4P_HO -#define H4P_VERSION "0.3.4" +#define H4P_VERSION "0.3.5" #include #include @@ -58,6 +58,7 @@ struct command{ using H4_CMD_MAP =std::unordered_multimap; using H4_CMD_MAP_I =H4_CMD_MAP::iterator; using H4P_CONFIG_BLOCK =std::unordered_map; +using H4P_FN_PSCHANGE =function; enum H4_CMD_ERROR:uint32_t { H4_CMD_OK, @@ -108,6 +109,7 @@ STAG(scmd); STAG(snif); STAG(ssid); STAG(state); +STAG(stor); STAG(tfnb); STAG(upnp); STAG(wink); diff --git a/src/H4P_LocalLogger.cpp b/src/H4P_LocalLogger.cpp index 9b0f4af8..4357a078 100644 --- a/src/H4P_LocalLogger.cpp +++ b/src/H4P_LocalLogger.cpp @@ -30,8 +30,6 @@ SOFTWARE. #include // H4P_LocalLogger::H4P_LocalLogger(uint32_t limit): H4PLogService(logTag()), _limit(limit) { - // subid=H4PC_LLOG; -// _names={ {H4P_TRID_LLOG,uppercase(_pid)} }; _local={ {_pid, {H4PC_SHOW, 0, CMD(show)}}, {"clear", {subid, 0, CMD(clear)}}, diff --git a/src/H4P_MQTT.h b/src/H4P_MQTT.h index 861e5143..8a2debde 100644 --- a/src/H4P_MQTT.h +++ b/src/H4P_MQTT.h @@ -64,7 +64,6 @@ class H4P_MQTT: public H4PluginService, public PubSubClient{ _cb["mpasswd"]=pass; _pid=mqttTag(); - //subid=subid; _names={ {H4P_TRID_MQMS,"MQMS"}, @@ -77,7 +76,6 @@ class H4P_MQTT: public H4PluginService, public PubSubClient{ {"grid", { subid, 0, CMD(showGrid) }}, {"offline", { subid, 0, CMDVS(_offline) }}, {"online", { subid, 0, CMDVS(_online) }} -// {"set", { subid, 0, [this](vector vs){ return H4PluginService::_setHandler(vs); }}} }; } void change(const string& broker,uint16_t port); diff --git a/src/H4P_PersistentStorage.cpp b/src/H4P_PersistentStorage.cpp new file mode 100644 index 00000000..b07c8a88 --- /dev/null +++ b/src/H4P_PersistentStorage.cpp @@ -0,0 +1,89 @@ +/* + MIT License + +Copyright (c) 2019 Phil Bowles + github https://github.com/philbowles/esparto + blog https://8266iot.blogspot.com + groups https://www.facebook.com/groups/esp8266questions/ + https://www.facebook.com/Esparto-Esp8266-Firmware-Support-2338535503093896/ + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +#ifndef ARDUINO_ARCH_STM32 +#include +#include + +#define SEPARATOR "\xff" + +void H4P_PersistentStorage::_hookIn() { + + if(H4Plugin::isLoaded(scmdTag())){ + vector items=split(H4P_SerialCmd::read("/"+_pid),SEPARATOR); + for(auto const& i:items){ + vector nv=split(i,"="); + psRam[nv[0]]=nv[1]; + } + H4PluginService::hookFactory([this](){ clear(); }); + } else { DEPENDFAIL(scmd); } +} + +H4P_PersistentStorage::H4P_PersistentStorage(H4P_FN_PSCHANGE f): _f(f){ + _pid=storTag(); + _cmds={ + {_pid, { H4PC_SHOW, 0, CMD(showStore) }}, + {_pid, { H4PC_ROOT, subid, nullptr}}, + {"clear", { subid, 0, CMD(clear)}}, + {"get", { subid, 0, CMDVS(_get)}}, + {"set", { subid, 0, CMDVS(_set)}} + }; +} + +void H4P_PersistentStorage::setstring(const string& name,const string& value){ + if(psRam[name]!=value){ + psRam[name]=value; + // + string items; + for(auto const& p:psRam) items+=p.first+"="+p.second+SEPARATOR; + items.pop_back(); + H4P_SerialCmd::write("/"+_pid,items); + // + if(_f) _f(name,value); + } +} + +uint32_t H4P_PersistentStorage::_get(vector vs){ + return H4Plugin::guard1(vs,[this](vector vs){ + if(psRam.count(PAYLOAD)) _showItem(PAYLOAD); + return H4_CMD_OK; + }); +} + +uint32_t H4P_PersistentStorage::_set(vector vs){ + return guardString2(vs,[this](string a,string b){ + psRam[a]=b; + _showItem(a); // + }); +} + +void H4P_PersistentStorage::clear(){ + psRam.clear(); + SPIFFS.remove(CSTR(string("/"+_pid))); +} +#endif \ No newline at end of file diff --git a/src/H4P_PersistentStorage.h b/src/H4P_PersistentStorage.h new file mode 100644 index 00000000..83c1dd93 --- /dev/null +++ b/src/H4P_PersistentStorage.h @@ -0,0 +1,66 @@ +/* + MIT License + +Copyright (c) 2019 Phil Bowles + github https://github.com/philbowles/H4 + blog https://8266iot.blogspot.com + groups https://www.facebook.com/groups/esp8266questions/ + https://www.facebook.com/H4-Esp8266-Firmware-Support-2338535503093896/ + + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ +#ifndef H4P_PersistentStorage_HO +#define H4P_PersistentStorage_HO + +#ifndef ARDUINO_ARCH_STM32 +#include + +class H4P_PersistentStorage: public H4Plugin { + H4P_CONFIG_BLOCK psRam={}; + H4P_FN_PSCHANGE _f; + + VSCMD(_get); + VSCMD(_set); + + void _hookIn() override; + void _showItem(const string& n){ reply("%s=%s\n",CSTR(n),CSTR(psRam[n])); } + public: + string& operator[](const string& name){ + return psRam[name]; + } + + H4P_PersistentStorage(H4P_FN_PSCHANGE f=nullptr); + + void clear(); + void dec(const string& name){ if(isNumeric(psRam[name])) setint(name,getint(name)-1); } + bool exists(const string& name){ return psRam.count(name); } + string getstring(const string& name){ return psRam[name]; } + int getint(const string& name){ return atoi(CSTR(psRam[name])); } + void inc(const string& name){ if(isNumeric(psRam[name])) setint(name,getint(name)+1); } + void setstring(const string& name,const string& value); + void setint(const string& name,int value){ setstring(name,stringFromInt(value)); } + void showStore(){ for(auto const& p:psRam) _showItem(p.first); } +}; + +extern __attribute__((weak)) H4P_PersistentStorage h4ps; + +#endif +#endif // H4P_PersistentStorage_H \ No newline at end of file diff --git a/src/H4P_SerialCmd.cpp b/src/H4P_SerialCmd.cpp index 706afa80..eaf50e5e 100644 --- a/src/H4P_SerialCmd.cpp +++ b/src/H4P_SerialCmd.cpp @@ -28,6 +28,8 @@ SOFTWARE. */ #include +extern void h4FactoryReset(); + H4_CMD_MAP_I H4P_SerialCmd::__exactMatch(const string& cmd,uint32_t owner){ auto any=commands.equal_range(cmd); for(auto i=any.first;i!=any.second;i++) if(i->second.owner==owner) return i; @@ -159,6 +161,7 @@ H4P_SerialCmd::H4P_SerialCmd(){ {"h4", { 0, H4PC_ROOT, nullptr}}, {"help", { 0, 0, CMD(help) }}, {"reboot", { H4PC_ROOT, 0, CMD(h4reboot) }}, + {"factory", { H4PC_ROOT, 0, CMD(h4FactoryReset) }}, {"show", { H4PC_ROOT, H4PC_SHOW, nullptr}}, {"all", { H4PC_SHOW, 0, CMD(all) }}, {"config", { H4PC_SHOW, 0, CMD(config) }}, diff --git a/src/H4P_UPNPCommon.cpp b/src/H4P_UPNPCommon.cpp index 564ee5d0..8af8934d 100644 --- a/src/H4P_UPNPCommon.cpp +++ b/src/H4P_UPNPCommon.cpp @@ -57,8 +57,7 @@ void H4P_UPNPCommon::friendlyName(const string& name){ h4wifi.setPersistentValu uint32_t H4P_UPNPCommon::_friendly(vector vs){ return H4Plugin::guard1(vs,[this](vector vs){ - //h4wifi.setPersistentValue(nameTag(),PAYLOAD,true); - Serial.printf("WOULD FRIEND %s\n",CSTR(PAYLOAD)); + h4wifi.setPersistentValue(nameTag(),PAYLOAD,true); return H4_CMD_OK; }); } diff --git a/src/H4P_WiFi.h b/src/H4P_WiFi.h index d3c672ea..654a2288 100644 --- a/src/H4P_WiFi.h +++ b/src/H4P_WiFi.h @@ -36,7 +36,7 @@ SOFTWARE. #ifndef ARDUINO_ARCH_STM32 #include -extern void h4FactoryReset(); +//extern void h4FactoryReset(); class H4P_WiFi: public H4PluginService{ DNSServer* _dnsServer; @@ -64,14 +64,13 @@ class H4P_WiFi: public H4PluginService{ _pid=wifiTag(); _names={ -// {H4P_TRID_WIFI,uppercase(_pid)}, {H4P_TRID_WFAP,"WFAP"}, {H4P_TRID_HOTA,"HOTA"} }; _local={ {"clear", { subid, 0, CMD(clear)}}, - {"factory", { H4PC_ROOT, 0, CMD(h4FactoryReset) }}, +// {"factory", { H4PC_ROOT, 0, CMD(h4FactoryReset) }}, {"change", { subid, 0, CMDVS(_change)}}, {"host", { subid, 0, CMDVS(_host)}}, {_pid, { H4PC_SHOW, 0, CMD(show) }} diff --git a/src/H4Plugins.h b/src/H4Plugins.h index 24c3f0c3..4368f0dc 100644 --- a/src/H4Plugins.h +++ b/src/H4Plugins.h @@ -3,6 +3,7 @@ #include #include +#include #include #include //#include