PBW.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. /*
  2. *
  3. * Sebastien L. 2023, sle118@hotmail.com
  4. * Philippe G. 2023, philippe_44@outlook.com
  5. *
  6. * This software is released under the MIT License.
  7. * https://opensource.org/licenses/MIT
  8. *
  9. * License Overview:
  10. * ----------------
  11. * The MIT License is a permissive open source license. As a user of this software, you are free to:
  12. * - Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software.
  13. * - Use the software for private, commercial, or any other purposes.
  14. *
  15. * Conditions:
  16. * - You must include the above copyright notice and this permission notice in all
  17. * copies or substantial portions of the Software.
  18. *
  19. * The MIT License offers a high degree of freedom and is well-suited for both open source and
  20. * commercial applications. It places minimal restrictions on how the software can be used,
  21. * modified, and redistributed. For more details on the MIT License, please refer to the link above.
  22. */
  23. #pragma once
  24. #ifndef LOG_LOCAL_LEVEL
  25. #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
  26. #endif
  27. #include "Locking.h"
  28. #include "MessageDefinition.pb.h"
  29. #include "accessors.h"
  30. #include "configuration.pb.h"
  31. #include "esp_log.h"
  32. #include "freertos/FreeRTOS.h"
  33. #include "freertos/event_groups.h"
  34. #include "network_manager.h"
  35. #include "pb.h"
  36. #include "pb_decode.h" // Nanopb header for decoding (deserialization)
  37. #include "pb_encode.h" // Nanopb header for encoding (serialization)
  38. #include "tools.h"
  39. #include "tools_spiffs_utils.h"
  40. #include "bootstate.h"
  41. #include "State.pb.h"
  42. #ifdef __cplusplus
  43. // #include <functional>
  44. #include <sstream>
  45. #include <vector>
  46. namespace System {
  47. template <typename T> struct has_target_implementation : std::false_type {};
  48. class FileNotFoundException : public std::runtime_error {
  49. public:
  50. explicit FileNotFoundException(const std::string& message) : std::runtime_error(message) {}
  51. };
  52. class DecodeError : public std::runtime_error {
  53. public:
  54. explicit DecodeError(const std::string& message) : std::runtime_error(message) {}
  55. };
  56. class IPBBase {
  57. public:
  58. virtual void CommitChanges() = 0;
  59. virtual ~IPBBase() {}
  60. };
  61. class PBHelper : public IPBBase {
  62. protected:
  63. const pb_msgdesc_t* _fields;
  64. EventGroupHandle_t _group;
  65. std::string name;
  66. std::string filename;
  67. static const int MaxDelay = 1000;
  68. Locking _lock;
  69. size_t _datasize;
  70. bool _no_save;
  71. public:
  72. enum class Flags { COMMITTED = BIT0, LOAD = BIT1 };
  73. bool SetGroupBit(Flags flags, bool flag);
  74. static const char* PROTOTAG;
  75. std::string& GetName() { return name; }
  76. const char* GetCName() { return name.c_str(); }
  77. static std::string GetDefFileName(std::string name){
  78. return std::string(spiffs_base_path) + "/data/def_" + name + ".bin";
  79. }
  80. std::string GetDefFileName(){
  81. return GetDefFileName(this->name);
  82. }
  83. size_t GetDataSize(){
  84. return _datasize;
  85. }
  86. PBHelper(std::string name, const pb_msgdesc_t* fields, size_t defn_size, size_t datasize, bool no_save = false)
  87. : _fields(fields), _group(xEventGroupCreate()), name(std::move(name)), _lock(this->name),_datasize(datasize), _no_save(no_save) {
  88. sys_message_def definition = sys_message_def_init_default;
  89. bool savedef = false;
  90. ESP_LOGD(PROTOTAG,"Getting definition file name");
  91. auto deffile = GetDefFileName();
  92. ESP_LOGD(PROTOTAG,"Instantiating with definition size %d and data size %d", defn_size,datasize);
  93. try {
  94. PBHelper::LoadFile(deffile, &sys_message_def_msg, static_cast<void*>(&definition));
  95. if (definition.data->size != defn_size || definition.datasize != _datasize) {
  96. ESP_LOGW(PROTOTAG, "Structure definition %s has changed", this->name.c_str());
  97. if (!is_recovery_running) {
  98. savedef = true;
  99. pb_release(&sys_message_def_msg, &definition);
  100. } else {
  101. ESP_LOGW(PROTOTAG, "Using existing definition for recovery");
  102. _fields = reinterpret_cast<const pb_msgdesc_t*>(definition.data->bytes);
  103. _datasize = definition.datasize;
  104. }
  105. }
  106. } catch (const FileNotFoundException& e) {
  107. savedef = true;
  108. }
  109. if (savedef) {
  110. ESP_LOGW(PROTOTAG, "Saving definition for structure %s", this->name.c_str());
  111. auto data = (pb_bytes_array_t*)malloc_init_external(sizeof(pb_bytes_array_t)+defn_size);
  112. memcpy(&data->bytes, fields, defn_size);
  113. data->size = defn_size;
  114. definition.data = data;
  115. definition.datasize = _datasize;
  116. ESP_LOGD(PROTOTAG,"Committing structure with %d bytes ",data->size);
  117. PBHelper::CommitFile(deffile, &sys_message_def_msg, &definition);
  118. ESP_LOGD(PROTOTAG,"Releasing memory");
  119. free(data);
  120. }
  121. }
  122. void ResetModified();
  123. static void SyncCommit(void* protoWrapper);
  124. static void CommitFile(const std::string& filename, const pb_msgdesc_t* fields, const void* src_struct);
  125. static bool IsDataDifferent(const pb_msgdesc_t* fields, const void* src_struct, const void* other_struct);
  126. static void CopyStructure(const void* src_data, const pb_msgdesc_t* fields, void* target_data);
  127. static void LoadFile(const std::string& filename, const pb_msgdesc_t* fields, void* target_data, bool noinit = false);
  128. static std::vector<pb_byte_t> EncodeData(const pb_msgdesc_t* fields, const void* src_struct);
  129. static void DecodeData(std::vector<pb_byte_t> data, const pb_msgdesc_t* fields, void* target, bool noinit = false);
  130. bool FileExists(std::string filename);
  131. bool FileExists();
  132. bool IsLoading();
  133. void SetLoading(bool active);
  134. bool WaitForCommit(uint8_t retries );
  135. void RaiseChangedAsync();
  136. const std::string& GetFileName();
  137. bool HasChanges();
  138. };
  139. template <typename T> class PB : public PBHelper {
  140. private:
  141. T *_root;
  142. // Generic _setTarget implementation
  143. void _setTarget(std::string target, std::false_type) { ESP_LOGE(PROTOTAG, "Setting target not implemented for %s", name.c_str()); }
  144. // Special handling for sys_config
  145. void _setTarget(std::string target, std::true_type) {
  146. if (_root->target) {
  147. free(_root->target);
  148. }
  149. _root->target = strdup_psram(target.c_str());
  150. }
  151. std::string _getTargetName(std::false_type) { return ""; }
  152. std::string _getTargetName(std::true_type) { return STR_OR_BLANK(_root->target); }
  153. public:
  154. // Accessor for the underlying structure
  155. T& Root() { return *_root; }
  156. // Const accessor for the underlying structure
  157. const T& Root() const { return *_root; }
  158. T* get() { return _root; }
  159. const T* get() const { return (const T*)_root; }
  160. // Constructor
  161. explicit PB(std::string name, const pb_msgdesc_t* fields, size_t defn_size, bool no_save = false) :
  162. PBHelper(std::move(name), fields,defn_size, sizeof(T), no_save) {
  163. ESP_LOGD(PROTOTAG, "Instantiating PB class for %s with data size %d", this->name.c_str(), sizeof(T));
  164. ResetModified();
  165. filename = std::string(spiffs_base_path) + "/data/" + this->name + ".bin";
  166. _root = (T*)(malloc_init_external(_datasize));
  167. memset(_root, 0x00, sizeof(_datasize));
  168. }
  169. std::string GetTargetName() { return _getTargetName(has_target_implementation<T>{}); }
  170. void SetTarget(std::string targetname, bool skip_commit = false) {
  171. std::string newtarget = trim(targetname);
  172. std::string currenttarget = trim(GetTargetName());
  173. ESP_LOGD(PROTOTAG, "SetTarget called with %s", newtarget.c_str());
  174. if (newtarget == currenttarget && !newtarget.empty()) {
  175. ESP_LOGD(PROTOTAG, "Target name %s not changed for %s", currenttarget.c_str(), name.c_str());
  176. } else if (newtarget.empty() && !currenttarget.empty()) {
  177. ESP_LOGW(PROTOTAG, "Target name %s was removed for %s ", currenttarget.c_str(), name.c_str());
  178. }
  179. ESP_LOGI(PROTOTAG, "Setting target %s for %s", newtarget.c_str(), name.c_str());
  180. _setTarget(newtarget, has_target_implementation<T>{});
  181. if (!skip_commit) {
  182. ESP_LOGD(PROTOTAG, "Raising changed flag to commit new target name.");
  183. RaiseChangedAsync();
  184. } else {
  185. SetGroupBit(Flags::COMMITTED, false);
  186. }
  187. }
  188. std::string GetTargetFileName() {
  189. if (GetTargetName().empty()) {
  190. return "";
  191. }
  192. auto target_name = GetTargetName();
  193. return std::string(spiffs_base_path) + "/targets/" + toLowerStr(target_name) + "/" + name + ".bin";
  194. }
  195. void Reinitialize(bool skip_target = false, bool commit = false, std::string target_name = "") {
  196. ESP_LOGW(PROTOTAG, "Initializing %s", name.c_str());
  197. pb_istream_t stream = PB_ISTREAM_EMPTY;
  198. // initialize blank structure by
  199. // decoding a dummy stream
  200. pb_decode(&stream, _fields, _root);
  201. SetLoading(true);
  202. try {
  203. std::string fullpath = std::string(spiffs_base_path) + "/defaults/" + this->name + ".bin";
  204. ESP_LOGD(PROTOTAG, "Attempting to load defaults file for %s", fullpath.c_str());
  205. PBHelper::LoadFile(fullpath.c_str(), _fields, static_cast<void*>(_root), true);
  206. } catch (FileNotFoundException&) {
  207. ESP_LOGW(PROTOTAG, "No defaults found for %s", name.c_str());
  208. } catch (std::runtime_error& e) {
  209. ESP_LOGE(PROTOTAG, "Error loading Target %s overrides file: %s", GetTargetName().c_str(), e.what());
  210. }
  211. SetLoading(false);
  212. if (!skip_target) {
  213. if (!target_name.empty()) {
  214. SetTarget(target_name, true);
  215. }
  216. LoadTargetValues();
  217. }
  218. if (commit) {
  219. CommitChanges();
  220. }
  221. }
  222. void LoadFile(bool skip_target = false, bool noinit = false) {
  223. SetLoading(true);
  224. PBHelper::LoadFile(filename, _fields, static_cast<void*>(_root), noinit);
  225. SetLoading(false);
  226. if (!skip_target) {
  227. LoadTargetValues();
  228. }
  229. }
  230. void LoadTargetValues() {
  231. ESP_LOGD(PROTOTAG, "Loading target %s values for %s", GetTargetName().c_str(), name.c_str());
  232. if (GetTargetFileName().empty()) {
  233. ESP_LOGD(PROTOTAG, "No target file to load for %s", name.c_str());
  234. return;
  235. }
  236. try {
  237. // T old;
  238. // CopyTo(old);
  239. ESP_LOGI(PROTOTAG, "Loading target %s values for %s", GetTargetName().c_str(), name.c_str());
  240. PBHelper::LoadFile(GetTargetFileName(), _fields, static_cast<void*>(_root), true);
  241. // don't commit the values here, as it doesn't work well with
  242. // repeated values
  243. // if (*this != old) {
  244. // ESP_LOGI(PROTOTAG, "Changes detected from target values.");
  245. // RaiseChangedAsync();
  246. // }
  247. SetGroupBit(Flags::COMMITTED, false);
  248. } catch (FileNotFoundException&) {
  249. ESP_LOGD(PROTOTAG, "Target %s overrides file not found for %s", GetTargetName().c_str(), name.c_str());
  250. } catch (std::runtime_error& e) {
  251. ESP_LOGE(PROTOTAG, "Error loading Target %s overrides file: %s", GetTargetName().c_str(), e.what());
  252. }
  253. }
  254. void CommitChanges() override {
  255. ESP_LOGI(PROTOTAG, "Committing %s to flash.", name.c_str());
  256. if (!_lock.Lock()) {
  257. ESP_LOGE(PROTOTAG, "Unable to lock config for commit ");
  258. return;
  259. }
  260. ESP_LOGV(PROTOTAG, "Config Locked. Committing");
  261. try {
  262. CommitFile(filename, _fields, _root);
  263. } catch (...) {
  264. }
  265. ResetModified();
  266. _lock.Unlock();
  267. ESP_LOGI(PROTOTAG, "Done committing %s to flash.", name.c_str());
  268. }
  269. bool Lock() { return _lock.Lock(); }
  270. void Unlock() { return _lock.Unlock(); }
  271. std::vector<pb_byte_t> Encode() {
  272. auto data = std::vector<pb_byte_t>();
  273. if (!_lock.Lock()) {
  274. throw std::runtime_error("Unable to lock object");
  275. }
  276. data = EncodeData(_fields, this->_root);
  277. _lock.Unlock();
  278. return data;
  279. }
  280. void CopyTo(T& target_data) {
  281. if (!_lock.Lock()) {
  282. ESP_LOGE(PROTOTAG, "Lock failed for %s", name.c_str());
  283. throw std::runtime_error("Lock failed ");
  284. }
  285. CopyStructure(_root, _fields, &target_data);
  286. _lock.Unlock();
  287. }
  288. void CopyFrom(const T& source_data) {
  289. if (!_lock.Lock()) {
  290. ESP_LOGE(PROTOTAG, "Lock failed for %s", name.c_str());
  291. throw std::runtime_error("Lock failed ");
  292. }
  293. CopyStructure(&source_data, _fields, _root);
  294. _lock.Unlock();
  295. }
  296. bool operator!=(const T& other) const { return IsDataDifferent(_fields, _root, &other); }
  297. bool operator==(const T& other) const { return !IsDataDifferent(_fields, _root, &other); }
  298. void DecodeData(const std::vector<pb_byte_t> data, bool noinit = false) { PBHelper::DecodeData(data, _fields, (void*)_root, noinit); }
  299. ~PB() { vEventGroupDelete(_group); };
  300. };
  301. template <> struct has_target_implementation<sys_config> : std::true_type {};
  302. template <> struct has_target_implementation<sys_state_data> : std::true_type {};
  303. } // namespace PlatformConfig
  304. extern "C" {
  305. #endif
  306. bool proto_load_file(const char* filename, const pb_msgdesc_t* fields, void* target_data, bool noinit);
  307. #ifdef __cplusplus
  308. }
  309. #endif