/
singlwolf
/
Radiola-2S3
Обзор
Документация
Войти
/
singlwolf
/
Radiola-2S3
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/eeStorage.cpp
1 628 строк
40 KB
SinglWolf
Release 1.0.1 HotFix
01 апр 2026, 03:31
01 апр 2026, 03:31
26e6f5c
Код
Авторство
О чём код?
/** * data_storage.cpp * @author SinglWolf * @description Radiola-2 * @created 2022.07.28 06=45=15.094 * @copyright Radiola-2 © 2022 SinglWolf * * @telegram https=//t.me/radiola_2 * @email singlwolf@gmail.com * @last-modified 2022.07.28 06=45=28.189 */ // (doc(?:\["[^"]+"\]|\[\d+\])+)\s*=\s*(Config->[\w.]+(?:\[\d+\])?) // $2 = $1 #include "pinout.h" #include "eeStorage.h" #include <fs_system.h> #include "controls.h" #include "esp_mac.h" #include "at24c32.h" #include <DS3231.h> #include "Wire.h" // #include "nvs_flash.h" #include "net_work.h" #include "web_server.h" #define CURRENT_SPACE "current" #define CONFIG_SPACE "config" #define WIFI_SPACE "wifi" #define PART_HARDWARE "eeprom" #define MAGIC_WORD 0xFEFE // #define EEPROM_ADDRESS (0x57) // #define EEPROM_SIZE 0x1000U // #define WIFI_CRC_OFFSET 0x0000U // #define WIFI_LEN_OFFSET 0x0004U // 2 байта под длину (смещение 4) #define MAGIC_OFFSET 0x0006U // #define WIFI_OFFSET 0x0008U // Данные начинаются с адреса 8 #define CONF_CRC_OFFSET 0x0200U // #define MAX_WIFI_SIZE (CONF_CRC_OFFSET - WIFI_OFFSET) - 1U // Размер данных для WIFI и DS3231 #define CONF_LEN_OFFSET (CONF_CRC_OFFSET + 4U) // 2 байта под длину (смещение 4) #define CONF_OFFSET (CONF_CRC_OFFSET + 8U) // Данные начинаются с адреса 8 #define CONF_OFFSET_END (EEPROM_SIZE - 1U) // Данные заканчиваются адресом 512 #define MAX_CONF_SIZE (CONF_OFFSET_END - CONF_OFFSET) // Размер данных для Config // #define txtPrintInfo(fmt, ...) \ do \ { \ printf(LOG_COLOR_I); \ printf(fmt, ##__VA_ARGS__); \ printf(LOG_RESET_COLOR "\n"); \ } while (0) // #define txtPrintWarn(fmt, ...) \ do \ { \ printf(LOG_COLOR_W); \ printf(fmt, ##__VA_ARGS__); \ printf(LOG_RESET_COLOR "\n"); \ } while (0) // #define txtPrintError(fmt, ...) \ do \ { \ printf(LOG_COLOR_E); \ printf(fmt, ##__VA_ARGS__); \ printf(LOG_RESET_COLOR "\n"); \ } while (0) // static wifi_ wifi; static config_ config; static current_ current; static current_ copycurrent; config_ *Config = nullptr; wifi_ *ee = nullptr; uint8_t dark_theme = 0; uint16_t magicWord = 0; // struct SpiRamAllocator : ArduinoJson::Allocator { void *allocate(size_t size) override { return heap_caps_malloc(size, MALLOC_CAP_SPIRAM); } void deallocate(void *pointer) override { heap_caps_free(pointer); } void *reallocate(void *ptr, size_t new_size) override { return heap_caps_realloc(ptr, new_size, MALLOC_CAP_SPIRAM); } }; // SpiRamAllocator allocator; // JsonDocument Config(&allocator); JsonDocument IR_KEY(&allocator); JsonDocument KBD_BTN(&allocator); // // Глобальный экземпляр eeStorage eeprom; DS3231 rtc; BME280I2C *bme = nullptr; // TDA7313 *tda = nullptr; AT24C32 at24c(EEPROM_ADDRESS); struct StationsPS { StringPS name; StringPS logo; StringPS uri; }; // Вектор для хранения станций в PSRAM std::vector<StationsPS, PSRAMAllocator<StationsPS>> stations; StationsPS station; // Конструктор eeStorage::eeStorage() : _initialized(false), _usingExternal(false) { eeNVS = xSemaphoreCreateMutex(); eeI2C = xSemaphoreCreateMutex(); if ((eeI2C == nullptr) || (eeNVS == nullptr)) { txtPrintError("Failed to create _ee mutexs!"); while (true) { delay(10); } } } void eeStorage::begin(bool sd) { Wire.begin(PIN_SDA, PIN_SCL); Wire.beginTransmission(EEPROM_ADDRESS); if (!Wire.endTransmission()) { Wire.setBufferSize(2048); _usingExternal = true; _initialized = true; txtPrintInfo("AT24C32 OK."); } else { _usingExternal = false; } if (_preferences.begin(CURRENT_SPACE, false, PART_HARDWARE)) { _preferences.end(); if (!_usingExternal) { _initialized = true; txtPrintInfo("NVS OK."); } } else { txtPrintError("Failed to initialize NVS storage!"); while (true) { delay(10); } } initEEdata(); if (FileFS->exists(PATH_CONFIG)) { FileFS->remove(PATH_CONFIG); } InitConfig(); InitIrcodes(); InitKeyboard(); if (Config->sd != sd) { Config->sd = sd; SaveConfig(); } InitCurrConfig(); InitPlaylist(); if (Config->i2c.tda7313) { tda->init_nvs(eeNVS); tda->load_nvs(); } } // Метод сохранения DeserializationError eeStorage::save(const char *name, const JsonDocument &doc) { std::string jsonString; serializeJson(doc, jsonString); size_t dataLen = measureJson(doc); if (_usingExternal) { size_t max_size; uint32_t crc_offset; uint16_t len_offset; uint16_t address; if (strcmp(name, "wifi") == 0) { max_size = MAX_WIFI_SIZE; crc_offset = WIFI_CRC_OFFSET; len_offset = WIFI_LEN_OFFSET; address = WIFI_OFFSET; } else { max_size = MAX_CONF_SIZE; crc_offset = CONF_CRC_OFFSET; len_offset = CONF_LEN_OFFSET; address = CONF_OFFSET; } if (dataLen > max_size) { txtPrintError("JSON '%s' too large: %u bytes (max %u)\n", name, dataLen, MAX_WIFI_SIZE); return DeserializationError::NoMemory; } const auto *data = (const uint8_t *)jsonString.c_str(); uint32_t crc = calculateCRC(data, dataLen); auto len = (uint16_t)dataLen; // Запись CRC (4 байта) xSemaphoreTake(eeI2C, portMAX_DELAY); uint32_t bytesWritten = at24c.put((int)crc_offset, crc); xSemaphoreGive(eeI2C); if (bytesWritten != crc) { txtPrintError("Failed to write CRC to EEPROM"); return DeserializationError::InvalidInput; } // Запись длины (2 байта) xSemaphoreTake(eeI2C, portMAX_DELAY); bytesWritten = at24c.put(len_offset, len); xSemaphoreGive(eeI2C); if (bytesWritten != len) { txtPrintError("Failed to write length to EEPROM"); return DeserializationError::InvalidInput; } // Запись данных xSemaphoreTake(eeI2C, portMAX_DELAY); bytesWritten = at24c.writeBuffer(address, (uint8_t *)data, dataLen); xSemaphoreGive(eeI2C); if (bytesWritten != dataLen) { txtPrintError("Failed to write data to EEPROM (wrote %u of %u)\n", bytesWritten, dataLen); return DeserializationError::InvalidInput; } } else { xSemaphoreTake(eeNVS, portMAX_DELAY); _preferences.begin(name, false, PART_HARDWARE); size_t len = _preferences.putString(name, jsonString.c_str()); txtPrintInfo("Saved %u bytes to NVS", len); txtPrintInfo("Str:\n%s", jsonString.c_str()); _preferences.end(); xSemaphoreGive(eeNVS); if (!len) { txtPrintError("Failed to save data to NVS"); return DeserializationError::InvalidInput; } } return DeserializationError::Ok; } // Расчёт CRC32 uint32_t eeStorage::calculateCRC(const uint8_t *data, size_t length) { uint32_t crc = 0xFFFFFFFF; for (size_t i = 0; i < length; ++i) { crc ^= data[i]; for (int j = 0; j < 8; ++j) { if (crc & 1) { crc = (crc >> 1) ^ 0xEDB88320; } else { crc >>= 1; } } } return crc ^ 0xFFFFFFFF; } // Метод загрузки DeserializationError eeStorage::load(const char *name, JsonDocument &doc) { std::string jsonString; if (_usingExternal) { size_t max_size; uint32_t crc_offset; uint16_t len_offset; uint16_t address; if (strcmp(name, "wifi") == 0) { max_size = MAX_WIFI_SIZE; crc_offset = WIFI_CRC_OFFSET; len_offset = WIFI_LEN_OFFSET; address = WIFI_OFFSET; } else { max_size = MAX_CONF_SIZE; crc_offset = CONF_CRC_OFFSET; len_offset = CONF_LEN_OFFSET; address = CONF_OFFSET; } uint16_t storedLen; xSemaphoreTake(eeI2C, portMAX_DELAY); at24c.get(len_offset, storedLen); xSemaphoreGive(eeI2C); if (storedLen == 0) { return DeserializationError::EmptyInput; } else if (storedLen > max_size) { txtPrintError("Stored '%s' length too large: %u bytes\n", name, storedLen); return DeserializationError::InvalidInput; } uint32_t storedCRC; xSemaphoreTake(eeI2C, portMAX_DELAY); at24c.get((int)crc_offset, storedCRC); // uint8_t buffer[storedLen]; // auto* buffer = (uint8_t*)calloc(storedLen, sizeof(uint8_t)); std::vector<uint8_t> buffer(storedLen, 0); size_t bytesRead = at24c.readBuffer(address, buffer.data(), storedLen); xSemaphoreGive(eeI2C); if (bytesRead != storedLen) { txtPrintError("Failed to read full data: got %u, expected %u\n", bytesRead, storedLen); return DeserializationError::InvalidInput; } uint32_t calcCRC = calculateCRC(buffer.data(), bytesRead); if (storedCRC != calcCRC) { txtPrintError("EEPROM CRC mismatch: stored=0x%08X, calc=0x%08X\n", storedCRC, calcCRC); return DeserializationError::InvalidInput; } jsonString.assign((char *)buffer.data(), bytesRead); } else { xSemaphoreTake(eeNVS, portMAX_DELAY); _preferences.begin(name, true, PART_HARDWARE); jsonString = _preferences.getString(name, "").c_str(); _preferences.end(); xSemaphoreGive(eeNVS); if (jsonString.length() == 0) { return DeserializationError::EmptyInput; } } return deserializeJson(doc, jsonString); } // bool eeStorage::erase() { if (_usingExternal) { txtPrintWarn("Erasing EEPROM..."); uint16_t zeroLength = 0; xSemaphoreTake(eeI2C, portMAX_DELAY); at24c.put(WIFI_LEN_OFFSET, zeroLength); at24c.put(CONF_LEN_OFFSET, zeroLength); uint16_t mg = MAGIC_WORD; at24c.put(MAGIC_OFFSET, mg); xSemaphoreGive(eeI2C); txtPrintInfo("Erasing EEPROM... success."); } else { txtPrintWarn("Erasing NVS..."); xSemaphoreTake(eeNVS, portMAX_DELAY); _preferences.begin(WIFI_SPACE, false, PART_HARDWARE); _preferences.clear(); _preferences.putUShort("magic", MAGIC_WORD); _preferences.putString(WIFI_SPACE, ""); _preferences.end(); _preferences.begin(CONFIG_SPACE, false, PART_HARDWARE); _preferences.clear(); _preferences.putString(CONFIG_SPACE, ""); _preferences.end(); xSemaphoreGive(eeNVS); txtPrintInfo("Erasing NVS... success."); } return true; } // void eeStorage::InitConfig(bool def, StringPS *conf) { bool save = false; if (magicWord != MAGIC_WORD) { def = true; } if (Config == nullptr) { Config = &config; } JsonDocument doc(&allocator); DeserializationError _err = DeserializationError::Ok; if (conf != nullptr) { _err = deserializeJson(doc, *conf); save = true; } else { _err = load(CONFIG_SPACE, doc); } if (_err == _err.EmptyInput) { def = true; } else if (_err != _err.Ok) { txtPrintError("Load EEdata for Config failed!\nErr: %s", _err.c_str()); while (true) { delay(10); } } if (def) { txtPrintError("Config loading default configuration!"); doc.clear(); } Config->sd = doc["sd"] | false; Config->mode = doc["mode"] | WEB_RADIO; Config->flash_size = doc["flash"] | ESP.getFlashChipSize(); Config->firmware = doc["firmware"] | (int8_t)4; Config->lang = doc["lang"] | 0; // 0 - ru, 1 - en Config->license = doc["license"] | false; Config->lcd.dark = doc["lcd"]["dark"] | true; Config->lcd.rotat = doc["lcd"]["rotat"] | true; Config->lcd.Long = doc["lcd"]["long"] | true; Config->lcd.div = doc["lcd"]["div"] | 3; if (doc["lcd"]["inch"].isNull()) { Config->lcd.inch = 3.2; } else { Config->lcd.inch = doc["lcd"]["inch"].as<float>(); } Config->beep.on = doc["beep"]["on"] | true; Config->beep.kbd = doc["beep"]["kbd"] | true; Config->beep.enc = doc["beep"]["enc"] | true; Config->beep.ir = doc["beep"]["ir"] | true; Config->led.on = doc["led"]["on"] | true; Config->led.ldr = doc["led"]["ldr"] | false; Config->led.mode = doc["led"]["mode"] | 3; Config->led.hand = doc["led"]["hand"] | 255; Config->led.day = doc["led"]["day"] | 255; Config->led.night = doc["led"]["night"] | 90; Config->led.begin = doc["led"]["begin"] | 8; Config->led.end = doc["led"]["end"] | 21; if (bme == nullptr) { bme = new BME280I2C(); uint8_t bmp = 0; xSemaphoreTake(eeI2C, portMAX_DELAY); if (bme->begin()) { bool sensor = false; switch (bme->chipModel()) { case BME280::ChipModel_BME280: txtPrintInfo("BME280 OK!\n"); bmp = 3; sensor = true; break; case BME280::ChipModel_BMP280: txtPrintInfo("BMP280 OK!\n"); bmp = 2; sensor = true; break; case BME280::ChipModel_UNKNOWN: default: sensor = false; break; } if (!sensor) { txtPrintError("I2C Sensor WRONG!\n"); } } else { txtPrintError("I2C Sensor not found!"); } xSemaphoreGive(eeI2C); Config->i2c.bmp = doc["i2c"]["bmp"] | bmp; if (Config->i2c.bmp != bmp) { Config->i2c.bmp = bmp; save = true; } } // Config->i2c.ds3231 = doc["i2c"]["ds3231"] | ee->rts.on; if (Config->i2c.ds3231 != ee->rts.on) { Config->i2c.ds3231 = ee->rts.on; save = true; } // Config->i2c.eeprom = doc["i2c"]["eeprom"] | _usingExternal; if (Config->i2c.eeprom != _usingExternal) { Config->i2c.eeprom = _usingExternal; save = true; } // if (tda == nullptr) { tda = new TDA7313("tda", "eeprom"); tda->begin(0x44, eeI2C); Config->i2c.tda7313 = doc["i2c"]["tda7313"] | tda->present; if (Config->i2c.tda7313 != tda->present) { Config->i2c.tda7313 = tda->present; save = true; } } // Config->i2c.rda5807 = doc["i2c"]["rda5807"] | false; Config->sensor.temp = doc["sensor"]["temp"] | true; Config->sensor.press = doc["sensor"]["press"] | true; Config->sensor.wet = doc["sensor"]["wet"] | true; Config->sensor.degree = doc["sensor"]["degree"] | true; Config->sensor.cels = doc["sensor"]["cels"] | true; Config->sensor.torr = doc["sensor"]["torr"] | true; Config->sensor.time = doc["sensor"]["time"] | 5; Config->sensor.offset = doc["sensor"]["offset"] | 0; Config->alarm.on = doc["alarm"]["on"] | false; Config->alarm.week = doc["alarm"]["week"] | false; Config->alarm.day[0] = doc["alarm"]["day"][0] | false; Config->alarm.day[1] = doc["alarm"]["day"][1] | true; Config->alarm.day[2] = doc["alarm"]["day"][2] | true; Config->alarm.day[3] = doc["alarm"]["day"][3] | true; Config->alarm.day[4] = doc["alarm"]["day"][4] | true; Config->alarm.day[5] = doc["alarm"]["day"][5] | true; Config->alarm.day[6] = doc["alarm"]["day"][6] | false; Config->alarm.hours = doc["alarm"]["hours"] | 7; Config->alarm.minutes = doc["alarm"]["minutes"] | 0; Config->sleep.on = doc["sleep"]["on"] | false; Config->sleep.hours = doc["sleep"]["hours"] | 23; Config->sleep.minutes = doc["sleep"]["minutes"] | 0; Config->weather.on = doc["weather"]["on"] | false; // strcpy(Config->weather.key, doc["weather"]["key"]); // strcpy(Config->weather.city, doc["weather"]["city"]); // strcpy(Config->weather.lat, doc["weather"]["lat"]); // strcpy(Config->weather.lon, doc["weather"]["lon"]); strlcpy(Config->color.clock[0], doc["color"]["clock"][0] | "#9000B8", sizeof(Config->color.clock[0])); strlcpy(Config->color.clock[1], doc["color"]["clock"][1] | "#00FF00", sizeof(Config->color.clock[1])); strlcpy(Config->color.temp[0], doc["color"]["temp"][0] | "#9000B8", sizeof(Config->color.temp[0])); strlcpy(Config->color.temp[1], doc["color"]["temp"][1] | "#00FF00", sizeof(Config->color.temp[1])); strlcpy(Config->color.press[0], doc["color"]["press"][0] | "#9000B8", sizeof(Config->color.press[0])); strlcpy(Config->color.press[1], doc["color"]["press"][1] | "#00FF00", sizeof(Config->color.press[1])); strlcpy(Config->color.wet[0], doc["color"]["wet"][0] | "#9000B8", sizeof(Config->color.wet[0])); strlcpy(Config->color.wet[1], doc["color"]["wet"][1] | "#00FF00", sizeof(Config->color.wet[1])); dark_theme = Config->lcd.dark; if (def || save) SaveConfig(); } void eeStorage::InitCurrConfig(bool def) { copycurrent = current; if (magicWord != MAGIC_WORD) { def = true; } if (!def) { xSemaphoreTake(eeNVS, portMAX_DELAY); _preferences.begin(CURRENT_SPACE, true, PART_HARDWARE); size_t Len = _preferences.getBytes(CURRENT_SPACE, ¤t, sizeof(current_)); _preferences.end(); xSemaphoreGive(eeNVS); if (Len != sizeof(current_)) { def = true; } } if (def) { current = current_{}; SaveCurrConfig(); } } bool eeStorage::CheckCurrConfig() { if (current.autoplay != copycurrent.autoplay) return false; if (current.info != copycurrent.info) return false; if (current.maxvol != copycurrent.maxvol) return false; if (current.volume != copycurrent.volume) return false; if (current.balance != copycurrent.balance) return false; if (current.ir_set != copycurrent.ir_set) return false; // Сравниваем eq_band[5] for (int i = 0; i < 5; ++i) { if (current.eq.band[i] != copycurrent.eq.band[i]) return false; } // if (current.eq.mode != copycurrent.eq.mode) return false; // Сравниваем eq_set[5] for (int i = 0; i < 5; ++i) { if (current.eq.set[i] != copycurrent.eq.set[i]) return false; } if (current.eq.spatial_mode != copycurrent.eq.spatial_mode) return false; if (current.CurrentPlayList != copycurrent.CurrentPlayList) return false; // Сравниваем строки PlayListName[4][33] for (int i = 0; i < 4; ++i) { if (strcmp(current.PlayListName[i], copycurrent.PlayListName[i]) != 0) { return false; } } // Сравниваем массивы all[4], sel[4], show_rows[4] for (int i = 0; i < 4; ++i) { if (current.all[i] != copycurrent.all[i]) return false; if (current.sel[i] != copycurrent.sel[i]) return false; if (current.show_rows[i] != copycurrent.show_rows[i]) return false; } return true; // Все поля совпали } void eeStorage::CheckConfig() { if (!CheckCurrConfig()) network.save_curr_conf(); } // StringPS eeStorage::SaveCurrConfig() { copycurrent = current; xSemaphoreTake(eeNVS, portMAX_DELAY); _preferences.begin(CURRENT_SPACE, false, PART_HARDWARE); _preferences.putBytes(CURRENT_SPACE, ¤t, sizeof(current_)); _preferences.end(); xSemaphoreGive(eeNVS); JsonDocument doc(&allocator); doc["autoplay"] = current.autoplay; doc["info"] = current.info; doc["maxvol"] = current.maxvol; doc["volume"] = current.volume; doc["balance"] = current.balance; doc["ir_set"] = current.ir_set; JsonObject eq = doc["eq"].to<JsonObject>(); JsonArray bandArr = eq["band"].to<JsonArray>(); for (signed char i : current.eq.band) { bandArr.add(i); } eq["mode"] = current.eq.mode; JsonArray setArr = eq["set"].to<JsonArray>(); for (signed char i : current.eq.set) { setArr.add(i); } eq["spatial_mode"] = current.eq.spatial_mode; doc["CurrentPlayList"] = current.CurrentPlayList; for (int i = 0; i < 4; i++) { doc["PlayListName"][i] = current.PlayListName[i]; doc["all"][i] = current.all[i]; doc["sel"][i] = current.sel[i]; doc["show_rows"][i] = current.show_rows[i]; } StringPS output; serializeJson(doc, output); return output; } // void eeStorage::InitPlaylist() { stations.clear(); JsonDocument doc(&allocator); TAKE_MUTEX(); std::string path = "/configs/list" + std::to_string(current.CurrentPlayList) + ".json"; if (FileFS->exists(path.c_str())) { File file = FileFS->open(path.c_str()); DeserializationError _err = deserializeJson(doc, file); file.close(); if (_err) { txtPrintError("Playlist file is corrupted! Err: %s", _err.c_str()); if (FileFS->remove(path.c_str())) { txtPrintError("Playlist deleted!"); } } } GIVE_MUTEX(); for (JsonObject stationJson : doc.as<JsonArray>()) { station.name = StringPS(stationJson["Name"].as<const char *>()); station.logo = StringPS(stationJson["Logo"].as<const char *>()); station.uri = StringPS(stationJson["Uri"].as<const char *>()); stations.push_back(station); } bool save = false; if (current.all[current.CurrentPlayList] != GetListSize()) { current.all[current.CurrentPlayList] = GetListSize(); save = true; } if (current.sel[current.CurrentPlayList] > (current.all[current.CurrentPlayList] + 1)) { current.autoplay = false; current.sel[current.CurrentPlayList] = 1; save = true; } if (save) { SaveCurrConfig(); } txtPrintInfo("Current playlist: %u", current.CurrentPlayList + 1); txtPrintInfo("Total stations: %u", current.all[current.CurrentPlayList]); } // void eeStorage::InitKeyboard(bool def) { if (magicWord != MAGIC_WORD) { def = true; } TAKE_MUTEX(); if (FileFS->exists(PATH_KBD_BTN)) { File file = FileFS->open(PATH_KBD_BTN); DeserializationError _err = deserializeJson(KBD_BTN, file); file.close(); if (_err || KBD_BTN.size() == 0) { txtPrintError("KBD Code Config file is corrupted!"); if (_err) { txtPrintError("Err: %s", _err.c_str()); } if (FileFS->remove(PATH_KBD_BTN)) { txtPrintError("KBD Code Config deleted!"); } def = true; KBD_BTN.clear(); } } else { def = true; } GIVE_MUTEX(); if (def) { TAKE_MUTEX(); if (FileFS->exists(PATH_KBD_BTN)) { FileFS->remove(PATH_KBD_BTN); txtPrintError("ReInit KBD Code!!!"); } else { txtPrintWarn("Init KBD Code Default!"); } GIVE_MUTEX(); // KBD1 KBD_BTN["BTN_POWER"] = BTN_POWER; KBD_BTN["BTN_MODE"] = BTN_MODE; KBD_BTN["BTN_MUTE"] = BTN_MUTE; KBD_BTN["BTN_NEXT"] = BTN_NEXT; KBD_BTN["BTN_PREV"] = BTN_PREV; KBD_BTN["BTN_STOP"] = BTN_STOP; KBD_BTN["BTN_EQ"] = BTN_EQ; KBD_BTN["BTN_VOLm"] = BTN_VOLm; KBD_BTN["BTN_VOLp"] = BTN_VOLp; // KBD2 KBD_BTN["BTN_DOWN"] = BTN_DOWN; KBD_BTN["BTN_RIGHT"] = BTN_RIGHT; KBD_BTN["BTN_UP"] = BTN_UP; KBD_BTN["BTN_LEFT"] = BTN_LEFT; KBD_BTN["BTN_PAUSE"] = BTN_PAUSE; KBD_BTN["BTN_TUNER"] = BTN_TUNER; KBD_BTN["BTN_PLAY"] = BTN_PLAY; KBD_BTN["BTN_ALARM"] = BTN_ALARM; SaveKBD(); } } void eeStorage::InitIrcodes(bool def) { if (magicWord != MAGIC_WORD) { def = true; } TAKE_MUTEX(); if (FileFS->exists(PATH_IRCODES)) { File file = FileFS->open(PATH_IRCODES); DeserializationError _err = deserializeJson(IR_KEY, file); file.close(); if (_err || IR_KEY.size() == 0) { txtPrintError("IR Code Config file is corrupted!"); if (_err) { txtPrintError("Err: %s", _err.c_str()); } if (FileFS->remove(PATH_IRCODES)) { txtPrintError("IR Code Config deleted!"); } def = true; IR_KEY.clear(); } } else { def = true; } GIVE_MUTEX(); if (def) { TAKE_MUTEX(); if (FileFS->exists(PATH_IRCODES)) { FileFS->remove(PATH_IRCODES); txtPrintError("ReInit IR Code!!!"); } else { txtPrintWarn("Init IR Code Default!"); } GIVE_MUTEX(); IR_KEY["KEY_POWER"][0] = "8000045"; IR_KEY["KEY_POWER"][1] = "8000045"; IR_KEY["KEY_MODE"][0] = "8000046"; IR_KEY["KEY_MODE"][1] = "8000046"; IR_KEY["KEY_MUTE"][0] = "8000047"; IR_KEY["KEY_MUTE"][1] = "8000047"; IR_KEY["KEY_PLAY"][0] = "8000044"; IR_KEY["KEY_PLAY"][1] = "8000044"; IR_KEY["KEY_PREV"][0] = "8000040"; IR_KEY["KEY_PREV"][1] = "8000040"; IR_KEY["KEY_NEXT"][0] = "8000043"; IR_KEY["KEY_NEXT"][1] = "8000043"; IR_KEY["KEY_EQ"][0] = "8000007"; IR_KEY["KEY_EQ"][1] = "8000007"; IR_KEY["KEY_VOLm"][0] = "8000015"; IR_KEY["KEY_VOLm"][1] = "8000015"; IR_KEY["KEY_VOLp"][0] = "8000009"; IR_KEY["KEY_VOLp"][1] = "8000009"; IR_KEY["KEY_RPT"][0] = "8000019"; IR_KEY["KEY_RPT"][1] = "8000019"; IR_KEY["KEY_CLOCK"][0] = "800000D"; IR_KEY["KEY_CLOCK"][1] = "800000D"; IR_KEY["KEY_0"][0] = "8000016"; IR_KEY["KEY_0"][1] = "8000016"; IR_KEY["KEY_1"][0] = "800000C"; IR_KEY["KEY_1"][1] = "800000C"; IR_KEY["KEY_2"][0] = "8000018"; IR_KEY["KEY_2"][1] = "8000018"; IR_KEY["KEY_3"][0] = "800005E"; IR_KEY["KEY_3"][1] = "800005E"; IR_KEY["KEY_4"][0] = "8000008"; IR_KEY["KEY_4"][1] = "8000008"; IR_KEY["KEY_5"][0] = "800001C"; IR_KEY["KEY_5"][1] = "800001C"; IR_KEY["KEY_6"][0] = "800005A"; IR_KEY["KEY_6"][1] = "800005A"; IR_KEY["KEY_7"][0] = "8000042"; IR_KEY["KEY_7"][1] = "8000042"; IR_KEY["KEY_8"][0] = "8000052"; IR_KEY["KEY_8"][1] = "8000052"; IR_KEY["KEY_9"][0] = "800004A"; IR_KEY["KEY_9"][1] = "800004A"; SaveIR(); } } time_t eeStorage::SyncSysDateTime() { struct tm rtcTime; xSemaphoreTake(eeI2C, portMAX_DELAY); bool ret = rtc.read(&rtcTime); xSemaphoreGive(eeI2C); if (ret) { time_t rtcEpoch = mktime(&rtcTime); struct timeval tv; tv.tv_sec = rtcEpoch; tv.tv_usec = 0; settimeofday(&tv, nullptr); return rtcEpoch; } return 0; } void eeStorage::initEEdata(bool def, StringPS *conf) { bool keep = false; if (ee == nullptr) { ee = &wifi; } if (_usingExternal) { xSemaphoreTake(eeI2C, portMAX_DELAY); at24c.get(MAGIC_OFFSET, magicWord); xSemaphoreGive(eeI2C); } else { xSemaphoreTake(eeNVS, portMAX_DELAY); _preferences.begin(WIFI_SPACE, true, PART_HARDWARE); magicWord = _preferences.getUShort("magic"); _preferences.end(); xSemaphoreGive(eeNVS); } if (magicWord != MAGIC_WORD) { txtPrintWarn("%s: First run detected!", _usingExternal ? "AT24C32" : "NVS"); if (!erase()) { while (true) { delay(10); } } def = true; } JsonDocument doc(&allocator); DeserializationError err = err.Ok; if (conf != nullptr) { err = deserializeJson(doc, *conf); keep = true; } else { err = load(WIFI_SPACE, doc); } if (err == err.EmptyInput) { def = true; } else if (err != err.Ok) { txtPrintError("Load EEdata failed!\nErr: %s", err.c_str()); while (true) { delay(10); } } if (def) { txtPrintError("EEdata loading default configuration!"); doc.clear(); } if (!def) { if (!keep) { ee->rts.last = doc["rtc"]["last"]; xSemaphoreTake(eeI2C, portMAX_DELAY); ee->rts.on = rtc.begin(); xSemaphoreGive(eeI2C); if (doc["rtc"]["on"] != ee->rts.on) { doc["rtc"]["on"] = ee->rts.on; keep = true; } if (!ee->rts.on) { txtPrintError("DS3231 not found!"); ee->rts.run = false; if (doc["rtc"]["run"] != ee->rts.run) { doc["rtc"]["run"] = ee->rts.run; keep = true; } } else { xSemaphoreTake(eeI2C, portMAX_DELAY); ee->rts.run = rtc.isRunning(); xSemaphoreGive(eeI2C); if (doc["rtc"]["run"] != ee->rts.run) { doc["rtc"]["run"] = ee->rts.run; keep = true; } if (!ee->rts.run || !ee->rts.last) { if (!ee->rts.run) txtPrintWarn("DS3231: power failure detected!"); else txtPrintWarn("DS3231: First run!"); } else { time_t epochTime = SyncSysDateTime(); if (epochTime == 0) { txtPrintError("DS3231: Time reading error!"); while (true) { delay(10); } } txtPrintInfo("DS3231: The system time is set!"); if (difftime(epochTime, ee->rts.last) < SYNC_INTERVAL) { time_sync = true; } } } } ee->ntp[0] = (const char *)doc["ntp"][0]; ee->ntp[1] = (const char *)doc["ntp"][1]; ee->ntp[2] = (const char *)doc["ntp"][2]; ee->ntp[3] = (const char *)doc["ntp"][3]; ee->host = (const char *)doc["host"]; ee->ssid = (const char *)doc["ssid"]; ee->pass = (const char *)doc["pass"]; ee->tz = (const char *)doc["tz"]; ee->autowifi = doc["autowifi"]; ee->mac = (const char *)doc["mac"]; if (keep) { SaveEEdata(); if (!def) { web.needReboot = true; } } } if (def) { doc.clear(); // xSemaphoreTake(eeI2C, portMAX_DELAY); ee->rts.on = rtc.begin(); doc["rtc"]["on"] = ee->rts.on; if (!ee->rts.on) { txtPrintError("DS3231 not found!"); ee->rts.run = false; doc["rtc"]["run"] = ee->rts.run; } else { ee->rts.run = rtc.isRunning(); doc["rtc"]["run"] = ee->rts.run; } xSemaphoreGive(eeI2C); doc["rtc"]["last"] = 0LL; ee->rts.last = 0LL; // doc["autowifi"] = ee->autowifi; std::string hostname = PROGNAME; // hostname.toLowerCase(); ee->host = hostname; doc["host"] = ee->host; doc["ua"] = ee->ua; doc["tz"] = ee->tz; doc["ntp"][0] = ee->ntp[0]; doc["ntp"][1] = ee->ntp[1]; doc["ntp"][2] = ee->ntp[2]; doc["ntp"][3] = ee->ntp[3]; doc["ssid"] = ee->ssid; doc["pass"] = ee->pass; uint8_t base_mac_addr[6] = {0}; ESP_ERROR_CHECK(esp_efuse_mac_get_default(base_mac_addr)); char mac[18]; sprintf(mac, MACSTR, MAC2STR(base_mac_addr)); // mac-address doc["mac"] = mac; ee->mac = mac; err = save(WIFI_SPACE, doc); if (err) { txtPrintError("EEdata saving error!\nErr: %s", err.c_str()); while (true) { delay(10); } } } if (conf == nullptr && !time_sync) { txtPrintInfo("Set TZ: %s", ee->tz.c_str()); setenv("TZ", ee->tz.c_str(), 1); tzset(); } } // void eeStorage::EraseAll(bool def) { TAKE_MUTEX(); if (FileFS->exists(PATH_CONFIG)) { FileFS->remove(PATH_CONFIG); } if (def) { if (FileFS->exists(PATH_IRCODES)) { FileFS->remove(PATH_IRCODES); } if (FileFS->exists(PATH_KBD_BTN)) { FileFS->remove(PATH_KBD_BTN); } } GIVE_MUTEX(); if (!erase()) { while (true) { delay(10); } } } // StringPS eeStorage::SaveConfig(bool def) { JsonDocument doc(&allocator); doc["sd"] = Config->sd; doc["mode"] = Config->mode; doc["flash"] = Config->flash_size; doc["firmware"] = Config->firmware; doc["lang"] = Config->lang; doc["license"] = Config->license; doc["lcd"]["dark"] = Config->lcd.dark; doc["lcd"]["rotat"] = Config->lcd.rotat; doc["lcd"]["long"] = Config->lcd.Long; doc["lcd"]["div"] = Config->lcd.div; doc["lcd"]["inch"] = Config->lcd.inch; doc["beep"]["on"] = Config->beep.on; doc["beep"]["kbd"] = Config->beep.kbd; doc["beep"]["enc"] = Config->beep.enc; doc["beep"]["ir"] = Config->beep.ir; doc["led"]["on"] = Config->led.on; doc["led"]["ldr"] = Config->led.ldr; doc["led"]["mode"] = Config->led.mode; doc["led"]["hand"] = Config->led.hand; doc["led"]["day"] = Config->led.day; doc["led"]["night"] = Config->led.night; doc["led"]["begin"] = Config->led.begin; doc["led"]["end"] = Config->led.end; doc["i2c"]["bmp"] = Config->i2c.bmp; doc["i2c"]["ds3231"] = Config->i2c.ds3231; doc["i2c"]["eeprom"] = Config->i2c.eeprom; doc["i2c"]["rda5807"] = Config->i2c.rda5807; doc["i2c"]["tda7313"] = Config->i2c.tda7313; doc["sensor"]["temp"] = Config->sensor.temp; doc["sensor"]["press"] = Config->sensor.press; doc["sensor"]["wet"] = Config->sensor.wet; doc["sensor"]["degree"] = Config->sensor.degree; doc["sensor"]["cels"] = Config->sensor.cels; doc["sensor"]["torr"] = Config->sensor.torr; doc["sensor"]["time"] = Config->sensor.time; doc["sensor"]["offset"] = Config->sensor.offset; doc["alarm"]["on"] = Config->alarm.on; doc["alarm"]["week"] = Config->alarm.week; doc["alarm"]["day"][0] = Config->alarm.day[0]; doc["alarm"]["day"][1] = Config->alarm.day[1]; doc["alarm"]["day"][2] = Config->alarm.day[2]; doc["alarm"]["day"][3] = Config->alarm.day[3]; doc["alarm"]["day"][4] = Config->alarm.day[4]; doc["alarm"]["day"][5] = Config->alarm.day[5]; doc["alarm"]["day"][6] = Config->alarm.day[6]; doc["alarm"]["hours"] = Config->alarm.hours; doc["alarm"]["minutes"] = Config->alarm.minutes; doc["sleep"]["on"] = Config->sleep.on; doc["sleep"]["hours"] = Config->sleep.hours; doc["sleep"]["minutes"] = Config->sleep.minutes; doc["weather"]["on"] = Config->weather.on; // doc["weather"]["key"] = Config->weather.key; // doc["weather"]["city"] = Config->weather.city; // doc["weather"]["lat"] = Config->weather.lat; // doc["weather"]["lon"] = Config->weather.lon; doc["color"]["clock"][0] = Config->color.clock[0]; doc["color"]["clock"][1] = Config->color.clock[1]; doc["color"]["temp"][0] = Config->color.temp[0]; doc["color"]["temp"][1] = Config->color.temp[1]; doc["color"]["press"][0] = Config->color.press[0]; doc["color"]["press"][1] = Config->color.press[1]; doc["color"]["wet"][0] = Config->color.wet[0]; doc["color"]["wet"][1] = Config->color.wet[1]; if (def) { DeserializationError err = save(CONFIG_SPACE, doc); if (err) { txtPrintError("EEdata saving for Config error!\nErr: %s", err.c_str()); while (true) { delay(10); } } return ""; } else { StringPS output; serializeJson(doc, output); return output; } } // StringPS eeStorage::SaveEEdata(bool def) { JsonDocument doc(&allocator); doc["rtc"]["on"] = ee->rts.on; doc["rtc"]["run"] = ee->rts.run; doc["rtc"]["last"] = ee->rts.last; doc["autowifi"] = ee->autowifi; doc["host"] = ee->host; doc["ua"] = ee->ua; doc["tz"] = ee->tz; doc["ntp"][0] = ee->ntp[0]; doc["ntp"][1] = ee->ntp[1]; doc["ntp"][2] = ee->ntp[2]; doc["ntp"][3] = ee->ntp[3]; doc["ssid"] = ee->ssid; doc["pass"] = ee->pass; doc["mac"] = ee->mac; if (def) { DeserializationError err = save(WIFI_SPACE, doc); if (err) { txtPrintError("EEdata saving error!\nErr: %s", err.c_str()); while (true) { delay(10); } } return ""; } else { StringPS output; serializeJson(doc, output); return output; } } // void eeStorage::SaveIR() { TAKE_MUTEX(); if (FileFS->exists(PATH_IRCODES)) { FileFS->remove(PATH_IRCODES); } File file = FileFS->open(PATH_IRCODES, FILE_WRITE, true); serializeJsonPretty(IR_KEY, file); file.flush(); file.close(); GIVE_MUTEX(); } // void eeStorage::SaveKBD() { TAKE_MUTEX(); if (FileFS->exists(PATH_KBD_BTN)) { FileFS->remove(PATH_KBD_BTN); } File file = FileFS->open(PATH_KBD_BTN, FILE_WRITE, true); serializeJsonPretty(KBD_BTN, file); file.flush(); file.close(); GIVE_MUTEX(); } bool eeStorage::RTCwrite(const tm *info) { xSemaphoreTake(eeI2C, portMAX_DELAY); bool ret = rtc.write(info); xSemaphoreGive(eeI2C); return ret; } void eeStorage::GetEqData(int8_t *band, int8_t *set, uint8_t &mode) { memcpy(band, current.eq.band, sizeof(current.eq.band)); mode = current.eq.mode; memcpy(set, current.eq.set, sizeof(current.eq.set)); } // Отдать данные ИЗ хранилища ВО внешнюю структуру void eeStorage::GetEqData(void *dest) { memcpy(dest, ¤t.eq, sizeof(current.eq)); } // Записать данные ИЗ внешней структуры В хранилище (в память current) void eeStorage::SetEqData(void *src) { memcpy(¤t.eq, src, sizeof(current.eq)); CheckConfig(); } void eeStorage::SetEqData(int8_t *band, int8_t *set, uint8_t mode) { memcpy(current.eq.band, band, sizeof(current.eq.band)); current.eq.mode = mode; memcpy(current.eq.set, set, sizeof(current.eq.set)); } // uint8_t eeStorage::GetEqMode() { return current.eq.mode; } void eeStorage::SetEqMode(uint8_t mode) { current.eq.mode = mode; CheckConfig(); } void eeStorage::GetEqPreset(int8_t preset[5]) { for (int i = 0; i < 5; i++) { preset[i] = current.eq.set[i]; } } void eeStorage::SetEqPreset(int8_t preset[5]) { for (int i = 0; i < 5; i++) { current.eq.set[i] = preset[i]; } CheckConfig(); } // StringPS eeStorage::GetStaName(uint8_t ID) { return stations[ID - 1].name; } uint8_t eeStorage::GetID() { return current.sel[current.CurrentPlayList]; } uint8_t eeStorage::GetTotalSta() { return current.all[current.CurrentPlayList]; } uint8_t eeStorage::GetVolume() { return current.volume; } int8_t eeStorage::GetBalanse() { return current.balance; } bool eeStorage::GetInfo() { return current.info; } void eeStorage::SetBalanse(int8_t val) { current.balance = val; CheckConfig(); } void eeStorage::SetInfo(bool ena) { current.info = ena; CheckConfig(); } uint8_t eeStorage::GetMaxVolume() { return current.maxvol; } uint8_t eeStorage::GetShowRows() { return current.show_rows[current.CurrentPlayList]; } void eeStorage::SetShowRows(uint8_t rows) { current.show_rows[current.CurrentPlayList] = rows; CheckConfig(); } void eeStorage::setIRSet(uint8_t set) { current.ir_set = set; CheckConfig(); } uint8_t eeStorage::getIRSet() { return current.ir_set; } uint8_t eeStorage::GetTotalSta(uint8_t num) { return current.all[num]; } uint8_t eeStorage::GetCurrentPlayList() { return current.CurrentPlayList; } bool eeStorage::GetAutoPlay() { return current.autoplay; } void eeStorage::SetAutoPlay(bool set) { current.autoplay = set; CheckConfig(); } // void eeStorage::GetTone(int8_t &bass, int8_t &middle, int8_t &trebble) // { // bass = current.bass; // middle = current.middle; // trebble = current.trebble; // } // void eeStorage::SetTone(int8_t bass, int8_t middle, int8_t trebble) // { // current.bass = bass; // current.middle = middle; // current.trebble = trebble; // CheckConfig(); // } void eeStorage::SetCurrentPlayList(uint8_t num) { current.CurrentPlayList = num; CheckConfig(); } void eeStorage::SetID(uint8_t ID) { current.sel[current.CurrentPlayList] = ID; CheckConfig(); } void eeStorage::SetVolume(uint8_t val) { current.volume = val; CheckConfig(); } void eeStorage::SetMaxVolume(uint8_t val) { current.maxvol = val; CheckConfig(); } StringPS eeStorage::GetPlayListName(uint8_t ID) { return current.PlayListName[ID]; } // StringPS eeStorage::GetCurrentPlayListName() { return current.PlayListName[GetCurrentPlayList()]; } // StringPS eeStorage::GetList() { JsonDocument doc(&allocator); // Создаем корневой JSON-массив JsonArray jsonArray = doc.to<JsonArray>(); // Проходим по каждой станции в вашем векторе for (const auto &station : stations) { // Добавляем новый объект в JSON-мамассив auto obj = jsonArray.add<JsonObject>(); // Копируем данные из структуры C++ в объект JSON // Мы используем c_str() для доступа к данным строки StdStringPSRAM obj["Name"] = station.name.c_str(); obj["Logo"] = station.logo.c_str(); obj["Uri"] = station.uri.c_str(); } StringPS output; serializeJson(doc, output); return output; } // StringPS eeStorage::GetStaLogo(uint8_t ID) { return stations[ID - 1].logo; } // StringPS eeStorage::GetStaUrl(uint8_t ID) { return stations[ID - 1].uri; } size_t eeStorage::GetListSize() { return stations.size(); } size_t eeStorage::GetListBytes() { size_t totalSize = 0; // Проходим по всем элементам и считаем размер буферов строк в PSRAM for (const auto &station : stations) { totalSize += station.name.length() + 1; // +1 для нулевого терминатора totalSize += station.logo.length() + 1; totalSize += station.uri.length() + 1; } // Добавляем размер буфера самих структур (который теперь тоже в PSRAM) totalSize += stations.size() * sizeof(StationsPS); // Управляющая структура самого вектора (около 24 байт) все еще в DRAM totalSize += sizeof(stations); // NOLINT return totalSize; } //