diff --git a/src/Web.cpp b/src/Web.cpp index b4686eed..37247829 100644 --- a/src/Web.cpp +++ b/src/Web.cpp @@ -71,13 +71,13 @@ static void handleGetWiFiConfig(AsyncWebServerRequest *request); static void handlePostWiFiConfig(AsyncWebServerRequest *request, JsonVariant &json); static void handleCoverImageRequest(AsyncWebServerRequest *request); static void handleWiFiScanRequest(AsyncWebServerRequest *request); +static void handleGetRFIDRequest(AsyncWebServerRequest *request); +static void handlePostRFIDRequest(AsyncWebServerRequest *request, JsonVariant &json); +static void handleDeleteRFIDRequest(AsyncWebServerRequest *request); static void handleGetInfo(AsyncWebServerRequest *request); static void handleGetSettings(AsyncWebServerRequest *request); static void handlePostSettings(AsyncWebServerRequest *request, JsonVariant &json); - -static bool Web_DumpNvsToSd(const char *_namespace, const char *_destFile); - static void onWebsocketEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len); static void settingsToJSON(JsonObject obj, String section); static bool JSONToSettings(JsonObject obj); @@ -155,6 +155,85 @@ class OneParamRewrite : public AsyncWebRewrite }; +// List all key in NVS for a given namespace +// callback function is called for every key with userdefined data object +bool listNVSKeys(const char *_namespace, void* data, bool (*callback)(const char * key, void* data)) { + Led_SetPause(true); // Workaround to prevent exceptions due to Neopixel-signalisation while NVS-write + esp_partition_iterator_t pi; // Iterator for find + const esp_partition_t *nvs; // Pointer to partition struct + esp_err_t result = ESP_OK; + const char *partname = "nvs"; + uint8_t pagenr = 0; // Page number in NVS + uint8_t i; // Index in Entry 0..125 + uint8_t bm; // Bitmap for an entry + uint32_t offset = 0; // Offset in nvs partition + uint8_t namespace_ID; // Namespace ID found + + pi = esp_partition_find(ESP_PARTITION_TYPE_DATA, // Get partition iterator for + ESP_PARTITION_SUBTYPE_ANY, // this partition + partname); + if (pi) { + nvs = esp_partition_get(pi); // Get partition struct + esp_partition_iterator_release(pi); // Release the iterator + Log_Printf(LOGLEVEL_DEBUG, "Partition %s found, %d bytes", partname, nvs->size); + } else { + Log_Printf(LOGLEVEL_ERROR, "Partition %s not found!", partname); + return NULL; + } + namespace_ID = FindNsID(nvs, _namespace); // Find ID of our namespace in NVS + while (offset < nvs->size) { + result = esp_partition_read(nvs, offset, // Read 1 page in nvs partition + &buf, + sizeof(nvs_page)); + if (result != ESP_OK) { + Log_Println("Error reading NVS!", LOGLEVEL_ERROR); + return false; + } + + i = 0; + + while (i < 126) { + bm = (buf.Bitmap[i / 4] >> ((i % 4) * 2)) & 0x03; // Get bitmap for this entry + if (bm == 2) { + if ((namespace_ID == 0xFF) || // Show all if ID = 0xFF + (buf.Entry[i].Ns == namespace_ID)) { // otherwise just my namespace + if (isNumber(buf.Entry[i].Key)) { + if (!callback(buf.Entry[i].Key, data)) { + return false; + } + } + } + i += buf.Entry[i].Span; // Next entry + } else { + i++; + } + } + offset += sizeof(nvs_page); // Prepare to read next page in nvs + pagenr++; + } + Led_SetPause(false); + + return true; +} + +// callback for writing a NVS entry to file +bool DumpNvsToSdCallback(const char * key, void* data) { + String s = gPrefsRfid.getString(key); + File *file = (File *) data; + file->printf("%s%s%s%s\n", stringOuterDelimiter, key, stringOuterDelimiter, s.c_str()); + return true; +} + +// Dumps all RFID-entries from NVS into a file on SD-card +bool Web_DumpNvsToSd(const char *_namespace, const char *_destFile) { + File file = gFSystem.open(_destFile, FILE_WRITE); + if (!file) { + return false; + } + bool success = listNVSKeys(_namespace, &file, DumpNvsToSdCallback); + file.close(); + return success; +} // First request will return 0 results unless you start scan from somewhere else (loop/setup) // Do not request more often than 3-5 seconds @@ -335,6 +414,11 @@ void webserverStart(void) { System_UpdateActivityTimer(); }); + // RFID + wServer.on("/rfid", HTTP_GET, handleGetRFIDRequest); + wServer.addHandler(new AsyncCallbackJsonWebHandler("/rfid", handlePostRFIDRequest)); + wServer.addRewrite(new OneParamRewrite("/rfid/{id}", "/rfid?id={id}") ); + wServer.on("/rfid", HTTP_DELETE, handleDeleteRFIDRequest); // WiFi scan wServer.on("/wifiscan", HTTP_GET, handleWiFiScanRequest); @@ -1490,6 +1574,132 @@ void handlePostWiFiConfig(AsyncWebServerRequest *request, JsonVariant &json){ } } +static bool tagIdToJSON(String tagId, JsonObject entry) { + String s = gPrefsRfid.getString(tagId.c_str(), "-1"); // Try to lookup rfidId in NVS + if (!s.compareTo("-1")) { + return false; + } + char _file[255]; + uint32_t _lastPlayPos = 0; + uint16_t _trackLastPlayed = 0; + uint32_t _mode = 1; + char *token; + uint8_t i = 1; + token = strtok((char *)s.c_str(), stringDelimiter); + while (token != NULL) { // Try to extract data from string after lookup + if (i == 1) { + strncpy(_file, token, sizeof(_file) / sizeof(_file[0])); + } else if (i == 2) { + _lastPlayPos = strtoul(token, NULL, 10); + } else if (i == 3) { + _mode = strtoul(token, NULL, 10); + } else if (i == 4) { + _trackLastPlayed = strtoul(token, NULL, 10); + } + i++; + token = strtok(NULL, stringDelimiter); + } + entry["id"] = tagId; + if (_mode >= 100) { + entry["modId"] = _mode; + } else { + entry["fileOrUrl"] = _file; + entry["playMode"] = _mode; + entry["lastPlayPos"] = _lastPlayPos; + entry["trackLastPlayed"] = _trackLastPlayed; + } + return true; +} + +// callback for writing a NVS entry to JSON +bool DumpNvsToJSONCallback(const char * key, void* data) { + JsonArray *myArr = (JsonArray*) data; + JsonObject obj = myArr->createNestedObject(); + return tagIdToJSON(key, obj); +} + +static void handleGetRFIDRequest(AsyncWebServerRequest *request) { + String tagId = ""; + if (request->hasParam("id")) { + tagId = request->getParam("id")->value(); + } + if (tagId == "") { + // return all RFID assignments + AsyncJsonResponse *response = new AsyncJsonResponse(true); + JsonArray Arr = response->getRoot(); + if (listNVSKeys("rfidTags", &Arr, DumpNvsToJSONCallback)) { + response->setLength(); + request->send(response); + } else { + request->send(500, "error reading entries from NVS"); + } + } else { + if (gPrefsRfid.isKey(tagId.c_str())) { + // return single RFID assignment + AsyncJsonResponse *response = new AsyncJsonResponse(false); + JsonObject obj = response->getRoot(); + tagIdToJSON(tagId, obj); + response->setLength(); + request->send(response); + } else { + // RFID assignment not found + request->send(404); + } + } +} + +static void handlePostRFIDRequest(AsyncWebServerRequest *request, JsonVariant &json) { + const JsonObject& jsonObj = json.as(); + + String tagId = jsonObj["id"]; + String fileOrUrl = jsonObj["fileOrUrl"]; + if (fileOrUrl.isEmpty()) { + fileOrUrl = "0"; + } + char _fileOrUrlAscii[MAX_FILEPATH_LENTGH]; + convertFilenameToAscii(fileOrUrl, _fileOrUrlAscii); + uint8_t _playModeOrModId; + if (jsonObj.containsKey("modId")) { + _playModeOrModId = jsonObj["modId"]; + } else { + _playModeOrModId = jsonObj["playMode"]; + } + if (_playModeOrModId <= 0) { + Log_Println("rfidAssign: Invalid playMode or modId", LOGLEVEL_ERROR); + request->send(500, "text/plain; charset=utf-8", "rfidAssign: Invalid playMode or modId"); + return; + } + char rfidString[275]; + snprintf(rfidString, sizeof(rfidString) / sizeof(rfidString[0]), "%s%s%s0%s%u%s0", stringDelimiter, _fileOrUrlAscii, stringDelimiter, stringDelimiter, _playModeOrModId, stringDelimiter); + gPrefsRfid.putString(tagId.c_str(), rfidString); + + String s = gPrefsRfid.getString(tagId.c_str(), "-1"); + if (s.compareTo(rfidString)) { + request->send(500, "text/plain; charset=utf-8", "rfidAssign: cannot save assignment to NVS"); + return; + } + Web_DumpNvsToSd("rfidTags", backupFile); // Store backup-file every time when a new rfid-tag is programmed +} + +static void handleDeleteRFIDRequest(AsyncWebServerRequest *request) { + String tagId = ""; + if (request->hasParam("id")) { + tagId = request->getParam("id")->value(); + } + if ((tagId != "") && (gPrefsRfid.isKey(tagId.c_str()))) { + // stop playback, tag to delete might be in use + Cmd_Action(CMD_STOP); + if (gPrefsRfid.remove(tagId.c_str())) { + request->send(200, "text/plain; charset=utf-8", tagId + " removed successfuly"); + } else { + request->send(500, "text/plain; charset=utf-8", "error removing tag from NVS"); + } + } else { + request->send(404, "text/plain; charset=utf-8", "error removing tag from NVS: Tag not exists"); + } +} + + // Takes stream from file-upload and writes payload into a temporary sd-file. void handleUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { @@ -1577,72 +1787,6 @@ void Web_DumpSdToNvs(const char *_filename) { gFSystem.remove(_filename); } -// Dumps all RFID-entries from NVS into a file on SD-card -bool Web_DumpNvsToSd(const char *_namespace, const char *_destFile) { - Led_SetPause(true); // Workaround to prevent exceptions due to Neopixel-signalisation while NVS-write - esp_partition_iterator_t pi; // Iterator for find - const esp_partition_t *nvs; // Pointer to partition struct - esp_err_t result = ESP_OK; - const char *partname = "nvs"; - uint8_t pagenr = 0; // Page number in NVS - uint8_t i; // Index in Entry 0..125 - uint8_t bm; // Bitmap for an entry - uint32_t offset = 0; // Offset in nvs partition - uint8_t namespace_ID; // Namespace ID found - - pi = esp_partition_find(ESP_PARTITION_TYPE_DATA, // Get partition iterator for - ESP_PARTITION_SUBTYPE_ANY, // this partition - partname); - if (pi) { - nvs = esp_partition_get(pi); // Get partition struct - esp_partition_iterator_release(pi); // Release the iterator - Log_Printf(LOGLEVEL_DEBUG, "Partition %s found, %d bytes", partname, nvs->size); - } else { - Log_Printf(LOGLEVEL_ERROR, "Partition %s not found!", partname); - return NULL; - } - namespace_ID = FindNsID(nvs, _namespace); // Find ID of our namespace in NVS - File backupFile = gFSystem.open(_destFile, FILE_WRITE); - if (!backupFile) { - return false; - } - while (offset < nvs->size) { - result = esp_partition_read(nvs, offset, // Read 1 page in nvs partition - &buf, - sizeof(nvs_page)); - if (result != ESP_OK) { - Log_Println("Error reading NVS!", LOGLEVEL_ERROR); - return false; - } - - i = 0; - - while (i < 126) { - bm = (buf.Bitmap[i / 4] >> ((i % 4) * 2)) & 0x03; // Get bitmap for this entry - if (bm == 2) { - if ((namespace_ID == 0xFF) || // Show all if ID = 0xFF - (buf.Entry[i].Ns == namespace_ID)) { // otherwise just my namespace - if (isNumber(buf.Entry[i].Key)) { - String s = gPrefsRfid.getString((const char *)buf.Entry[i].Key); - backupFile.printf("%s%s%s%s\n", stringOuterDelimiter, buf.Entry[i].Key, stringOuterDelimiter, s.c_str()); - } - } - i += buf.Entry[i].Span; // Next entry - } else { - i++; - } - } - offset += sizeof(nvs_page); // Prepare to read next page in nvs - pagenr++; - } - - backupFile.close(); - Led_SetPause(false); - - return true; -} - - // handle album cover image request static void handleCoverImageRequest(AsyncWebServerRequest *request) {