#pragma once #include #include #include #include #include #include #include template class ProtoEventStorage { public: ProtoEventStorage(const char* filename, const pb_msgdesc_t* descriptor, size_t maxSize, uint32_t debounceMs = 1000) : _filename(filename), _descriptor(descriptor), _maxSize(maxSize), _debounceMs(debounceMs) {} void begin() { ProtoMsg loaded {}; loadOrDefault(loaded); EventBus::instance().publish(loaded); esp_timer_create_args_t timerArgs {}; timerArgs.callback = timerCallback; timerArgs.arg = this; timerArgs.dispatch_method = ESP_TIMER_TASK; timerArgs.name = _filename; esp_timer_create(&timerArgs, &_timer); _handle = EventBus::instance().subscribe([this](const ProtoMsg& msg) { _pending = msg; esp_timer_stop(_timer); esp_timer_start_once(_timer, static_cast(_debounceMs) * 1000); }); } private: const char* _filename; const pb_msgdesc_t* _descriptor; size_t _maxSize; uint32_t _debounceMs; SubscriptionHandle _handle; ProtoMsg _pending {}; esp_timer_handle_t _timer = nullptr; static void timerCallback(void* arg) { auto* self = static_cast(arg); self->save(self->_pending); } void loadOrDefault(ProtoMsg& outMsg) { FILE* file = fopen(_filename, "rb"); if (!file) { ESP_LOGI("ProtoStorage", "No file %s, using defaults", _filename); outMsg = DefaultsFn(); return; } fseek(file, 0, SEEK_END); long tellResult = ftell(file); fseek(file, 0, SEEK_SET); if (tellResult < 0) { ESP_LOGE("ProtoStorage", "ftell failed for %s", _filename); fclose(file); outMsg = DefaultsFn(); return; } size_t size = static_cast(tellResult); if (size == 0 || size > _maxSize) { ESP_LOGW("ProtoStorage", "Invalid size %zu for %s (max %zu)", size, _filename, _maxSize); fclose(file); outMsg = DefaultsFn(); return; } auto buffer = std::make_unique(size); size_t read = fread(buffer.get(), 1, size, file); fclose(file); if (read != size) { ESP_LOGE("ProtoStorage", "Read %zu/%zu from %s", read, size, _filename); outMsg = DefaultsFn(); return; } pb_istream_t stream = pb_istream_from_buffer(buffer.get(), size); if (!pb_decode(&stream, _descriptor, &outMsg)) { ESP_LOGE("ProtoStorage", "Decode failed for %s", _filename); outMsg = DefaultsFn(); } } void save(const ProtoMsg& msg) { auto buffer = std::make_unique(_maxSize); pb_ostream_t stream = pb_ostream_from_buffer(buffer.get(), _maxSize); if (!pb_encode(&stream, _descriptor, &msg)) { ESP_LOGE("ProtoStorage", "Encode failed for %s", _filename); return; } FILE* file = fopen(_filename, "wb"); if (!file) { ESP_LOGE("ProtoStorage", "Open write failed for %s", _filename); return; } size_t written = fwrite(buffer.get(), 1, stream.bytes_written, file); fclose(file); if (written != stream.bytes_written) { ESP_LOGE("ProtoStorage", "Write failed for %s (%zu/%zu)", _filename, written, stream.bytes_written); } } };