mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-09 04:57:06 +03:00
Start of 5.X work
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
idf_component_register( SRCS operator.cpp tools.c trace.c
|
||||
REQUIRES esp_common pthread
|
||||
PRIV_REQUIRES esp_http_client esp_http_server esp-tls json spiffs
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
idf_component_register( SRC_DIRS .
|
||||
REQUIRES esp_common pthread json spiffs
|
||||
PRIV_REQUIRES esp_http_client esp_http_server esp-tls services
|
||||
INCLUDE_DIRS .
|
||||
)
|
||||
|
||||
#doing our own implementation of new operator for some pre-compiled binaries
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u _ZdlPv")
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u _Znwj")
|
||||
|
||||
|
||||
143
components/tools/bootstate.cpp
Normal file
143
components/tools/bootstate.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
#include "bootstate.h"
|
||||
#include "Config.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_ota_ops.h"
|
||||
#include "esp_spi_flash.h"
|
||||
#include "messaging.h"
|
||||
#include "tools.h"
|
||||
static const char* TAG = "bootstate";
|
||||
|
||||
RTC_NOINIT_ATTR uint32_t RebootCounter;
|
||||
RTC_NOINIT_ATTR uint32_t RecoveryRebootCounter;
|
||||
RTC_NOINIT_ATTR uint16_t ColdBootIndicatorFlag;
|
||||
EXT_RAM_ATTR bool is_recovery_running = false;
|
||||
EXT_RAM_ATTR bool cold_boot = true;
|
||||
EXT_RAM_ATTR esp_reset_reason_t xReason = ESP_RST_UNKNOWN;
|
||||
EXT_RAM_ATTR static bool restarting = false;
|
||||
|
||||
uint32_t bootstate_read_counter(void) { return RebootCounter; }
|
||||
uint32_t bootstate_uptate_counter(int32_t xValue) {
|
||||
if (RebootCounter > 100) {
|
||||
RebootCounter = 0;
|
||||
RecoveryRebootCounter = 0;
|
||||
}
|
||||
RebootCounter = (xValue != 0) ? (RebootCounter + xValue) : 0;
|
||||
RecoveryRebootCounter = (xValue != 0) && is_recovery_running ? (RecoveryRebootCounter + xValue) : 0;
|
||||
return RebootCounter;
|
||||
}
|
||||
|
||||
void bootstate_handle_boot() {
|
||||
if (ColdBootIndicatorFlag != 0xFACE) {
|
||||
ESP_LOGI(TAG, "System is booting from power on.");
|
||||
cold_boot = true;
|
||||
ColdBootIndicatorFlag = 0xFACE;
|
||||
} else {
|
||||
cold_boot = false;
|
||||
}
|
||||
const esp_partition_t* running = esp_ota_get_running_partition();
|
||||
|
||||
xReason = esp_reset_reason();
|
||||
ESP_LOGI(TAG, "Reset reason is: %u. Running from partition %s type %s ", xReason, running->label,
|
||||
running->subtype == ESP_PARTITION_SUBTYPE_APP_FACTORY ? "Factory" : "Application");
|
||||
is_recovery_running = (running->subtype == ESP_PARTITION_SUBTYPE_APP_FACTORY);
|
||||
|
||||
if (!is_recovery_running) {
|
||||
/* unscheduled restart (HW, Watchdog or similar) thus increment dynamic
|
||||
* counter then log current boot statistics as a warning */
|
||||
uint32_t Counter = bootstate_uptate_counter(1); // increment counter
|
||||
ESP_LOGI(TAG, "Reboot counter=%u\n", Counter);
|
||||
if (Counter == 5) {
|
||||
guided_factory();
|
||||
}
|
||||
} else {
|
||||
uint32_t Counter = bootstate_uptate_counter(1); // increment counter
|
||||
if (RecoveryRebootCounter == 1 && Counter >= 5) {
|
||||
// First time we are rebooting in recovery after crashing
|
||||
messaging_post_message(MESSAGING_ERROR, MESSAGING_CLASS_SYSTEM,
|
||||
"System was forced into recovery mode after crash likely caused by some bad "
|
||||
"configuration\n");
|
||||
}
|
||||
ESP_LOGI(TAG, "Recovery Reboot counter=%u\n", Counter);
|
||||
if (RecoveryRebootCounter == 5) {
|
||||
ESP_LOGW(TAG, "System rebooted too many times. This could be an indication that "
|
||||
"configuration is corrupted. Erasing config.");
|
||||
if (config_erase_config()) {
|
||||
config_raise_changed(true);
|
||||
guided_factory();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error erasing configuration");
|
||||
}
|
||||
}
|
||||
if (RecoveryRebootCounter > 5) {
|
||||
messaging_post_message(MESSAGING_ERROR, MESSAGING_CLASS_SYSTEM,
|
||||
"System was forced into recovery mode after crash likely caused by some bad "
|
||||
"configuration. Configuration was reset to factory.\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
esp_err_t guided_boot(esp_partition_subtype_t partition_subtype) {
|
||||
if (is_recovery_running) {
|
||||
if (partition_subtype == ESP_PARTITION_SUBTYPE_APP_FACTORY) {
|
||||
simple_restart();
|
||||
}
|
||||
} else {
|
||||
if (partition_subtype != ESP_PARTITION_SUBTYPE_APP_FACTORY) {
|
||||
simple_restart();
|
||||
}
|
||||
}
|
||||
esp_err_t err = ESP_OK;
|
||||
const esp_partition_t* partition;
|
||||
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_APP, partition_subtype, NULL);
|
||||
|
||||
if (it == NULL) {
|
||||
log_send_messaging(MESSAGING_ERROR, "Reboot failed. Partitions error");
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Found partition. Getting info.");
|
||||
partition = (esp_partition_t*)esp_partition_get(it);
|
||||
ESP_LOGD(TAG, "Releasing partition iterator");
|
||||
esp_partition_iterator_release(it);
|
||||
if (partition != NULL) {
|
||||
log_send_messaging(MESSAGING_INFO, "Rebooting to %s", partition->label);
|
||||
err = esp_ota_set_boot_partition(partition);
|
||||
if (err != ESP_OK) {
|
||||
log_send_messaging(MESSAGING_ERROR, "Unable to select partition for reboot: %s", esp_err_to_name(err));
|
||||
}
|
||||
} else {
|
||||
log_send_messaging(MESSAGING_ERROR, "partition type %u not found! Unable to reboot to recovery.", partition_subtype);
|
||||
}
|
||||
ESP_LOGD(TAG, "Yielding to other processes");
|
||||
taskYIELD();
|
||||
simple_restart();
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
esp_err_t guided_restart_ota() {
|
||||
log_send_messaging(MESSAGING_WARNING, "Booting to Squeezelite");
|
||||
guided_boot(ESP_PARTITION_SUBTYPE_APP_OTA_0);
|
||||
return ESP_FAIL; // return fail. This should never return... we're rebooting!
|
||||
}
|
||||
esp_err_t guided_factory() {
|
||||
log_send_messaging(MESSAGING_WARNING, "Booting to recovery");
|
||||
guided_boot(ESP_PARTITION_SUBTYPE_APP_FACTORY);
|
||||
return ESP_FAIL; // return fail. This should never return... we're rebooting!
|
||||
}
|
||||
void simple_restart() {
|
||||
restarting = true;
|
||||
log_send_messaging(MESSAGING_WARNING, "Rebooting.");
|
||||
|
||||
TimerHandle_t timer = xTimerCreate("reboot", 1, pdFALSE, nullptr, [](TimerHandle_t xTimer) {
|
||||
if (!config_waitcommit()) {
|
||||
log_send_messaging(MESSAGING_WARNING, "Waiting for configuration to commit ");
|
||||
ESP_LOGD(TAG,"Queuing restart asynchronously to ensure all events are flushed.");
|
||||
network_async_reboot(RESTART);
|
||||
return;
|
||||
}
|
||||
vTaskDelay(750 / portTICK_PERIOD_MS);
|
||||
esp_restart();
|
||||
xTimerDelete(xTimer, portMAX_DELAY);
|
||||
});
|
||||
xTimerStart(timer, portMAX_DELAY);
|
||||
}
|
||||
bool is_restarting() { return restarting; }
|
||||
126
components/tools/bootstate.h
Normal file
126
components/tools/bootstate.h
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
*
|
||||
* Sebastien L. 2023, sle118@hotmail.com
|
||||
* Philippe G. 2023, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* License Overview:
|
||||
* ----------------
|
||||
* The MIT License is a permissive open source license. As a user of this software, you are free to:
|
||||
* - Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software.
|
||||
* - Use the software for private, commercial, or any other purposes.
|
||||
*
|
||||
* Conditions:
|
||||
* - You must include the above copyright notice and this permission notice in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* The MIT License offers a high degree of freedom and is well-suited for both open source and
|
||||
* commercial applications. It places minimal restrictions on how the software can be used,
|
||||
* modified, and redistributed. For more details on the MIT License, please refer to the link above.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "esp_partition.h"
|
||||
#include "esp_system.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
uint32_t bootstate_read_counter(void);
|
||||
|
||||
/**
|
||||
* @fn uint32_t bootstate_uptate_counter(int32_t xValue)
|
||||
* Updates the boot state counter.
|
||||
*
|
||||
* Increments the RebootCounter by 'xValue'. If 'xValue' is 0, or if RebootCounter
|
||||
* exceeds 100, it resets both RebootCounter and RecoveryRebootCounter to 0.
|
||||
* Additionally, updates the RecoveryRebootCounter based on the recovery state.
|
||||
*
|
||||
* @param xValue The value to increment the RebootCounter.
|
||||
* @return The updated value of the RebootCounter.
|
||||
*/
|
||||
uint32_t bootstate_uptate_counter(int32_t xValue);
|
||||
|
||||
/**
|
||||
* @fn void bootstate_handle_boot(void)
|
||||
* Handles the boot state logic on system startup.
|
||||
*
|
||||
* This function manages boot states based on cold boot indicators, recovery states, and reboot counters.
|
||||
* It includes logic for handling regular and recovery boots, and manages the boot counter's logic
|
||||
* for different scenarios such as factory resets or configuration erasures.
|
||||
*/
|
||||
void bootstate_handle_boot(void);
|
||||
|
||||
/**
|
||||
* @fn esp_err_t guided_boot(esp_partition_subtype_t partition_subtype)
|
||||
* Performs a guided boot to a specified partition subtype.
|
||||
*
|
||||
* This function attempts to reboot the device to a specified partition subtype.
|
||||
* It includes logic to handle different scenarios based on the current running state
|
||||
* (normal or recovery) and the target partition subtype.
|
||||
*
|
||||
* @param partition_subtype The target partition subtype to boot to.
|
||||
* @return ESP_OK on successful execution, or an ESP_ERR code on failure.
|
||||
*/
|
||||
esp_err_t guided_boot(esp_partition_subtype_t partition_subtype);
|
||||
|
||||
/**
|
||||
* @fn esp_err_t guided_restart_ota(void)
|
||||
* Initiates a guided restart to an OTA (Over The Air) update partition.
|
||||
*
|
||||
* This function logs the intention to boot to the Squeezelite OTA partition and then
|
||||
* calls guided_boot to execute the reboot. It always returns ESP_FAIL as the system
|
||||
* is expected to reboot and not return from the function.
|
||||
*
|
||||
* @return ESP_FAIL to indicate the function should not return as a reboot is expected.
|
||||
*/
|
||||
esp_err_t guided_restart_ota(void);
|
||||
|
||||
/**
|
||||
* @fn esp_err_t guided_factory(void)
|
||||
* Initiates a guided restart to the factory partition.
|
||||
*
|
||||
* This function logs the intention to boot to the recovery partition and then
|
||||
* calls guided_boot to execute the reboot. It always returns ESP_FAIL as the system
|
||||
* is expected to reboot and not return from the function.
|
||||
*
|
||||
* @return ESP_FAIL to indicate the function should not return as a reboot is expected.
|
||||
*/
|
||||
esp_err_t guided_factory(void);
|
||||
|
||||
/**
|
||||
* @fn void simple_restart(void)
|
||||
* Performs a simple system restart.
|
||||
*
|
||||
* This function logs a reboot message, attempts to commit any pending configurations,
|
||||
* waits for a short duration, and then triggers a system restart using `esp_restart`.
|
||||
*/
|
||||
void simple_restart(void);
|
||||
|
||||
/**
|
||||
* @fn bool is_restarting(void)
|
||||
* Checks if the system is in the process of restarting.
|
||||
*
|
||||
* @return `true` if the system is currently restarting, `false` otherwise.
|
||||
*/
|
||||
bool is_restarting(void);
|
||||
|
||||
/**
|
||||
* @var extern bool is_recovery_running
|
||||
* Indicates whether the system is currently running in recovery mode.
|
||||
*
|
||||
* This variable is used to determine the current operating mode of the system.
|
||||
* It should be set to `true` when the system is operating in recovery mode, and
|
||||
* `false` otherwise. As an external variable, it is likely defined and managed
|
||||
* in another file/module.
|
||||
*/
|
||||
extern bool is_recovery_running;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,11 +0,0 @@
|
||||
#
|
||||
# Component Makefile
|
||||
#
|
||||
# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default,
|
||||
# this will take the sources in the src/ directory, compile them and link them into
|
||||
# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
|
||||
# please read the SDK documents if you need to do this.
|
||||
#
|
||||
|
||||
COMPONENT_SRCDIRS := .
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
||||
22
components/tools/cpp_tools.cpp
Normal file
22
components/tools/cpp_tools.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "cpp_tools.h"
|
||||
#include <cctype>
|
||||
#include "tools.h"
|
||||
|
||||
std::string trim(const std::string& str) {
|
||||
const size_t first = str.find_first_not_of(' ');
|
||||
if (first == std::string::npos) {
|
||||
// String contains only spaces or is empty
|
||||
return "";
|
||||
}
|
||||
const size_t last = str.find_last_not_of(' ');
|
||||
return str.substr(first, (last - first + 1));
|
||||
}
|
||||
|
||||
std::string& toLowerStr(std::string& str) {
|
||||
for (auto& c : str) {
|
||||
if (c >= 'A' && c <= 'Z') {
|
||||
c = c - 'A' + 'a';
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
48
components/tools/cpp_tools.h
Normal file
48
components/tools/cpp_tools.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
*
|
||||
* Sebastien L. 2023, sle118@hotmail.com
|
||||
* Philippe G. 2023, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* License Overview:
|
||||
* ----------------
|
||||
* The MIT License is a permissive open source license. As a user of this software, you are free to:
|
||||
* - Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software.
|
||||
* - Use the software for private, commercial, or any other purposes.
|
||||
*
|
||||
* Conditions:
|
||||
* - You must include the above copyright notice and this permission notice in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* The MIT License offers a high degree of freedom and is well-suited for both open source and
|
||||
* commercial applications. It places minimal restrictions on how the software can be used,
|
||||
* modified, and redistributed. For more details on the MIT License, please refer to the link above.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <string>
|
||||
/**
|
||||
* @brief Trims leading and trailing whitespace from a string.
|
||||
*
|
||||
* This function removes all leading and trailing spaces from the given string.
|
||||
* It does not modify the original string but returns a new trimmed string.
|
||||
*
|
||||
* @param str The string to trim.
|
||||
* @return std::string A new string with leading and trailing spaces removed.
|
||||
*/
|
||||
std::string trim(const std::string& str);
|
||||
|
||||
/**
|
||||
* @brief Converts a string to lowercase.
|
||||
*
|
||||
* This function modifies the given string in place, converting all characters
|
||||
* to their lowercase equivalents.
|
||||
*
|
||||
* @param str Reference to the string to be converted to lowercase.
|
||||
* @return std::string& Reference to the modified string.
|
||||
*/
|
||||
std::string& toLowerStr(std::string& str);
|
||||
|
||||
#endif
|
||||
@@ -1,18 +1,34 @@
|
||||
/*
|
||||
* Squeezelite for esp32
|
||||
/*
|
||||
*
|
||||
* (c) Sebastien 2019
|
||||
* Philippe G. 2019, philippe_44@outlook.com
|
||||
* Sebastien L. 2023, sle118@hotmail.com
|
||||
* Philippe G. 2023, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* License Overview:
|
||||
* ----------------
|
||||
* The MIT License is a permissive open source license. As a user of this software, you are free to:
|
||||
* - Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software.
|
||||
* - Use the software for private, commercial, or any other purposes.
|
||||
*
|
||||
* Conditions:
|
||||
* - You must include the above copyright notice and this permission notice in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* The MIT License offers a high degree of freedom and is well-suited for both open source and
|
||||
* commercial applications. It places minimal restrictions on how the software can be used,
|
||||
* modified, and redistributed. For more details on the MIT License, please refer to the link above.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sys/time.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define PERF_MAX LONG_MAX
|
||||
#define MIN_MAX_VAL(x) x==PERF_MAX?0:x
|
||||
#define CURR_SAMPLE_RATE output.current_sample_rate>0?output.current_sample_rate:1
|
||||
@@ -76,3 +92,7 @@ static inline bool hasTimeElapsed(time_t delayMS, bool bforce)
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
#ifndef SQUEEZELITE_ESP32_RELEASE_URL
|
||||
#define SQUEEZELITE_ESP32_RELEASE_URL "https://github.com/sle118/squeezelite-esp32/releases"
|
||||
#endif
|
||||
extern bool is_recovery_running;
|
||||
extern bool wait_for_wifi();
|
||||
|
||||
extern bool console_push(const char * data, size_t size);
|
||||
extern void console_start();
|
||||
extern pthread_cond_t wifi_connect_suspend_cond;
|
||||
|
||||
4
components/tools/test/CMakeLists.txt
Normal file
4
components/tools/test/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
idf_component_register(SRC_DIRS .
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES unity tools)
|
||||
target_include_directories(${COMPONENT_LIB} PUBLIC SYSTEM ${CMAKE_SOURCE_DIR}/test_main)
|
||||
387
components/tools/test/test_tools.cpp
Normal file
387
components/tools/test/test_tools.cpp
Normal file
@@ -0,0 +1,387 @@
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#include "esp_log.h"
|
||||
#include "test_common_init.h"
|
||||
#include "tools.h"
|
||||
#include "unity.h"
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "tools_spiffs_utils.h"
|
||||
|
||||
static const char* TAG = "test_tools";
|
||||
|
||||
static const char* data = "SomeTestData\0";
|
||||
#define UINT8T_DATA (const uint8_t*)data
|
||||
static size_t len = strlen(data);
|
||||
|
||||
TEST_CASE("Test Write/Read File", "[tools]") {
|
||||
common_test_init();
|
||||
const char* testfilename = "/spiffs/test_file.bin";
|
||||
size_t loaded = 0;
|
||||
// allow +1 to the length below for null termination of the string
|
||||
TEST_ASSERT_TRUE(write_file(UINT8T_DATA, len + 1, testfilename));
|
||||
char* loaded_data = (char*)load_file_psram(&loaded, testfilename);
|
||||
TEST_ASSERT_EQUAL(loaded, len + 1);
|
||||
TEST_ASSERT_NOT_EQUAL(loaded_data, NULL);
|
||||
TEST_ASSERT_EQUAL_STRING((const char*)data, (char*)loaded_data);
|
||||
free(loaded_data);
|
||||
}
|
||||
|
||||
TEST_CASE("Test Simple Erase File ", "[tools]") {
|
||||
std::ostringstream fullfilename;
|
||||
const char* prefix = "test_file";
|
||||
|
||||
struct stat fileInfo;
|
||||
// test for simple file names
|
||||
fullfilename << "/spiffs/" << prefix << ".bin";
|
||||
TEST_ASSERT_TRUE(write_file(UINT8T_DATA, len, fullfilename.str().c_str()));
|
||||
TEST_ASSERT_TRUE(get_file_info(&fileInfo, fullfilename.str().c_str()));
|
||||
TEST_ASSERT_EQUAL(fileInfo.st_size, len);
|
||||
TEST_ASSERT_TRUE(erase_path(fullfilename.str().c_str(), false));
|
||||
TEST_ASSERT_FALSE(get_file_info(&fileInfo, fullfilename.str().c_str()));
|
||||
fullfilename.clear();
|
||||
fullfilename.str("");
|
||||
}
|
||||
TEST_CASE("Test Wildcard Erase File ", "[tools]") {
|
||||
std::ostringstream fullfilename;
|
||||
std::ostringstream fullfilename2;
|
||||
std::ostringstream fullnamewc;
|
||||
const char* prefix = "/spiffs/test_file";
|
||||
struct stat fileInfo;
|
||||
// Test for wildcard
|
||||
|
||||
fullfilename << prefix << ".bin";
|
||||
fullfilename2 << prefix << ".bin2";
|
||||
fullnamewc << prefix << "*.bin";
|
||||
TEST_ASSERT_TRUE(write_file(UINT8T_DATA, len, fullfilename.str().c_str()));
|
||||
TEST_ASSERT_TRUE(write_file(UINT8T_DATA, len, fullfilename2.str().c_str()));
|
||||
TEST_ASSERT_TRUE(get_file_info(&fileInfo, fullfilename.str().c_str()));
|
||||
TEST_ASSERT_EQUAL(fileInfo.st_size, len);
|
||||
TEST_ASSERT_TRUE(erase_path(fullnamewc.str().c_str(), false));
|
||||
TEST_ASSERT_FALSE(get_file_info(&fileInfo, fullfilename.str().c_str()));
|
||||
TEST_ASSERT_TRUE(get_file_info(&fileInfo, fullfilename2.str().c_str()));
|
||||
fullnamewc.str("");
|
||||
fullnamewc << prefix << ".bin*";
|
||||
TEST_ASSERT_TRUE(erase_path(fullnamewc.str().c_str(), false));
|
||||
TEST_ASSERT_FALSE(get_file_info(&fileInfo, fullfilename2.str().c_str()));
|
||||
}
|
||||
TEST_CASE("Test Folder Erase File ", "[tools]") {
|
||||
std::ostringstream fullfilename;
|
||||
const char* prefix = "test_file";
|
||||
struct stat fileInfo;
|
||||
fullfilename << "/spiffs/somefolder/" << prefix << ".bin";
|
||||
TEST_ASSERT_TRUE(write_file(UINT8T_DATA, len, fullfilename.str().c_str()));
|
||||
TEST_ASSERT_TRUE(get_file_info(&fileInfo, fullfilename.str().c_str()));
|
||||
TEST_ASSERT_EQUAL(fileInfo.st_size, len);
|
||||
TEST_ASSERT_TRUE(erase_path(fullfilename.str().c_str(), false));
|
||||
TEST_ASSERT_FALSE(get_file_info(&fileInfo, fullfilename.str().c_str()));
|
||||
}
|
||||
TEST_CASE("Test Folder Wildcard Erase File ", "[tools]") {
|
||||
std::ostringstream fullfilename;
|
||||
std::ostringstream fullnamewc;
|
||||
const char* prefix = "test_file";
|
||||
struct stat fileInfo;
|
||||
|
||||
fullfilename << "/spiffs/somefolder/" << prefix << ".bin";
|
||||
fullnamewc << prefix << "*.bin";
|
||||
TEST_ASSERT_TRUE(write_file(UINT8T_DATA, len, fullfilename.str().c_str()));
|
||||
TEST_ASSERT_TRUE(get_file_info(&fileInfo, fullfilename.str().c_str()));
|
||||
TEST_ASSERT_EQUAL(fileInfo.st_size, len);
|
||||
TEST_ASSERT_TRUE(erase_path(fullfilename.str().c_str(), false));
|
||||
TEST_ASSERT_FALSE(get_file_info(&fileInfo, fullnamewc.str().c_str()));
|
||||
}
|
||||
|
||||
const std::vector<std::string> testRestrictedFiles = {"defaults/config.bin",
|
||||
"fonts/droid_sans_fb_11x13.bin", "targets/bureau-oled/config.bin", "www/favicon-32x32.png"};
|
||||
|
||||
TEST_CASE("Test _write_file Function", "[tools]") {
|
||||
init_spiffs();
|
||||
const uint8_t testData[] = {0x01, 0x02, 0x03, 0x04};
|
||||
size_t testDataSize = sizeof(testData);
|
||||
const char* testFileName = "/spiffs/testfile.bin";
|
||||
|
||||
// Test writing valid data
|
||||
TEST_ASSERT_TRUE(write_file(testData, testDataSize, testFileName));
|
||||
|
||||
// Verify file size
|
||||
struct stat fileInfo;
|
||||
TEST_ASSERT_TRUE(get_file_info(&fileInfo, testFileName));
|
||||
TEST_ASSERT_EQUAL_UINT32(testDataSize, fileInfo.st_size);
|
||||
|
||||
// Verify file content
|
||||
size_t loadedSize;
|
||||
uint8_t* loadedData = (uint8_t*)load_file_psram(&loadedSize, testFileName);
|
||||
TEST_ASSERT_NOT_NULL(loadedData);
|
||||
TEST_ASSERT_EQUAL_UINT32(testDataSize, loadedSize);
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(testData, loadedData, testDataSize);
|
||||
free(loadedData);
|
||||
ESP_LOGI(TAG, "Erasing test file");
|
||||
TEST_ASSERT_TRUE(erase_path(testFileName, false));
|
||||
|
||||
// Test writing with null data
|
||||
ESP_LOGI(TAG, "Test Invalid write_file with NULL pointer");
|
||||
TEST_ASSERT_FALSE(write_file(NULL, testDataSize, testFileName));
|
||||
|
||||
// Test writing with zero size
|
||||
ESP_LOGI(TAG, "Test Invalid write_file with data length 0");
|
||||
TEST_ASSERT_FALSE(write_file(testData, 0, testFileName));
|
||||
}
|
||||
|
||||
TEST_CASE("Test _open_file Function", "[tools]") {
|
||||
init_spiffs();
|
||||
const char* testFileName = "/spiffs/test_open_file.bin";
|
||||
const char* testFileContent = "Hello, world!";
|
||||
size_t contentLength = strlen(testFileContent);
|
||||
|
||||
// Test opening a file for writing
|
||||
FILE* writeFile = fopen(testFileName, "w");
|
||||
TEST_ASSERT_NOT_NULL(writeFile);
|
||||
fwrite(testFileContent, 1, contentLength, writeFile);
|
||||
fclose(writeFile);
|
||||
|
||||
// Test opening the same file for reading
|
||||
FILE* readFile = fopen(testFileName, "r");
|
||||
TEST_ASSERT_NOT_NULL(readFile);
|
||||
char buffer[100];
|
||||
size_t bytesRead = fread(buffer, 1, contentLength, readFile);
|
||||
TEST_ASSERT_EQUAL_UINT32(contentLength, bytesRead);
|
||||
buffer[bytesRead] = '\0'; // Null-terminate the string
|
||||
TEST_ASSERT_EQUAL_STRING(testFileContent, buffer);
|
||||
fclose(readFile);
|
||||
|
||||
// Test opening a file with an invalid mode
|
||||
FILE* invalidFile = fopen(testFileName, "invalid_mode");
|
||||
TEST_ASSERT_NULL(invalidFile);
|
||||
|
||||
TEST_ASSERT_TRUE(erase_path(testFileName, false));
|
||||
|
||||
// Test opening a non-existent file for reading
|
||||
FILE* nonExistentFile = fopen("/spiffs/non_existent_file.bin", "r");
|
||||
TEST_ASSERT_NULL(nonExistentFile);
|
||||
}
|
||||
|
||||
TEST_CASE("Test _load_file and _get_file_info Functions", "[tools]") {
|
||||
init_spiffs();
|
||||
const char* testFileName = "/spiffs/test_load_file.bin";
|
||||
const char* testFileContent = "Hello, world!";
|
||||
size_t contentLength = strlen(testFileContent);
|
||||
|
||||
// Write test data to a file
|
||||
FILE* writeFile = fopen(testFileName, "w");
|
||||
TEST_ASSERT_NOT_NULL(writeFile);
|
||||
fwrite(testFileContent, 1, contentLength, writeFile);
|
||||
fclose(writeFile);
|
||||
|
||||
// Test loading file content
|
||||
size_t loadedSize;
|
||||
uint8_t* loadedData = (uint8_t*)load_file_psram(&loadedSize, testFileName);
|
||||
TEST_ASSERT_NOT_NULL(loadedData);
|
||||
TEST_ASSERT_EQUAL_UINT32(contentLength, loadedSize);
|
||||
TEST_ASSERT_EQUAL_UINT8_ARRAY(testFileContent, loadedData, contentLength);
|
||||
free(loadedData);
|
||||
|
||||
// Test getting file information
|
||||
struct stat fileInfo;
|
||||
TEST_ASSERT_TRUE(get_file_info(&fileInfo, testFileName));
|
||||
TEST_ASSERT_EQUAL_UINT32(contentLength, fileInfo.st_size);
|
||||
TEST_ASSERT_TRUE(erase_path(testFileName, false));
|
||||
|
||||
// Test loading a non-existent file
|
||||
uint8_t* nonExistentData =
|
||||
(uint8_t*)load_file_psram(&loadedSize, "/spiffs/non_existent_file.bin");
|
||||
TEST_ASSERT_NULL(nonExistentData);
|
||||
|
||||
// Test getting information for a non-existent file
|
||||
TEST_ASSERT_FALSE(get_file_info(&fileInfo, "/spiffs/non_existent_file.bin"));
|
||||
}
|
||||
|
||||
TEST_CASE("Test trim Function", "[tools]") {
|
||||
// Test trimming a string with leading and trailing spaces
|
||||
std::string str1 = " Hello World ";
|
||||
TEST_ASSERT_EQUAL_STRING("Hello World", trim(str1).c_str());
|
||||
|
||||
// Test trimming a string with no leading or trailing spaces
|
||||
std::string str2 = "Hello World";
|
||||
TEST_ASSERT_EQUAL_STRING("Hello World", trim(str2).c_str());
|
||||
|
||||
// Test trimming an empty string
|
||||
std::string str3 = "";
|
||||
TEST_ASSERT_EQUAL_STRING("", trim(str3).c_str());
|
||||
|
||||
// Test trimming a string with only spaces
|
||||
std::string str4 = " ";
|
||||
TEST_ASSERT_EQUAL_STRING("", trim(str4).c_str());
|
||||
}
|
||||
|
||||
TEST_CASE("Test toLowerStr Function", "[tools]") {
|
||||
// Test converting a mixed case string to lowercase
|
||||
std::string str1 = "Hello World";
|
||||
toLowerStr(str1);
|
||||
TEST_ASSERT_EQUAL_STRING("hello world", str1.c_str());
|
||||
|
||||
// Test converting an already lowercase string
|
||||
std::string str2 = "hello world";
|
||||
toLowerStr(str2);
|
||||
TEST_ASSERT_EQUAL_STRING("hello world", str2.c_str());
|
||||
|
||||
// Test converting an uppercase string
|
||||
std::string str3 = "HELLO WORLD";
|
||||
toLowerStr(str3);
|
||||
TEST_ASSERT_EQUAL_STRING("hello world", str3.c_str());
|
||||
|
||||
// Test converting an empty string
|
||||
std::string str4 = "";
|
||||
toLowerStr(str4);
|
||||
TEST_ASSERT_EQUAL_STRING("", str4.c_str());
|
||||
|
||||
// Test converting a string with special characters
|
||||
std::string str5 = "Hello-World_123";
|
||||
toLowerStr(str5);
|
||||
TEST_ASSERT_EQUAL_STRING("hello-world_123", str5.c_str());
|
||||
}
|
||||
static const std::vector<std::string> testRestrictedPaths = {
|
||||
"/spiffs/defaults/file.txt", // A restricted path
|
||||
"/spiffs/fonts/otherfile.txt", // A restricted path
|
||||
"/spiffs/targets/somefile.txt", // A restricted path
|
||||
"/spiffs/targets/*.txt", // A restricted path
|
||||
"/spiffs/www/index.html" // A restricted path
|
||||
};
|
||||
TEST_CASE("Test is_restricted_path with restricted paths", "[tools]") {
|
||||
for (const std::string& path : testRestrictedPaths) {
|
||||
bool result = is_restricted_path(path.c_str());
|
||||
TEST_ASSERT_TRUE(result);
|
||||
}
|
||||
}
|
||||
|
||||
// Negative Testing
|
||||
TEST_CASE("Test is_restricted_path with non-restricted paths", "[tools]") {
|
||||
const std::vector<std::string> nonRestrictedPaths = {
|
||||
"/spiffs/allowed/file.txt", // Not a restricted path
|
||||
"/spiffs/allowed/otherfile.txt" // Not a restricted path
|
||||
};
|
||||
|
||||
for (const std::string& path : nonRestrictedPaths) {
|
||||
bool result = is_restricted_path(path.c_str());
|
||||
TEST_ASSERT_FALSE(result);
|
||||
}
|
||||
}
|
||||
static const std::vector<std::string> testWildcardPaths = {
|
||||
"/spiffs/defaults/file.txt", // A restricted path
|
||||
"/spiffs/fonts/otherfile.txt", // A restricted path
|
||||
"/spiffs/targets/somefile.txt", // A restricted path
|
||||
"/spiffs/www/index.html", // A restricted path
|
||||
"/spiffs/defaults/*", // A path with wildcard
|
||||
"/spiffs/fonts/*" // A path with wildcard
|
||||
};
|
||||
TEST_CASE("Test is_restricted_path with wildcards (positive)", "[tools]") {
|
||||
for (const std::string& path : testWildcardPaths) {
|
||||
bool result = is_restricted_path(path.c_str());
|
||||
if (!result) {
|
||||
ESP_LOGE(TAG, "Unexpected result. File should be restricted: %s", path.c_str());
|
||||
}
|
||||
TEST_ASSERT_TRUE(result);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Test Erase Restricted Path", "[erase_path]") {
|
||||
|
||||
for (const std::string& path : testRestrictedFiles) {
|
||||
std::ostringstream fullfilename;
|
||||
fullfilename << "/spiffs/" << path;
|
||||
|
||||
// Check if the file exists before erasing
|
||||
struct stat fileInfoBefore;
|
||||
TEST_ASSERT_TRUE(get_file_info(&fileInfoBefore, fullfilename.str().c_str()));
|
||||
|
||||
// Attempt to erase the restricted path
|
||||
TEST_ASSERT_FALSE(erase_path(fullfilename.str().c_str(), true));
|
||||
|
||||
// Check if the file still exists after the erase attempt
|
||||
struct stat fileInfoAfter;
|
||||
TEST_ASSERT_TRUE(get_file_info(&fileInfoAfter, fullfilename.str().c_str()));
|
||||
TEST_ASSERT_EQUAL(fileInfoBefore.st_ino, fileInfoAfter.st_ino);
|
||||
}
|
||||
}
|
||||
|
||||
// Test case to attempt erasing restricted files with wildcards
|
||||
TEST_CASE("Test Erase Restricted Files with Wildcards", "[erase_path]") {
|
||||
// Get a list of restricted files based on restrictedPaths
|
||||
std::list<tools_file_entry_t> restrictedFiles = get_files_list(std::string("/"));
|
||||
|
||||
for (const tools_file_entry_t& restrictedFile : restrictedFiles) {
|
||||
std::string fullfilename = "/" + restrictedFile.name;
|
||||
|
||||
// Check if the file exists before erasing
|
||||
struct stat fileInfoBefore;
|
||||
TEST_ASSERT_TRUE(get_file_info(&fileInfoBefore, fullfilename.c_str()));
|
||||
|
||||
// Attempt to erase the file with wildcard pattern
|
||||
TEST_ASSERT_FALSE(erase_path(fullfilename.c_str(), true));
|
||||
|
||||
// Check if the file still exists after the erase attempt
|
||||
struct stat fileInfoAfter;
|
||||
TEST_ASSERT_TRUE(get_file_info(&fileInfoAfter, fullfilename.c_str()));
|
||||
TEST_ASSERT_EQUAL(fileInfoBefore.st_ino, fileInfoAfter.st_ino);
|
||||
}
|
||||
}
|
||||
// Test case to create a file and delete it bypassing restrictions
|
||||
TEST_CASE("Test Create and Delete File Bypassing Restrictions", "[erase_path]") {
|
||||
const uint8_t testData[] = {0x01, 0x02, 0x03, 0x04};
|
||||
size_t testDataSize = sizeof(testData);
|
||||
const char* testFileName = "/spiffs/defaults/test_file.bin";
|
||||
|
||||
// Test writing valid data to create the file
|
||||
TEST_ASSERT_TRUE(write_file(testData, testDataSize, testFileName));
|
||||
|
||||
// Verify file size
|
||||
struct stat fileInfo;
|
||||
TEST_ASSERT_TRUE(get_file_info(&fileInfo, testFileName));
|
||||
TEST_ASSERT_EQUAL_UINT32(testDataSize, fileInfo.st_size);
|
||||
|
||||
// Attempt to erase the file with bypassing restrictions (false)
|
||||
TEST_ASSERT_TRUE(erase_path(testFileName, false));
|
||||
|
||||
// Verify that the file no longer exists
|
||||
TEST_ASSERT_FALSE(get_file_info(&fileInfo, testFileName));
|
||||
}
|
||||
|
||||
// Test function to create a file, check its presence and unrestricted status, and delete it
|
||||
TEST_CASE("Create, Check, and Delete File in SPIFFS", "[spiffs]") {
|
||||
const char* testFileName = "/spiffs/somerandomfile.bin";
|
||||
const uint8_t testData[] = {0x01, 0x02, 0x03, 0x04};
|
||||
size_t testDataSize = sizeof(testData);
|
||||
|
||||
// Create a file
|
||||
TEST_ASSERT_TRUE(write_file(testData, testDataSize, testFileName));
|
||||
|
||||
// Get the list of files
|
||||
std::list<tools_file_entry_t> fileList = get_files_list("/spiffs");
|
||||
|
||||
// Check if the new file is in the list and is unrestricted
|
||||
bool fileFound = false;
|
||||
bool fileUnrestricted = false;
|
||||
for (const auto& fileEntry : fileList) {
|
||||
if (fileEntry.name == testFileName) {
|
||||
fileFound = true;
|
||||
fileUnrestricted = !fileEntry.restricted;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_ASSERT_TRUE(fileFound);
|
||||
TEST_ASSERT_TRUE(fileUnrestricted);
|
||||
|
||||
// Delete the file
|
||||
TEST_ASSERT_TRUE(erase_path(testFileName, false)); // Assuming false bypasses restrictions
|
||||
fileFound = false;
|
||||
// Verify that the file no longer exists
|
||||
fileList = get_files_list("/spiffs");
|
||||
for (const auto& fileEntry : fileList) {
|
||||
if (std::string(testFileName) == fileEntry.name) {
|
||||
fileFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_ASSERT_FALSE(fileFound);
|
||||
}
|
||||
@@ -6,25 +6,22 @@
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
*/
|
||||
// #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_INFO
|
||||
#include "tools.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_task.h"
|
||||
#include "esp_tls.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
#include "esp_http_server.h"
|
||||
#if CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS < 2
|
||||
#error CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS must be at least 2
|
||||
#endif
|
||||
static bool initialized = false;
|
||||
|
||||
const static char TAG[] = "tools";
|
||||
static esp_vfs_spiffs_conf_t* spiffs_conf = NULL;
|
||||
const char unknown_string_placeholder[] = "unknown";
|
||||
const char null_string_placeholder[] = "null";
|
||||
|
||||
/****************************************************************************************
|
||||
* UTF-8 tools
|
||||
@@ -67,6 +64,15 @@ static const uint8_t utf8d[] = {
|
||||
1, // s7..s8
|
||||
};
|
||||
|
||||
/**
|
||||
* @fn static uint32_t decode(uint32_t* state, uint32_t* codep, uint32_t byte)
|
||||
* Decodes a single UTF-8 encoded byte.
|
||||
*
|
||||
* @param state A pointer to the current state of the UTF-8 decoder.
|
||||
* @param codep A pointer to the code point being built from the UTF-8 bytes.
|
||||
* @param byte The current byte to be decoded.
|
||||
* @return The new state after processing the byte.
|
||||
*/
|
||||
static uint32_t decode(uint32_t* state, uint32_t* codep, uint32_t byte) {
|
||||
uint32_t type = utf8d[byte];
|
||||
|
||||
@@ -76,6 +82,13 @@ static uint32_t decode(uint32_t* state, uint32_t* codep, uint32_t byte) {
|
||||
return *state;
|
||||
}
|
||||
|
||||
/**
|
||||
* @fn static uint8_t UNICODEtoCP1252(uint16_t chr)
|
||||
* Converts a Unicode character to its corresponding CP1252 character.
|
||||
*
|
||||
* @param chr The Unicode character to be converted.
|
||||
* @return The corresponding CP1252 character or 0x00 if there is no direct mapping.
|
||||
*/
|
||||
static uint8_t UNICODEtoCP1252(uint16_t chr) {
|
||||
if (chr <= 0xff)
|
||||
return (chr & 0xff);
|
||||
@@ -181,27 +194,6 @@ void utf8_decode(char* src) {
|
||||
*dst = '\0';
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* URL tools
|
||||
*/
|
||||
|
||||
static inline char from_hex(char ch) { return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10; }
|
||||
|
||||
void url_decode(char* url) {
|
||||
char *p, *src = strdup(url);
|
||||
for (p = src; *src; url++) {
|
||||
*url = *src++;
|
||||
if (*url == '%') {
|
||||
*url = from_hex(*src++) << 4;
|
||||
*url |= from_hex(*src++);
|
||||
} else if (*url == '+') {
|
||||
*url = ' ';
|
||||
}
|
||||
}
|
||||
*url = '\0';
|
||||
free(p);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* Memory tools
|
||||
*/
|
||||
@@ -233,8 +225,7 @@ char* strdup_psram(const char* source) {
|
||||
size_t source_sz = strlen(source) + 1;
|
||||
ptr = heap_caps_malloc(source_sz, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if (ptr == NULL) {
|
||||
ESP_LOGE(TAG, "strdup_psram: unable to allocate %d bytes of PSRAM! Cannot clone string %s",
|
||||
source_sz, source);
|
||||
ESP_LOGE(TAG, "strdup_psram: unable to allocate %d bytes of PSRAM! Cannot clone string %s", source_sz, source);
|
||||
} else {
|
||||
memset(ptr, 0x00, source_sz);
|
||||
strcpy(ptr, source);
|
||||
@@ -247,27 +238,42 @@ char* strdup_psram(const char* source) {
|
||||
*/
|
||||
#define TASK_TLS_INDEX 1
|
||||
|
||||
/**
|
||||
* @struct task_context_t
|
||||
* Structure to hold the context of a task including its task buffer and stack.
|
||||
*
|
||||
* @var StaticTask_t* xTaskBuffer
|
||||
* Pointer to the task's control block.
|
||||
*
|
||||
* @var StackType_t* xStack
|
||||
* Pointer to the task's stack.
|
||||
*/
|
||||
typedef struct {
|
||||
StaticTask_t* xTaskBuffer;
|
||||
StackType_t* xStack;
|
||||
} task_context_t;
|
||||
|
||||
/**
|
||||
* @fn static void task_cleanup(int index, task_context_t* context)
|
||||
* Cleans up the resources allocated for a task.
|
||||
* This function is intended to be used as a callback for task deletion.
|
||||
*
|
||||
* @param index The TLS index where the task's context is stored.
|
||||
* @param context Pointer to the task's context to be cleaned up.
|
||||
*/
|
||||
static void task_cleanup(int index, task_context_t* context) {
|
||||
free(context->xTaskBuffer);
|
||||
free(context->xStack);
|
||||
free(context);
|
||||
}
|
||||
|
||||
BaseType_t xTaskCreateEXTRAM(TaskFunction_t pvTaskCode, const char* const pcName,
|
||||
configSTACK_DEPTH_TYPE usStackDepth, void* pvParameters, UBaseType_t uxPriority,
|
||||
TaskHandle_t* pxCreatedTask) {
|
||||
BaseType_t xTaskCreateEXTRAM(TaskFunction_t pvTaskCode, const char* const pcName, configSTACK_DEPTH_TYPE usStackDepth, void* pvParameters,
|
||||
UBaseType_t uxPriority, TaskHandle_t* pxCreatedTask) {
|
||||
// create the worker task as a static
|
||||
task_context_t* context = calloc(1, sizeof(task_context_t));
|
||||
context->xTaskBuffer = (StaticTask_t*)heap_caps_malloc(
|
||||
sizeof(StaticTask_t), (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
|
||||
context->xTaskBuffer = (StaticTask_t*)heap_caps_malloc(sizeof(StaticTask_t), (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
|
||||
context->xStack = heap_caps_malloc(usStackDepth, (MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
|
||||
TaskHandle_t handle = xTaskCreateStatic(pvTaskCode, pcName, usStackDepth, pvParameters,
|
||||
uxPriority, context->xStack, context->xTaskBuffer);
|
||||
TaskHandle_t handle = xTaskCreateStatic(pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, context->xStack, context->xTaskBuffer);
|
||||
|
||||
// store context in TCB or free everything in case of failure
|
||||
if (!handle) {
|
||||
@@ -275,8 +281,7 @@ BaseType_t xTaskCreateEXTRAM(TaskFunction_t pvTaskCode, const char* const pcName
|
||||
free(context->xStack);
|
||||
free(context);
|
||||
} else {
|
||||
vTaskSetThreadLocalStoragePointerAndDelCallback(
|
||||
handle, TASK_TLS_INDEX, context, (TlsDeleteCallbackFunction_t)task_cleanup);
|
||||
vTaskSetThreadLocalStoragePointerAndDelCallback(handle, TASK_TLS_INDEX, context, (TlsDeleteCallbackFunction_t)task_cleanup);
|
||||
}
|
||||
|
||||
if (pxCreatedTask) *pxCreatedTask = handle;
|
||||
@@ -291,104 +296,6 @@ void vTaskDeleteEXTRAM(TaskHandle_t xTask) {
|
||||
vTaskDelete(xTask);
|
||||
}
|
||||
|
||||
/****************************************************************************************
|
||||
* URL download
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
void* user_context;
|
||||
http_download_cb_t callback;
|
||||
size_t max, bytes;
|
||||
bool abort;
|
||||
uint8_t* data;
|
||||
esp_http_client_handle_t client;
|
||||
} http_context_t;
|
||||
|
||||
static void http_downloader(void* arg);
|
||||
static esp_err_t http_event_handler(esp_http_client_event_t* evt);
|
||||
|
||||
void http_download(char* url, size_t max, http_download_cb_t callback, void* context) {
|
||||
http_context_t* http_context =
|
||||
(http_context_t*)heap_caps_calloc(sizeof(http_context_t), 1, MALLOC_CAP_SPIRAM);
|
||||
|
||||
esp_http_client_config_t config = {
|
||||
.url = url,
|
||||
.event_handler = http_event_handler,
|
||||
.user_data = http_context,
|
||||
};
|
||||
|
||||
http_context->callback = callback;
|
||||
http_context->user_context = context;
|
||||
http_context->max = max;
|
||||
http_context->client = esp_http_client_init(&config);
|
||||
|
||||
xTaskCreateEXTRAM(
|
||||
http_downloader, "downloader", 8 * 1024, http_context, ESP_TASK_PRIO_MIN + 1, NULL);
|
||||
}
|
||||
|
||||
static void http_downloader(void* arg) {
|
||||
http_context_t* http_context = (http_context_t*)arg;
|
||||
|
||||
esp_http_client_perform(http_context->client);
|
||||
esp_http_client_cleanup(http_context->client);
|
||||
|
||||
free(http_context);
|
||||
vTaskDeleteEXTRAM(NULL);
|
||||
}
|
||||
|
||||
static esp_err_t http_event_handler(esp_http_client_event_t* evt) {
|
||||
http_context_t* http_context = (http_context_t*)evt->user_data;
|
||||
|
||||
if (http_context->abort) return ESP_FAIL;
|
||||
|
||||
switch (evt->event_id) {
|
||||
case HTTP_EVENT_ERROR:
|
||||
http_context->callback(NULL, 0, http_context->user_context);
|
||||
http_context->abort = true;
|
||||
break;
|
||||
case HTTP_EVENT_ON_HEADER:
|
||||
if (!strcasecmp(evt->header_key, "Content-Length")) {
|
||||
size_t len = atoi(evt->header_value);
|
||||
if (!len || len > http_context->max) {
|
||||
ESP_LOGI(TAG, "content-length null or too large %zu / %zu", len, http_context->max);
|
||||
http_context->abort = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case HTTP_EVENT_ON_DATA: {
|
||||
size_t len = esp_http_client_get_content_length(evt->client);
|
||||
if (!http_context->data) {
|
||||
if ((http_context->data = (uint8_t*)malloc(len)) == NULL) {
|
||||
http_context->abort = true;
|
||||
ESP_LOGE(TAG, "failed to allocate memory for output buffer %zu", len);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
memcpy(http_context->data + http_context->bytes, evt->data, evt->data_len);
|
||||
http_context->bytes += evt->data_len;
|
||||
break;
|
||||
}
|
||||
case HTTP_EVENT_ON_FINISH:
|
||||
http_context->callback(http_context->data, http_context->bytes, http_context->user_context);
|
||||
break;
|
||||
case HTTP_EVENT_DISCONNECTED: {
|
||||
int mbedtls_err = 0;
|
||||
esp_err_t err = esp_tls_get_and_clear_last_error(evt->data, &mbedtls_err, NULL);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "HTTP download disconnect %d", err);
|
||||
if (http_context->data) free(http_context->data);
|
||||
http_context->callback(NULL, 0, http_context->user_context);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void dump_json_content(const char* prefix, cJSON* json, int level) {
|
||||
if (!json) {
|
||||
ESP_LOG_LEVEL(level, TAG, "%s: empty!", prefix);
|
||||
@@ -400,159 +307,10 @@ void dump_json_content(const char* prefix, cJSON* json, int level) {
|
||||
}
|
||||
FREE_AND_NULL(output);
|
||||
}
|
||||
void init_spiffs() {
|
||||
if (initialized) {
|
||||
ESP_LOGD(TAG, "SPIFFS already initialized. returning");
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Initializing the SPI File system");
|
||||
spiffs_conf = (esp_vfs_spiffs_conf_t*)malloc(sizeof(esp_vfs_spiffs_conf_t));
|
||||
spiffs_conf->base_path = "/spiffs";
|
||||
spiffs_conf->partition_label = NULL;
|
||||
spiffs_conf->max_files = 5;
|
||||
spiffs_conf->format_if_mount_failed = true;
|
||||
|
||||
// Use settings defined above to initialize and mount SPIFFS filesystem.
|
||||
// Note: esp_vfs_spiffs_register is an all-in-one convenience function.
|
||||
esp_err_t ret = esp_vfs_spiffs_register(spiffs_conf);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Failed to mount or format filesystem");
|
||||
} else if (ret == ESP_ERR_NOT_FOUND) {
|
||||
ESP_LOGE(TAG, "Failed to find SPIFFS partition");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
size_t total = 0, used = 0;
|
||||
ret = esp_spiffs_info(spiffs_conf->partition_label, &total, &used);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to get SPIFFS partition information (%s). Formatting...",
|
||||
esp_err_to_name(ret));
|
||||
esp_spiffs_format(spiffs_conf->partition_label);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
|
||||
}
|
||||
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
// Function to safely append a path part with '/' if needed
|
||||
void append_path_part(char** dest, const char* part) {
|
||||
if ((*dest)[-1] != '/' && part[0] != '/') {
|
||||
strcat(*dest, "/");
|
||||
*dest += 1; // Move the pointer past the '/'
|
||||
}
|
||||
strcat(*dest, part);
|
||||
*dest += strlen(part);
|
||||
}
|
||||
|
||||
// Function to calculate the total length needed for the new path
|
||||
size_t calculate_total_length(const char* base_path, va_list args) {
|
||||
ESP_LOGV(TAG, "%s, Starting with base path: %s", "calculate_total_length", base_path);
|
||||
size_t length = strlen(base_path) + 1; // +1 for null terminator
|
||||
const char* part;
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
while ((part = va_arg(args_copy, const char*)) != NULL) {
|
||||
ESP_LOGV(TAG, "Adding length of %s", part);
|
||||
length += strlen(part) + 1; // +1 for potential '/'
|
||||
}
|
||||
ESP_LOGV(TAG, "Done looping. calculated length: %d", length);
|
||||
va_end(args_copy);
|
||||
return length;
|
||||
}
|
||||
|
||||
// Main function to join paths
|
||||
char* __alloc_join_path(const char* base_path, va_list args) {
|
||||
size_t count = 0;
|
||||
ESP_LOGD(TAG, "Getting path length starting with %s", base_path);
|
||||
size_t total_length = calculate_total_length(base_path, args);
|
||||
|
||||
// Allocate memory
|
||||
char* full_path = malloc_init_external(total_length);
|
||||
if (!full_path) {
|
||||
ESP_LOGE(TAG, "Unable to allocate memory for path");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Start constructing the path
|
||||
strcpy(full_path, base_path);
|
||||
char* current_position = full_path + strlen(full_path);
|
||||
|
||||
// Append each path part
|
||||
const char* part;
|
||||
while ((part = va_arg(args, const char*)) != NULL) {
|
||||
append_path_part(¤t_position, part);
|
||||
}
|
||||
*current_position = '\0'; // Null-terminate the string
|
||||
return full_path;
|
||||
}
|
||||
char* _alloc_join_path(const char* base_path, ...) {
|
||||
va_list args;
|
||||
ESP_LOGD(TAG, "%s", "join_path_var_parms");
|
||||
va_start(args, base_path);
|
||||
char* result = __alloc_join_path(base_path, args);
|
||||
va_end(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
FILE* __open_file(const char* mode, va_list args) {
|
||||
FILE* file = NULL;
|
||||
char* fullfilename = __alloc_join_path(spiffs_conf->base_path, args);
|
||||
if (!fullfilename) {
|
||||
ESP_LOGE(TAG, "Open file failed: unable to determine name");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Opening file %s in mode %s ", fullfilename, mode);
|
||||
file = fopen(fullfilename, mode);
|
||||
}
|
||||
if (file == NULL) {
|
||||
ESP_LOGE(TAG, "Open file failed");
|
||||
}
|
||||
if (fullfilename) free(fullfilename);
|
||||
return file;
|
||||
}
|
||||
FILE* _open_file(const char* mode, ...) {
|
||||
va_list args;
|
||||
FILE* file = NULL;
|
||||
va_start(args, mode);
|
||||
file = __open_file(mode, args);
|
||||
va_end(args);
|
||||
return file;
|
||||
}
|
||||
bool _write_file(uint8_t* data, size_t sz, ...) {
|
||||
bool result = true;
|
||||
FILE* file = NULL;
|
||||
va_list args;
|
||||
if (data == NULL) {
|
||||
ESP_LOGE(TAG, "Cannot write file. Data not received");
|
||||
return false;
|
||||
}
|
||||
if (sz == 0) {
|
||||
ESP_LOGE(TAG, "Cannot write file. Data length 0");
|
||||
return false;
|
||||
}
|
||||
va_start(args, sz);
|
||||
file = __open_file("w+", args);
|
||||
va_end(args);
|
||||
if (file == NULL) {
|
||||
return false;
|
||||
}
|
||||
size_t written = fwrite(data, 1, sz, file);
|
||||
if (written != sz) {
|
||||
ESP_LOGE(TAG, "Write error. Wrote %d bytes of %d.", written, sz);
|
||||
result = false;
|
||||
}
|
||||
fclose(file);
|
||||
return result;
|
||||
}
|
||||
const char* get_mem_flag_desc(int flags) {
|
||||
static char flagString[101];
|
||||
memset(flagString,0x00,sizeof(flagString));
|
||||
static char flagString[101];
|
||||
memset(flagString, 0x00, sizeof(flagString));
|
||||
if (flags & MALLOC_CAP_EXEC) strcat(flagString, "EXEC ");
|
||||
if (flags & MALLOC_CAP_32BIT) strcat(flagString, "32BIT ");
|
||||
if (flags & MALLOC_CAP_8BIT) strcat(flagString, "8BIT ");
|
||||
@@ -571,65 +329,7 @@ const char* get_mem_flag_desc(int flags) {
|
||||
|
||||
return flagString;
|
||||
}
|
||||
void* _load_file(uint32_t memflags,size_t* sz, ...) {
|
||||
void* data = NULL;
|
||||
FILE* file = NULL;
|
||||
size_t fsz = 0;
|
||||
va_list args;
|
||||
va_start(args, sz);
|
||||
file = __open_file("rb", args);
|
||||
va_end(args);
|
||||
|
||||
if (file == NULL) {
|
||||
return data;
|
||||
}
|
||||
fseek(file, 0, SEEK_END);
|
||||
fsz = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
if (fsz > 0) {
|
||||
ESP_LOGD(TAG, "Allocating %d bytes to load file content with flags: %s ", fsz,get_mem_flag_desc(memflags));
|
||||
data = (void*)heap_caps_calloc(1, fsz, memflags);
|
||||
if (data == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to allocate %d bytes to load file", fsz);
|
||||
} else {
|
||||
fread(data, 1, fsz, file);
|
||||
if (sz) {
|
||||
*sz = fsz;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "File is empty. Nothing to read");
|
||||
}
|
||||
fclose(file);
|
||||
return data;
|
||||
}
|
||||
bool _get_file_info(struct stat* pfileInfo, ...) {
|
||||
va_list args;
|
||||
struct stat fileInfo;
|
||||
va_start(args, pfileInfo);
|
||||
char* fullfilename = __alloc_join_path(spiffs_conf->base_path, args);
|
||||
va_end(args);
|
||||
ESP_LOGD(TAG, "Getting file info for %s", fullfilename);
|
||||
|
||||
if (!fullfilename) {
|
||||
ESP_LOGE(TAG, "Failed to construct full file path");
|
||||
return false;
|
||||
}
|
||||
bool result = false;
|
||||
// Use stat to fill the fileInfo structure
|
||||
if (stat(fullfilename, &fileInfo) != 0) {
|
||||
ESP_LOGD(TAG, "File %s not found", fullfilename);
|
||||
} else {
|
||||
result = true;
|
||||
if (pfileInfo) {
|
||||
memcpy(pfileInfo, &fileInfo, sizeof(fileInfo));
|
||||
}
|
||||
ESP_LOGD(TAG, "File %s has %lu bytes", fullfilename, fileInfo.st_size);
|
||||
}
|
||||
|
||||
free(fullfilename);
|
||||
return result;
|
||||
}
|
||||
#define LOCAL_MAC_SIZE 10
|
||||
const char* get_mac_str() {
|
||||
uint8_t mac[6];
|
||||
@@ -644,111 +344,89 @@ const char* get_mac_str() {
|
||||
return macStr;
|
||||
}
|
||||
char* alloc_get_string_with_mac(const char* val) {
|
||||
uint8_t mac[6];
|
||||
char macStr[LOCAL_MAC_SIZE + 1];
|
||||
char* fullvalue = NULL;
|
||||
esp_read_mac((uint8_t*)&mac, ESP_MAC_WIFI_STA);
|
||||
snprintf(macStr, LOCAL_MAC_SIZE - 1, "-%x%x%x", mac[3], mac[4], mac[5]);
|
||||
fullvalue = (char*)malloc_init_external(strlen(val) + sizeof(macStr) + 1);
|
||||
const char* macstr = get_mac_str();
|
||||
char* fullvalue = (char*)malloc_init_external(strlen(val) + sizeof(macstr) + 1);
|
||||
if (fullvalue) {
|
||||
strcpy(fullvalue, val);
|
||||
strcat(fullvalue, macStr);
|
||||
strcat(fullvalue, macstr);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "malloc failed for value %s", val);
|
||||
}
|
||||
return fullvalue;
|
||||
}
|
||||
void listFiles(const char *path_requested) {
|
||||
DIR *dir = NULL;
|
||||
char * sep="---------------------------------------------------------\n";
|
||||
struct dirent *ent;
|
||||
char type;
|
||||
char size[21];
|
||||
char tpath[255];
|
||||
struct stat sb;
|
||||
struct tm *tm_info;
|
||||
char *lpath = NULL;
|
||||
int statok;
|
||||
char * path= alloc_join_path(spiffs_conf->base_path,path_requested);
|
||||
|
||||
printf("\nList of Directory [%s]\n", path);
|
||||
printf(sep);
|
||||
// Open directory
|
||||
dir = opendir(path);
|
||||
if (!dir) {
|
||||
printf("Error opening directory\n");
|
||||
free(path);
|
||||
return;
|
||||
char* alloc_get_fallback_unique_name() {
|
||||
#ifdef CONFIG_LWIP_LOCAL_HOSTNAME
|
||||
return alloc_get_string_with_mac(CONFIG_LWIP_LOCAL_HOSTNAME "-");
|
||||
#elif defined(CONFIG_FW_PLATFORM_NAME)
|
||||
return alloc_get_string_with_mac(CONFIG_FW_PLATFORM_NAME "-");
|
||||
#else
|
||||
return alloc_get_string_with_mac("squeezelite-");
|
||||
#endif
|
||||
}
|
||||
|
||||
#define LOCAL_MAC_SIZE 20
|
||||
char* alloc_get_formatted_mac_string(uint8_t mac[6]) {
|
||||
char* macStr = malloc_init_external(LOCAL_MAC_SIZE);
|
||||
if (macStr) {
|
||||
snprintf(macStr, LOCAL_MAC_SIZE, MACSTR, MAC2STR(mac));
|
||||
}
|
||||
return macStr;
|
||||
}
|
||||
|
||||
const char* str_or_unknown(const char* str) { return (str ? str : unknown_string_placeholder); }
|
||||
const char* str_or_null(const char* str) { return (str ? str : null_string_placeholder); }
|
||||
|
||||
esp_log_level_t get_log_level_from_char(char* level) {
|
||||
if (!strcasecmp(level, "NONE")) {
|
||||
return ESP_LOG_NONE;
|
||||
}
|
||||
if (!strcasecmp(level, "ERROR")) {
|
||||
return ESP_LOG_ERROR;
|
||||
}
|
||||
if (!strcasecmp(level, "WARN")) {
|
||||
return ESP_LOG_WARN;
|
||||
}
|
||||
if (!strcasecmp(level, "INFO")) {
|
||||
return ESP_LOG_INFO;
|
||||
}
|
||||
if (!strcasecmp(level, "DEBUG")) {
|
||||
return ESP_LOG_DEBUG;
|
||||
}
|
||||
if (!strcasecmp(level, "VERBOSE")) {
|
||||
return ESP_LOG_VERBOSE;
|
||||
}
|
||||
return ESP_LOG_WARN;
|
||||
}
|
||||
const char* get_log_level_desc(esp_log_level_t level) {
|
||||
switch (level) {
|
||||
case ESP_LOG_NONE:
|
||||
return "NONE";
|
||||
break;
|
||||
|
||||
case ESP_LOG_ERROR:
|
||||
return "ERROR";
|
||||
break;
|
||||
|
||||
case ESP_LOG_WARN:
|
||||
return "WARN";
|
||||
break;
|
||||
|
||||
case ESP_LOG_INFO:
|
||||
return "INFO";
|
||||
break;
|
||||
case ESP_LOG_DEBUG:
|
||||
return "DEBUG";
|
||||
break;
|
||||
case ESP_LOG_VERBOSE:
|
||||
return "VERBOSE";
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Read directory entries
|
||||
uint64_t total = 0;
|
||||
int nfiles = 0;
|
||||
printf("T Size Name\n");
|
||||
printf(sep);
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
sprintf(tpath, path);
|
||||
if (path[strlen(path)-1] != '/') strcat(tpath,"/");
|
||||
strcat(tpath,ent->d_name);
|
||||
|
||||
// Get file stat
|
||||
statok = stat(tpath, &sb);
|
||||
|
||||
if (ent->d_type == DT_REG) {
|
||||
type = 'f';
|
||||
nfiles++;
|
||||
if (statok) strcpy(size, " ?");
|
||||
else {
|
||||
total += sb.st_size;
|
||||
if (sb.st_size < (1024*1024)) sprintf(size,"%8d", (int)sb.st_size);
|
||||
else if ((sb.st_size/1024) < (1024*1024)) sprintf(size,"%6dKB", (int)(sb.st_size / 1024));
|
||||
else sprintf(size,"%6dMB", (int)(sb.st_size / (1024 * 1024)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
type = 'd';
|
||||
strcpy(size, " -");
|
||||
}
|
||||
|
||||
printf("%c %s %s\r\n",
|
||||
type,
|
||||
size,
|
||||
ent->d_name
|
||||
);
|
||||
|
||||
}
|
||||
if (total) {
|
||||
printf(sep);
|
||||
if (total < (1024*1024)) printf(" %8d", (int)total);
|
||||
else if ((total/1024) < (1024*1024)) printf(" %6dKB", (int)(total / 1024));
|
||||
else printf(" %6dMB", (int)(total / (1024 * 1024)));
|
||||
printf(" in %d file(s)\n", nfiles);
|
||||
}
|
||||
printf(sep);
|
||||
|
||||
closedir(dir);
|
||||
|
||||
free(lpath);
|
||||
free(path);
|
||||
uint32_t tot=0, used=0;
|
||||
esp_spiffs_info(NULL, &tot, &used);
|
||||
printf("SPIFFS: free %d KB of %d KB\n", (tot-used) / 1024, tot / 1024);
|
||||
printf(sep);
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
bool out_file_binding(pb_ostream_t* stream, const uint8_t* buf, size_t count) {
|
||||
FILE* file = (FILE*)stream->state;
|
||||
ESP_LOGD(TAG, "Writing %d bytes to file", count);
|
||||
return fwrite(buf, 1, count, file) == count;
|
||||
}
|
||||
bool in_file_binding(pb_istream_t* stream, pb_byte_t *buf, size_t count) {
|
||||
FILE* file = (FILE*)stream->state;
|
||||
ESP_LOGD(TAG, "Reading %d bytes from file", count);
|
||||
return fread(buf, 1, count, file) == count;
|
||||
}
|
||||
|
||||
bool out_http_binding(pb_ostream_t* stream, const uint8_t* buf, size_t count) {
|
||||
httpd_req_t* req = (httpd_req_t*)stream->state;
|
||||
ESP_LOGD(TAG, "Writing %d bytes to file", count);
|
||||
return httpd_resp_send_chunk(req, (const char*)buf, count) == ESP_OK;
|
||||
}
|
||||
void set_log_level(char* tag, char* level) { esp_log_level_set(tag, get_log_level_from_char(level)); }
|
||||
|
||||
@@ -1,125 +1,350 @@
|
||||
/*
|
||||
* Tools
|
||||
*
|
||||
* Philippe G. 2019, philippe_44@outlook.com
|
||||
* Sebastien L. 2023, sle118@hotmail.com
|
||||
* Philippe G. 2023, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* License Overview:
|
||||
* ----------------
|
||||
* The MIT License is a permissive open source license. As a user of this software, you are free to:
|
||||
* - Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software.
|
||||
* - Use the software for private, commercial, or any other purposes.
|
||||
*
|
||||
* Conditions:
|
||||
* - You must include the above copyright notice and this permission notice in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* The MIT License offers a high degree of freedom and is well-suited for both open source and
|
||||
* commercial applications. It places minimal restrictions on how the software can be used,
|
||||
* modified, and redistributed. For more details on the MIT License, please refer to the link above.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "esp_log.h"
|
||||
#include "cJSON.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_spiffs.h"
|
||||
#include "stdio.h"
|
||||
#include "sys/stat.h"
|
||||
#include "pb_common.h" // Nanopb header for encoding (serialization)
|
||||
#include "pb_decode.h" // Nanopb header for decoding (deserialization)
|
||||
#include "pb_encode.h" // Nanopb header for encoding (serialization)
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include "cpp_tools.h"
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def QUOTE
|
||||
* Converts the argument name into a string.
|
||||
*
|
||||
* @param name The name to be converted into a string.
|
||||
*/
|
||||
#ifndef QUOTE
|
||||
#define QUOTE(name) #name
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def STR
|
||||
* Converts a macro value into a string.
|
||||
*
|
||||
* @param macro The macro whose value is to be converted into a string.
|
||||
*/
|
||||
#ifndef STR
|
||||
#define STR(macro) QUOTE(macro)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def STR_OR_ALT
|
||||
* Returns the string 'str' or an alternative 'alt' if 'str' is NULL.
|
||||
*
|
||||
* @param str The string to check.
|
||||
* @param alt The alternative string to return if 'str' is NULL.
|
||||
*/
|
||||
#ifndef STR_OR_ALT
|
||||
#ifdef __cplusplus
|
||||
#define STR_OR_ALT(str, alt) (str!=nullptr ? str : alt)
|
||||
#else
|
||||
#define STR_OR_ALT(str, alt) (str ? str : alt)
|
||||
#endif
|
||||
|
||||
#ifndef STR_OR_BLANK
|
||||
#define STR_OR_BLANK(p) p == NULL ? "" : p
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def STR_OR_BLANK
|
||||
* Returns the string 'p' or an empty string if 'p' is NULL.
|
||||
*
|
||||
* @param p The string to check.
|
||||
*/
|
||||
#ifndef STR_OR_BLANK
|
||||
#ifdef __cplusplus
|
||||
#define STR_OR_BLANK(p) p == nullptr ? "" : p
|
||||
#else
|
||||
#define STR_OR_BLANK(p) p == NULL ? "" : p
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def ESP_LOG_DEBUG_EVENT
|
||||
* Logs a debug event with a given tag.
|
||||
*
|
||||
* @param tag The tag associated with the log message.
|
||||
* @param e The event to log.
|
||||
*/
|
||||
#define ESP_LOG_DEBUG_EVENT(tag, e) ESP_LOGD(tag, "evt: " e)
|
||||
|
||||
/**
|
||||
* @def FREE_AND_NULL
|
||||
* Frees a pointer if it's not NULL and sets it to NULL.
|
||||
*
|
||||
* @param x The pointer to free and nullify.
|
||||
*/
|
||||
#ifndef FREE_AND_NULL
|
||||
#define FREE_AND_NULL(x) \
|
||||
if (x) { \
|
||||
free(x); \
|
||||
x = NULL; \
|
||||
#define FREE_AND_NULL(x) \
|
||||
if (x) { \
|
||||
free(x); \
|
||||
x = NULL; \
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def CASE_TO_STR
|
||||
* A switch case that returns the string representation of a case constant.
|
||||
*
|
||||
* @param x The case constant.
|
||||
*/
|
||||
#ifndef CASE_TO_STR
|
||||
#define CASE_TO_STR(x) \
|
||||
case x: \
|
||||
return STR(x); \
|
||||
#define CASE_TO_STR(x) \
|
||||
case x: \
|
||||
return STR(x); \
|
||||
break;
|
||||
#endif
|
||||
|
||||
#define ENUM_TO_STRING(g) \
|
||||
case g: \
|
||||
return STR(g); \
|
||||
/**
|
||||
* @def ENUM_TO_STRING
|
||||
* A switch case for an enum that returns the string representation of the enum value.
|
||||
*
|
||||
* @param g The enum value.
|
||||
*/
|
||||
#define ENUM_TO_STRING(g) \
|
||||
case g: \
|
||||
return STR(g); \
|
||||
break;
|
||||
|
||||
/**
|
||||
* @fn void utf8_decode(char* src)
|
||||
* Decodes a string encoded in UTF-8 to CP1252 in place.
|
||||
* The result is stored in the same buffer as the input.
|
||||
*
|
||||
* @param src A pointer to the null-terminated string to be decoded.
|
||||
*/
|
||||
void utf8_decode(char* src);
|
||||
void url_decode(char* url);
|
||||
|
||||
/**
|
||||
* @fn void* malloc_init_external(size_t sz)
|
||||
* Allocates memory in PSRAM and initializes it to zero.
|
||||
*
|
||||
* This function attempts to allocate a block of memory of size 'sz' in PSRAM.
|
||||
* If allocation is successful, the memory is initialized to zero.
|
||||
*
|
||||
* @param sz The size of memory to allocate in bytes.
|
||||
* @return A pointer to the allocated memory block or NULL if allocation fails.
|
||||
*/
|
||||
void* malloc_init_external(size_t sz);
|
||||
|
||||
/**
|
||||
* @fn void* clone_obj_psram(void* source, size_t source_sz)
|
||||
* Clones an object into PSRAM.
|
||||
*
|
||||
* This function allocates memory in PSRAM and copies the contents of the
|
||||
* source object into the newly allocated memory. The size of the source
|
||||
* object is specified by 'source_sz'.
|
||||
*
|
||||
* @param source A pointer to the source object to clone.
|
||||
* @param source_sz The size of the source object in bytes.
|
||||
* @return A pointer to the cloned object in PSRAM or NULL if allocation fails.
|
||||
*/
|
||||
void* clone_obj_psram(void* source, size_t source_sz);
|
||||
|
||||
/**
|
||||
* @fn char* strdup_psram(const char* source)
|
||||
* Duplicates a string into PSRAM.
|
||||
*
|
||||
* This function allocates enough memory in PSRAM to hold a copy of 'source',
|
||||
* including the null terminator, and then copies the string into the new memory.
|
||||
*
|
||||
* @param source A pointer to the null-terminated string to duplicate.
|
||||
* @return A pointer to the duplicated string in PSRAM or NULL if allocation fails.
|
||||
*/
|
||||
char* strdup_psram(const char* source);
|
||||
/**
|
||||
* @fn const char* get_mem_flag_desc(int flags)
|
||||
* Returns a string describing memory allocation flags.
|
||||
*
|
||||
* This function takes memory allocation flags as an argument and returns
|
||||
* a string representation of these flags. The description includes various
|
||||
* memory capabilities like EXEC, DMA, SPIRAM, etc.
|
||||
*
|
||||
* @param flags The memory allocation flags to describe.
|
||||
* @return A string representation of the memory allocation flags.
|
||||
*/
|
||||
const char* get_mem_flag_desc(int flags);
|
||||
|
||||
/**
|
||||
* @fn const char* get_mac_str()
|
||||
* Returns a string representation of the device's MAC address.
|
||||
*
|
||||
* This function reads the MAC address of the device and returns a string
|
||||
* representation of the last three bytes of the MAC address. It uses a static
|
||||
* buffer to store the MAC address string.
|
||||
*
|
||||
* @return A string representation of the device's MAC address.
|
||||
*/
|
||||
const char* get_mac_str();
|
||||
|
||||
/**
|
||||
* @fn char* alloc_get_string_with_mac(const char* val)
|
||||
* Allocates a new string which is a concatenation of the provided value and the device's MAC address.
|
||||
*
|
||||
* This function concatenates 'val' with the device's MAC address string and returns
|
||||
* a pointer to the newly allocated string.
|
||||
*
|
||||
* @param val The string to concatenate with the MAC address.
|
||||
* @return A pointer to the newly allocated string, or NULL if allocation fails.
|
||||
*/
|
||||
char* alloc_get_string_with_mac(const char* val);
|
||||
|
||||
/**
|
||||
* @fn char* alloc_get_fallback_unique_name()
|
||||
* Allocates a unique name for the device based on predefined configurations or fallbacks.
|
||||
*
|
||||
* This function creates a unique name by concatenating a base name defined by
|
||||
* configuration macros (CONFIG_LWIP_LOCAL_HOSTNAME, CONFIG_FW_PLATFORM_NAME)
|
||||
* or a fallback prefix "squeezelite-" with the device's MAC address.
|
||||
*
|
||||
* @return A pointer to the newly allocated unique name, or NULL if allocation fails.
|
||||
*/
|
||||
char* alloc_get_fallback_unique_name();
|
||||
|
||||
/**
|
||||
* @fn char* alloc_get_formatted_mac_string(uint8_t mac[6])
|
||||
* Allocates and returns a formatted MAC address string.
|
||||
*
|
||||
* This function formats a given MAC address into a human-readable string,
|
||||
* allocating the necessary memory dynamically.
|
||||
*
|
||||
* @param mac An array containing the 6-byte MAC address.
|
||||
* @return A pointer to the dynamically allocated MAC address string, or NULL if allocation fails.
|
||||
*/
|
||||
char* alloc_get_formatted_mac_string(uint8_t mac[6]);
|
||||
|
||||
/**
|
||||
* @fn const char* str_or_unknown(const char* str)
|
||||
* Returns the input string or a placeholder for unknown string.
|
||||
*
|
||||
* If the input string is not NULL, it returns the string. Otherwise,
|
||||
* it returns a predefined placeholder for unknown strings.
|
||||
*
|
||||
* @param str The input string.
|
||||
* @return The input string or a placeholder if the input is NULL.
|
||||
*/
|
||||
const char* str_or_unknown(const char* str);
|
||||
|
||||
/**
|
||||
* @fn const char* str_or_null(const char* str)
|
||||
* Returns the input string or a placeholder for null string.
|
||||
*
|
||||
* If the input string is not NULL, it returns the string. Otherwise,
|
||||
* it returns a predefined placeholder for null strings.
|
||||
*
|
||||
* @param str The input string.
|
||||
* @return The input string or a placeholder if the input is NULL.
|
||||
*/
|
||||
const char* str_or_null(const char* str);
|
||||
|
||||
/**
|
||||
* @fn esp_log_level_t get_log_level_from_char(char* level)
|
||||
* Converts a string representation of a log level to its corresponding `esp_log_level_t` enum value.
|
||||
*
|
||||
* The function supports log level strings like "NONE", "ERROR", "WARN", "INFO", "DEBUG", and "VERBOSE".
|
||||
*
|
||||
* @param level The log level as a string.
|
||||
* @return The corresponding `esp_log_level_t` value, or ESP_LOG_WARN if the string does not match any log level.
|
||||
*/
|
||||
esp_log_level_t get_log_level_from_char(char* level);
|
||||
|
||||
/**
|
||||
* @fn const char* get_log_level_desc(esp_log_level_t level)
|
||||
* Returns the string description of a given `esp_log_level_t` log level.
|
||||
*
|
||||
* @param level The log level as an `esp_log_level_t` enum.
|
||||
* @return The string description of the log level.
|
||||
*/
|
||||
const char* get_log_level_desc(esp_log_level_t level);
|
||||
|
||||
/**
|
||||
* @fn void set_log_level(char* tag, char* level)
|
||||
* Sets the log level for a specific tag.
|
||||
*
|
||||
* The log level is specified as a string and converted internally to the corresponding `esp_log_level_t` value.
|
||||
*
|
||||
* @param tag The tag for which to set the log level.
|
||||
* @param level The log level as a string.
|
||||
*/
|
||||
void set_log_level(char* tag, char* level);
|
||||
|
||||
/**
|
||||
* @fn BaseType_t xTaskCreateEXTRAM(TaskFunction_t pvTaskCode, const char* const pcName, configSTACK_DEPTH_TYPE usStackDepth, void* pvParameters,
|
||||
* UBaseType_t uxPriority, TaskHandle_t* pxCreatedTask) Creates a new task with its stack allocated in external RAM (PSRAM). Use these to dynamically
|
||||
* create tasks whose stack is on EXTRAM. Be aware that it requires configNUM_THREAD_LOCAL_STORAGE_POINTERS to bet set to 2 at least (index 0 is used
|
||||
* by pthread and this uses index 1, obviously
|
||||
*
|
||||
* @param pvTaskCode Pointer to the task entry function.
|
||||
* @param pcName Task name.
|
||||
* @param usStackDepth Stack depth in words.
|
||||
* @param pvParameters Pointer to the task's parameters.
|
||||
* @param uxPriority Task priority.
|
||||
* @param pxCreatedTask Pointer to a variable to store the task's handle.
|
||||
* @return pdPASS on success, pdFAIL on failure.
|
||||
*/
|
||||
BaseType_t xTaskCreateEXTRAM(TaskFunction_t pvTaskCode, const char* const pcName, configSTACK_DEPTH_TYPE usStackDepth, void* pvParameters,
|
||||
UBaseType_t uxPriority, TaskHandle_t* pxCreatedTask);
|
||||
|
||||
/**
|
||||
* @fn void vTaskDeleteEXTRAM(TaskHandle_t xTask)
|
||||
* Deletes a task and cleans up its allocated resources.
|
||||
* This function should be used in place of vTaskDelete for tasks created with xTaskCreateEXTRAM.
|
||||
*
|
||||
* @param xTask Handle to the task to be deleted.
|
||||
*/
|
||||
void vTaskDeleteEXTRAM(TaskHandle_t xTask);
|
||||
|
||||
/**
|
||||
* @fn void dump_json_content(const char* prefix, cJSON* json, int level)
|
||||
* Dumps the content of a cJSON object to the log.
|
||||
*
|
||||
* @param prefix Prefix for the log message.
|
||||
* @param json Pointer to the cJSON object to dump.
|
||||
* @param level Logging level for the output.
|
||||
*/
|
||||
void dump_json_content(const char* prefix, cJSON* json, int level);
|
||||
void init_spiffs();
|
||||
char * alloc_get_string_with_mac(const char * val);
|
||||
const char * get_mac_str();
|
||||
|
||||
#define alloc_join_path(base_path, ...) _alloc_join_path(base_path,__VA_ARGS__, NULL)
|
||||
char* _alloc_join_path(const char* base_path, ...);
|
||||
|
||||
#define get_file_info(pfileInfo, ...) _get_file_info(pfileInfo,__VA_ARGS__, NULL)
|
||||
|
||||
bool _get_file_info(struct stat* pfileInfo, ...);
|
||||
|
||||
#define load_file(sz, ...) _load_file(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, sz, __VA_ARGS__, NULL)
|
||||
#define load_file_dma(sz, ...) _load_file(MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA, sz, __VA_ARGS__, NULL)
|
||||
|
||||
void* _load_file(uint32_t memflags, size_t* sz, ...);
|
||||
|
||||
#define file_exists(pfileinfo,...) _file_exists(pfileInfo,__VA_ARGS__, NULL)
|
||||
bool _file_exists(struct stat *fileInfo, ...);
|
||||
|
||||
#define open_file(mode,...) _open_file(mode,__VA_ARGS__, NULL)
|
||||
FILE* _open_file( const char* mode,...);
|
||||
|
||||
bool in_file_binding(pb_istream_t* stream, pb_byte_t *buf, size_t count);
|
||||
bool out_file_binding(pb_ostream_t* stream, const uint8_t* buf, size_t count);
|
||||
bool out_http_binding(pb_ostream_t* stream, const uint8_t* buf, size_t count);
|
||||
|
||||
#define write_file(data,sz,...) _write_file(data,sz,__VA_ARGS__, NULL)
|
||||
bool _write_file(uint8_t* data, size_t sz, ...);
|
||||
|
||||
void listFiles(const char *path_requested);
|
||||
#ifndef gettime_ms
|
||||
// body is provided somewhere else...
|
||||
uint32_t _gettime_ms_(void);
|
||||
#define gettime_ms _gettime_ms_
|
||||
#endif
|
||||
|
||||
typedef void (*http_download_cb_t)(uint8_t* data, size_t len, void* context);
|
||||
void http_download(char* url, size_t max, http_download_cb_t callback, void* context);
|
||||
|
||||
/* Use these to dynamically create tasks whose stack is on EXTRAM. Be aware that it
|
||||
* requires configNUM_THREAD_LOCAL_STORAGE_POINTERS to bet set to 2 at least (index 0
|
||||
* is used by pthread and this uses index 1, obviously
|
||||
/**
|
||||
* @def TRACE_DEBUG
|
||||
* A macro for debug tracing.
|
||||
*
|
||||
* This macro prints a formatted debug message to the standard output.
|
||||
* The message includes the name of the function and the line number from which
|
||||
* the macro is called, followed by a custom formatted message.
|
||||
*
|
||||
* @param msgformat A printf-style format string for the custom message.
|
||||
* @param ... Variable arguments for the format string.
|
||||
*/
|
||||
BaseType_t xTaskCreateEXTRAM(TaskFunction_t pvTaskCode, const char* const pcName,
|
||||
configSTACK_DEPTH_TYPE usStackDepth, void* pvParameters, UBaseType_t uxPriority,
|
||||
TaskHandle_t* pxCreatedTask);
|
||||
void vTaskDeleteEXTRAM(TaskHandle_t xTask);
|
||||
|
||||
extern const char unknown_string_placeholder[];
|
||||
|
||||
#ifndef TRACE_DEBUG
|
||||
#define TRACE_DEBUG(msgformat, ... ) printf("%-30s%-5d" msgformat "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
#define TRACE_DEBUG(msgformat, ...) printf("%-30s%-5d" msgformat "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
148
components/tools/tools_http_utils.c
Normal file
148
components/tools/tools_http_utils.c
Normal file
@@ -0,0 +1,148 @@
|
||||
#include "tools_http_utils.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_http_server.h"
|
||||
#include "esp_log.h"
|
||||
#include "tools.h"
|
||||
#include "esp_tls.h"
|
||||
#include "pb_decode.h"
|
||||
#include "pb_encode.h"
|
||||
|
||||
static const char* TAG = "http_utils";
|
||||
|
||||
/****************************************************************************************
|
||||
* URL download
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
void* user_context;
|
||||
http_download_cb_t callback;
|
||||
size_t max, bytes;
|
||||
bool abort;
|
||||
uint8_t* data;
|
||||
esp_http_client_handle_t client;
|
||||
} http_context_t;
|
||||
|
||||
static void http_downloader(void* arg);
|
||||
static esp_err_t http_event_handler(esp_http_client_event_t* evt);
|
||||
|
||||
void http_download(char* url, size_t max, http_download_cb_t callback, void* context) {
|
||||
http_context_t* http_context =
|
||||
(http_context_t*)heap_caps_calloc(sizeof(http_context_t), 1, MALLOC_CAP_SPIRAM);
|
||||
|
||||
esp_http_client_config_t config = {
|
||||
.url = url,
|
||||
.event_handler = http_event_handler,
|
||||
.user_data = http_context,
|
||||
};
|
||||
|
||||
http_context->callback = callback;
|
||||
http_context->user_context = context;
|
||||
http_context->max = max;
|
||||
http_context->client = esp_http_client_init(&config);
|
||||
|
||||
xTaskCreateEXTRAM(
|
||||
http_downloader, "downloader", 8 * 1024, http_context, ESP_TASK_PRIO_MIN + 1, NULL);
|
||||
}
|
||||
|
||||
static void http_downloader(void* arg) {
|
||||
http_context_t* http_context = (http_context_t*)arg;
|
||||
|
||||
esp_http_client_perform(http_context->client);
|
||||
esp_http_client_cleanup(http_context->client);
|
||||
|
||||
free(http_context);
|
||||
vTaskDeleteEXTRAM(NULL);
|
||||
}
|
||||
|
||||
static esp_err_t http_event_handler(esp_http_client_event_t* evt) {
|
||||
http_context_t* http_context = (http_context_t*)evt->user_data;
|
||||
|
||||
if (http_context->abort) return ESP_FAIL;
|
||||
|
||||
switch (evt->event_id) {
|
||||
case HTTP_EVENT_ERROR:
|
||||
http_context->callback(NULL, 0, http_context->user_context);
|
||||
http_context->abort = true;
|
||||
break;
|
||||
case HTTP_EVENT_ON_HEADER:
|
||||
if (!strcasecmp(evt->header_key, "Content-Length")) {
|
||||
size_t len = atoi(evt->header_value);
|
||||
if (!len || len > http_context->max) {
|
||||
ESP_LOGI(TAG, "content-length null or too large %zu / %zu", len, http_context->max);
|
||||
http_context->abort = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case HTTP_EVENT_ON_DATA: {
|
||||
size_t len = esp_http_client_get_content_length(evt->client);
|
||||
if (!http_context->data) {
|
||||
if ((http_context->data = (uint8_t*)malloc(len)) == NULL) {
|
||||
http_context->abort = true;
|
||||
ESP_LOGE(TAG, "failed to allocate memory for output buffer %zu", len);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
memcpy(http_context->data + http_context->bytes, evt->data, evt->data_len);
|
||||
http_context->bytes += evt->data_len;
|
||||
break;
|
||||
}
|
||||
case HTTP_EVENT_ON_FINISH:
|
||||
http_context->callback(http_context->data, http_context->bytes, http_context->user_context);
|
||||
break;
|
||||
case HTTP_EVENT_DISCONNECTED: {
|
||||
int mbedtls_err = 0;
|
||||
esp_err_t err = esp_tls_get_and_clear_last_error(evt->data, &mbedtls_err, NULL);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "HTTP download disconnect %d", err);
|
||||
if (http_context->data) free(http_context->data);
|
||||
http_context->callback(NULL, 0, http_context->user_context);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
/****************************************************************************************
|
||||
* URL tools
|
||||
*/
|
||||
|
||||
static inline char from_hex(char ch) { return isdigit(ch) ? ch - '0' : tolower(ch) - 'a' + 10; }
|
||||
|
||||
void url_decode(char* url) {
|
||||
char *p, *src = strdup(url);
|
||||
for (p = src; *src; url++) {
|
||||
*url = *src++;
|
||||
if (*url == '%') {
|
||||
*url = from_hex(*src++) << 4;
|
||||
*url |= from_hex(*src++);
|
||||
} else if (*url == '+') {
|
||||
*url = ' ';
|
||||
}
|
||||
}
|
||||
*url = '\0';
|
||||
free(p);
|
||||
}
|
||||
|
||||
bool out_http_binding(pb_ostream_t* stream, const uint8_t* buf, size_t count) {
|
||||
httpd_req_t* req = (httpd_req_t*)stream->state;
|
||||
ESP_LOGD(TAG, "Writing %d bytes to file", count);
|
||||
return httpd_resp_send_chunk(req, (const char*)buf, count) == ESP_OK;
|
||||
}
|
||||
|
||||
bool in_http_binding(pb_istream_t* stream, pb_byte_t* buf, size_t count) {
|
||||
httpd_req_t* req = (httpd_req_t*)stream->state;
|
||||
ESP_LOGV(TAG, "Reading %d bytes from http stream", count);
|
||||
int received = httpd_req_recv(req, (char*)buf, count);
|
||||
if (received <= 0) {
|
||||
stream->errmsg = "Not all data received";
|
||||
return false;
|
||||
}
|
||||
return received==count;
|
||||
|
||||
}
|
||||
100
components/tools/tools_http_utils.h
Normal file
100
components/tools/tools_http_utils.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
*
|
||||
* Sebastien L. 2023, sle118@hotmail.com
|
||||
* Philippe G. 2023, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* License Overview:
|
||||
* ----------------
|
||||
* The MIT License is a permissive open source license. As a user of this software, you are free to:
|
||||
* - Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software.
|
||||
* - Use the software for private, commercial, or any other purposes.
|
||||
*
|
||||
* Conditions:
|
||||
* - You must include the above copyright notice and this permission notice in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* The MIT License offers a high degree of freedom and is well-suited for both open source and
|
||||
* commercial applications. It places minimal restrictions on how the software can be used,
|
||||
* modified, and redistributed. For more details on the MIT License, please refer to the link above.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "esp_system.h"
|
||||
#include "pb.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* @brief Type definition for a callback function used in HTTP download.
|
||||
*
|
||||
* @param data Pointer to the downloaded data buffer.
|
||||
* @param len Length of the data buffer.
|
||||
* @param context User-defined context passed to the callback.
|
||||
*/
|
||||
typedef void (*http_download_cb_t)(uint8_t* data, size_t len, void* context);
|
||||
|
||||
/**
|
||||
* @brief Downloads data from a specified URL.
|
||||
*
|
||||
* This function initializes an HTTP client and starts a download task.
|
||||
* It uses a callback mechanism to return the downloaded data.
|
||||
*
|
||||
* @param url The URL from which to download data.
|
||||
* @param max The maximum size of data to download.
|
||||
* @param callback The callback function to be called with the downloaded data.
|
||||
* @param context User-defined context to be passed to the callback function.
|
||||
*/
|
||||
void http_download(char* url, size_t max, http_download_cb_t callback, void* context);
|
||||
|
||||
/**
|
||||
* @brief Decodes a URL-encoded string.
|
||||
*
|
||||
* This function replaces percent-encoded characters in the URL with their ASCII representations.
|
||||
* Spaces encoded as '+' are also converted to space characters.
|
||||
*
|
||||
* @param url The URL-encoded string to be decoded in place.
|
||||
*/
|
||||
void url_decode(char* url);
|
||||
|
||||
/**
|
||||
* @brief Callback function for output streaming with HTTP binding.
|
||||
*
|
||||
* This function is designed to be used with NanoPB for streaming output data over HTTP.
|
||||
* It sends the given buffer over an HTTP connection.
|
||||
*
|
||||
* @param stream The output stream provided by NanoPB.
|
||||
* @param buf The buffer containing data to be sent.
|
||||
* @param count The number of bytes in the buffer to be sent.
|
||||
* @return Returns true on successful transmission, false otherwise.
|
||||
*/
|
||||
bool out_http_binding(pb_ostream_t* stream, const uint8_t* buf, size_t count);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Callback function for input streaming with HTTP binding.
|
||||
*
|
||||
* This function is designed to be used with NanoPB for streaming input data over HTTP.
|
||||
* It reads data into the given buffer from an HTTP connection.
|
||||
*
|
||||
* The function is typically used as a callback in a `pb_istream_t` structure,
|
||||
* allowing NanoPB to receive data in a streaming manner from an HTTP source.
|
||||
*
|
||||
* @param stream The input stream provided by NanoPB.
|
||||
* @param buf The buffer where data should be stored.
|
||||
* @param count The size of the buffer, indicating the maximum number of bytes to read.
|
||||
* @return Returns true on successful reception of data, false otherwise. When false
|
||||
* is returned, it indicates an error in data reception or end of stream.
|
||||
*/
|
||||
bool in_http_binding(pb_istream_t* stream, pb_byte_t* buf, size_t count);
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
383
components/tools/tools_spiffs_utils.cpp
Normal file
383
components/tools/tools_spiffs_utils.cpp
Normal file
@@ -0,0 +1,383 @@
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_INFO
|
||||
#include "tools_spiffs_utils.h"
|
||||
#include "esp_log.h"
|
||||
#include "tools.h"
|
||||
|
||||
#include "PBW.h"
|
||||
#include <ctype.h> // For isprint()
|
||||
#include <dirent.h>
|
||||
#include <fnmatch.h>
|
||||
#include <iomanip> // for std::setw
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h> // for file stat
|
||||
#include <vector>
|
||||
|
||||
static const char* TAG = "spiffs_utils";
|
||||
static bool initialized = false;
|
||||
static esp_vfs_spiffs_conf_t* spiffs_conf = NULL;
|
||||
const char* spiffs_base_path = "/spiffs";
|
||||
// Struct to represent a file entry
|
||||
|
||||
// list of reserved files that the system controls
|
||||
const std::vector<std::string> restrictedPaths = {
|
||||
"/spiffs/defaults/*", "/spiffs/fonts/*", "/spiffs/targets/*", "/spiffs/www/*"};
|
||||
|
||||
void init_spiffs() {
|
||||
if (initialized) {
|
||||
ESP_LOGV(TAG, "SPIFFS already initialized. returning");
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Initializing the SPI File system");
|
||||
spiffs_conf = (esp_vfs_spiffs_conf_t*)malloc(sizeof(esp_vfs_spiffs_conf_t));
|
||||
spiffs_conf->base_path = spiffs_base_path;
|
||||
spiffs_conf->partition_label = NULL;
|
||||
spiffs_conf->max_files = 5;
|
||||
spiffs_conf->format_if_mount_failed = true;
|
||||
|
||||
// Use settings defined above to initialize and mount SPIFFS filesystem.
|
||||
// Note: esp_vfs_spiffs_register is an all-in-one convenience function.
|
||||
esp_err_t ret = esp_vfs_spiffs_register(spiffs_conf);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Failed to mount or format filesystem");
|
||||
} else if (ret == ESP_ERR_NOT_FOUND) {
|
||||
ESP_LOGE(TAG, "Failed to find SPIFFS partition");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
size_t total = 0, used = 0;
|
||||
ret = esp_spiffs_info(spiffs_conf->partition_label, &total, &used);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to get SPIFFS partition information (%s). Formatting...",
|
||||
esp_err_to_name(ret));
|
||||
esp_spiffs_format(spiffs_conf->partition_label);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
bool write_file(const uint8_t* data, size_t sz, const char* filename) {
|
||||
bool result = true;
|
||||
FILE* file = NULL;
|
||||
init_spiffs();
|
||||
if (data == NULL) {
|
||||
ESP_LOGE(TAG, "Cannot write file. Data not received");
|
||||
return false;
|
||||
}
|
||||
if (sz == 0) {
|
||||
ESP_LOGE(TAG, "Cannot write file. Data length 0");
|
||||
return false;
|
||||
}
|
||||
file = fopen(filename, "wb");
|
||||
if (file == NULL) {
|
||||
ESP_LOGE(TAG, "Error opening %s for writing", filename);
|
||||
return false;
|
||||
}
|
||||
size_t written = fwrite(data, 1, sz, file);
|
||||
if (written != sz) {
|
||||
ESP_LOGE(TAG, "Write error. Wrote %d bytes of %d.", written, sz);
|
||||
result = false;
|
||||
}
|
||||
fclose(file);
|
||||
return result;
|
||||
}
|
||||
|
||||
void* load_file(uint32_t memflags, size_t* sz, const char* filename) {
|
||||
void* data = NULL;
|
||||
FILE* file = NULL;
|
||||
init_spiffs();
|
||||
size_t fsz = 0;
|
||||
file = fopen(filename, "rb");
|
||||
|
||||
if (file == NULL) {
|
||||
return data;
|
||||
}
|
||||
fseek(file, 0, SEEK_END);
|
||||
fsz = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
if (fsz > 0) {
|
||||
ESP_LOGD(TAG, "Allocating %d bytes to load file %s content with flags: %s ", fsz, filename,
|
||||
get_mem_flag_desc(memflags));
|
||||
data = (void*)heap_caps_calloc(1, fsz, memflags);
|
||||
if (data == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to allocate %d bytes to load file %s", fsz, filename);
|
||||
} else {
|
||||
fread(data, 1, fsz, file);
|
||||
if (sz) {
|
||||
*sz = fsz;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "File is empty. Nothing to read");
|
||||
}
|
||||
fclose(file);
|
||||
return data;
|
||||
}
|
||||
bool get_file_info(struct stat* pfileInfo, const char* filename) {
|
||||
// ensure that the spiffs is initialized
|
||||
struct stat fileInfo;
|
||||
init_spiffs();
|
||||
if (strlen(filename) == 0) {
|
||||
ESP_LOGE(TAG, "Invalid file name");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGD(TAG, "Getting file info for %s", filename);
|
||||
bool result = false;
|
||||
// Use stat to fill the fileInfo structure
|
||||
if (stat(filename, &fileInfo) != 0) {
|
||||
ESP_LOGD(TAG, "File %s not found", filename);
|
||||
} else {
|
||||
result = true;
|
||||
if (pfileInfo) {
|
||||
memcpy(pfileInfo, &fileInfo, sizeof(fileInfo));
|
||||
}
|
||||
ESP_LOGD(TAG, "File %s has %lu bytes", filename, fileInfo.st_size);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool is_restricted_path(const char* filename) {
|
||||
for (const auto& pattern : restrictedPaths) {
|
||||
if (fnmatch(pattern.c_str(), filename, 0) == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool erase_path(const char* filename, bool restricted) {
|
||||
std::string full_path_with_wildcard = std::string(filename);
|
||||
if (full_path_with_wildcard.empty()) {
|
||||
ESP_LOGE(TAG, "Error constructing full path");
|
||||
return false;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Erasing file(s) matching pattern %s", full_path_with_wildcard.c_str());
|
||||
|
||||
// Extract directory path and wildcard pattern
|
||||
size_t lastSlashPos = full_path_with_wildcard.find_last_of('/');
|
||||
std::string dirpath = full_path_with_wildcard.substr(0, lastSlashPos);
|
||||
std::string wildcard = full_path_with_wildcard.substr(lastSlashPos + 1);
|
||||
ESP_LOGD(TAG, "Last slash pos: %d, dirpath: %s, wildcard %s ", lastSlashPos, dirpath.c_str(),
|
||||
wildcard.c_str());
|
||||
DIR* dir = opendir(dirpath.empty() ? "." : dirpath.c_str());
|
||||
if (!dir) {
|
||||
ESP_LOGE(TAG, "Error opening directory");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
struct dirent* ent;
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
if (fnmatch(wildcard.c_str(), ent->d_name, 0) == 0) {
|
||||
std::string fullfilename = dirpath + "/" + ent->d_name;
|
||||
// Check if the file is restricted
|
||||
if (restricted && is_restricted_path(fullfilename.c_str())) {
|
||||
ESP_LOGW(TAG, "Skipping restricted file %s", fullfilename.c_str());
|
||||
continue;
|
||||
}
|
||||
ESP_LOGW(TAG, "Deleting file %s", fullfilename.c_str());
|
||||
if (remove(fullfilename.c_str()) != 0) {
|
||||
ESP_LOGE(TAG, "Error deleting file %s", fullfilename.c_str());
|
||||
} else {
|
||||
result = true;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGV(
|
||||
TAG, "%s does not match file pattern to delete: %s", ent->d_name, wildcard.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Function to format and print a file entry
|
||||
void printFileEntry(const tools_file_entry_t& entry) {
|
||||
const char* suffix;
|
||||
double size;
|
||||
|
||||
// Format the size
|
||||
if (entry.type == 'F') {
|
||||
if (entry.size < 1024) { // less than 1 KB
|
||||
size = entry.size;
|
||||
suffix = " B";
|
||||
printf("%c %10.0f%s %-80s%4s\n", entry.type, size, suffix, entry.name.c_str(),
|
||||
entry.restricted ? "X" : "-");
|
||||
} else {
|
||||
if (entry.size < 1024 * 1024) { // 1 KB to <1 MB
|
||||
size = entry.size / 1024;
|
||||
suffix = " KB";
|
||||
} else { // 1 MB and above
|
||||
size = entry.size / (1024 * 1024);
|
||||
suffix = " MB";
|
||||
}
|
||||
printf("%c %10.0f%s %-80s%4s\n", entry.type, size, suffix, entry.name.c_str(),
|
||||
entry.restricted ? "X" : "-");
|
||||
}
|
||||
|
||||
} else {
|
||||
printf("%c - %-80s%4s\n", entry.type, entry.name.c_str(),
|
||||
entry.restricted ? "X" : "-");
|
||||
}
|
||||
}
|
||||
|
||||
void listFiles(const char* path_requested_char) {
|
||||
// Ensure that the SPIFFS is initialized
|
||||
init_spiffs();
|
||||
auto path_requested = std::string(path_requested_char);
|
||||
auto filesList = get_files_list(path_requested);
|
||||
printf("---------------------------------------------------------------------------------------"
|
||||
"---------------\n");
|
||||
printf("T SIZE NAME "
|
||||
" RSTR\n");
|
||||
printf("---------------------------------------------------------------------------------------"
|
||||
"---------------\n");
|
||||
|
||||
uint64_t total = 0;
|
||||
int nfiles = 0;
|
||||
for (auto& e : filesList) {
|
||||
if (e.type == 'F') {
|
||||
total += e.size;
|
||||
nfiles++;
|
||||
}
|
||||
printFileEntry(e);
|
||||
}
|
||||
printf("---------------------------------------------------------------------------------------"
|
||||
"---------------\n");
|
||||
if (total > 0) {
|
||||
printf("Total : %lu bytes in %d file(s)\n", (unsigned long)total, nfiles);
|
||||
}
|
||||
|
||||
uint32_t tot = 0, used = 0;
|
||||
esp_spiffs_info(NULL, &tot, &used);
|
||||
printf("SPIFFS: free %d KB of %d KB\n", (tot - used) / 1024, tot / 1024);
|
||||
printf("---------------------------------------------------------------------------------------"
|
||||
"---------------\n");
|
||||
}
|
||||
|
||||
bool out_file_binding(pb_ostream_t* stream, const uint8_t* buf, size_t count) {
|
||||
FILE* file = (FILE*)stream->state;
|
||||
ESP_LOGV(TAG, "Writing %d bytes to file", count);
|
||||
return fwrite(buf, 1, count, file) == count;
|
||||
}
|
||||
bool in_file_binding(pb_istream_t* stream, pb_byte_t* buf, size_t count) {
|
||||
FILE* file = (FILE*)stream->state;
|
||||
ESP_LOGV(TAG, "Reading %d bytes from file", count);
|
||||
return fread(buf, 1, count, file) == count;
|
||||
}
|
||||
|
||||
// Function to list files matching the path_requested and return a std::list of FileEntry
|
||||
std::list<tools_file_entry_t> get_files_list(const std::string& path_requested) {
|
||||
std::list<tools_file_entry_t> fileList;
|
||||
std::set<std::string> directoryNames;
|
||||
|
||||
struct dirent* ent;
|
||||
struct stat sb;
|
||||
|
||||
// Ensure that the SPIFFS is initialized
|
||||
init_spiffs();
|
||||
|
||||
std::string prefix = (path_requested.back() != '/' ? path_requested + "/" : path_requested);
|
||||
DIR* dir = opendir(path_requested.c_str());
|
||||
if (!dir) {
|
||||
ESP_LOGE(TAG, "Error opening directory %s ", path_requested.c_str());
|
||||
return fileList;
|
||||
}
|
||||
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
tools_file_entry_t fileEntry;
|
||||
fileEntry.name = prefix + ent->d_name;
|
||||
fileEntry.type = (ent->d_type == DT_REG) ? 'F' : 'D';
|
||||
fileEntry.restricted = is_restricted_path(fileEntry.name.c_str());
|
||||
if (stat(fileEntry.name.c_str(), &sb) == -1) {
|
||||
ESP_LOGE(TAG, "Ignoring file %s ", fileEntry.name.c_str());
|
||||
continue;
|
||||
}
|
||||
fileEntry.size = sb.st_size;
|
||||
fileList.push_back(fileEntry);
|
||||
|
||||
// Extract all parent directory names
|
||||
size_t pos = 0;
|
||||
while ((pos = fileEntry.name.find('/', pos + 1)) != std::string::npos) {
|
||||
directoryNames.insert(fileEntry.name.substr(0, pos));
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
// Add directories to the file list
|
||||
for (const auto& dirName : directoryNames) {
|
||||
tools_file_entry_t dirEntry;
|
||||
dirEntry.name = dirName;
|
||||
dirEntry.type = 'D'; // Mark as directory
|
||||
fileList.push_back(dirEntry);
|
||||
}
|
||||
|
||||
// Sort the list by directory/file name
|
||||
fileList.sort(
|
||||
[](const tools_file_entry_t& a, const tools_file_entry_t& b) { return a.name < b.name; });
|
||||
|
||||
// Remove duplicates
|
||||
fileList.unique(
|
||||
[](const tools_file_entry_t& a, const tools_file_entry_t& b) { return a.name == b.name; });
|
||||
|
||||
return fileList;
|
||||
}
|
||||
bool cat_file(const char* filename) {
|
||||
size_t sz;
|
||||
uint8_t* content = (uint8_t*)load_file_psram(&sz, filename);
|
||||
|
||||
if (content == NULL) {
|
||||
printf("Failed to load file\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sz; i++) {
|
||||
if (isprint(content[i])) {
|
||||
printf("%c", content[i]); // Print as a character
|
||||
} else {
|
||||
printf("\\x%02x", content[i]); // Print as hexadecimal
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n"); // New line after printing the content
|
||||
free(content);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dump_data(const uint8_t* pData, size_t length) {
|
||||
if (pData == NULL && length == 0) {
|
||||
printf("%s/%s\n", pData == nullptr ? "Invalid Data" : "Data OK",
|
||||
length == 0 ? "Invalid Length" : "Length OK");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
if (isprint((char)pData[i])) {
|
||||
printf("%c", (char)pData[i]); // Print as a character
|
||||
} else {
|
||||
printf("\\x%02x", (char)pData[i]); // Print as hexadecimal
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n"); // New line after printing the content
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dump_structure(const pb_msgdesc_t* fields, const void* src_struct) {
|
||||
try {
|
||||
std::vector<pb_byte_t> encodedData = System::PBHelper::EncodeData(fields, src_struct);
|
||||
return dump_data(encodedData.data(), encodedData.size());
|
||||
} catch (const std::runtime_error& e) {
|
||||
ESP_LOGE(TAG, "Error in dump_structure: %s", e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
249
components/tools/tools_spiffs_utils.h
Normal file
249
components/tools/tools_spiffs_utils.h
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
*
|
||||
* Sebastien L. 2023, sle118@hotmail.com
|
||||
* Philippe G. 2023, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* License Overview:
|
||||
* ----------------
|
||||
* The MIT License is a permissive open source license. As a user of this software, you are free to:
|
||||
* - Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software.
|
||||
* - Use the software for private, commercial, or any other purposes.
|
||||
*
|
||||
* Conditions:
|
||||
* - You must include the above copyright notice and this permission notice in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* The MIT License offers a high degree of freedom and is well-suited for both open source and
|
||||
* commercial applications. It places minimal restrictions on how the software can be used,
|
||||
* modified, and redistributed. For more details on the MIT License, please refer to the link above.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "esp_spiffs.h"
|
||||
#include "esp_system.h"
|
||||
#include "pb.h" // Nanopb header for encoding (serialization)
|
||||
#include "pb_decode.h" // Nanopb header for decoding (deserialization)
|
||||
#include "pb_encode.h" // Nanopb header for encoding (serialization)
|
||||
#include "sys/stat.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <cstdarg> // for va_list, va_start, va_end
|
||||
#include <list>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
/**
|
||||
* @brief Represents a file entry.
|
||||
*/
|
||||
typedef struct {
|
||||
char type; /**< File type ('F' for regular file, 'D' for directory). */
|
||||
long size; /**< File size in bytes. */
|
||||
std::string name; /**< File or directory name. */
|
||||
bool restricted; /**< Restricted (system controled)*/
|
||||
} tools_file_entry_t;
|
||||
|
||||
extern const std::vector<std::string> restrictedPaths;
|
||||
|
||||
/**
|
||||
* @brief Retrieve a list of file entries in the specified directory.
|
||||
*
|
||||
* This function collects information about files and directories in the specified
|
||||
* directory path. The caller is responsible for crafting the complete path
|
||||
* (including any necessary SPIFFS base path).
|
||||
*
|
||||
* @param path_requested The directory path for which to list files and directories.
|
||||
* @return A std::list of tools_file_entry_t representing the files and directories in the specified
|
||||
* path.
|
||||
*
|
||||
* @note The caller is responsible for adding the SPIFFS base path if needed.
|
||||
*
|
||||
* Example usage:
|
||||
* @code
|
||||
* std::string base_path = "/spiffs";
|
||||
* std::string directory = "/some_directory";
|
||||
* std::string full_path = base_path + directory;
|
||||
* std::list<tools_file_entry_t> fileList = get_files_list(full_path);
|
||||
* for (const auto& entry : fileList) {
|
||||
* // Access entry.type, entry.size, and entry.name
|
||||
* }
|
||||
* @endcode
|
||||
*/
|
||||
|
||||
std::list<tools_file_entry_t> get_files_list(const std::string& path_requested);
|
||||
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void init_spiffs();
|
||||
extern const char* spiffs_base_path;
|
||||
|
||||
/**
|
||||
* @brief Retrieves information about a file.
|
||||
*
|
||||
* This function uses the stat system call to fill a struct stat with information about the file.
|
||||
*
|
||||
* @param pfileInfo Pointer to a struct stat where file information will be stored.
|
||||
* @param filename The file path to get info for
|
||||
* @return bool True if the file information was successfully retrieved, false otherwise.
|
||||
*/
|
||||
bool get_file_info(struct stat* pfileInfo, const char* filename);
|
||||
|
||||
/**
|
||||
* @brief Loads the entire content of a file into memory.
|
||||
*
|
||||
* This function opens a file in binary read mode and loads its entire
|
||||
* content into a memory buffer. The memory for the buffer is allocated based
|
||||
* on the specified memory flags. The size of the loaded data is stored in the
|
||||
* variable pointed to by 'sz'.
|
||||
*
|
||||
* @param memflags Flags indicating the type of memory to allocate for the buffer.
|
||||
* This can be a combination of memory capabilities like MALLOC_CAP_SPIRAM,
|
||||
* MALLOC_CAP_8BIT, MALLOC_CAP_INTERNAL, MALLOC_CAP_DMA, etc.
|
||||
* @param sz Pointer to a size_t variable where the size of the loaded data will be stored.
|
||||
* Optional if loaded size is needed.
|
||||
* @param filename The path of the file to load. The file should exist and be readable.
|
||||
* @return A pointer to the allocated memory containing the file data. Returns NULL if the
|
||||
* file cannot be opened, if memory allocation fails, or if the file is empty.
|
||||
*/
|
||||
void* load_file(uint32_t memflags, size_t* sz, const char* filename);
|
||||
|
||||
/**
|
||||
* @brief Macro to load a file into PSRAM (Pseudo-Static Random Access Memory).
|
||||
*
|
||||
* This macro is a convenience wrapper for 'load_file' to load file data into PSRAM.
|
||||
* It is suitable for larger data that does not fit into the internal memory.
|
||||
*
|
||||
* @param pSz Pointer to a size_t variable to store the size of the loaded data.
|
||||
* @param filename The path of the file to load.
|
||||
* @return A pointer to the allocated memory in PSRAM containing the file data, or NULL on failure.
|
||||
*/
|
||||
#define load_file_psram(pSz, filename) load_file(MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, pSz, filename)
|
||||
|
||||
/**
|
||||
* @brief Macro to load a file into DMA-capable internal memory.
|
||||
*
|
||||
* This macro is a convenience wrapper for 'load_file' to load file data into
|
||||
* DMA-capable internal memory. It is suitable for smaller data that needs to be
|
||||
* accessed by DMA controllers.
|
||||
*
|
||||
* @param pSz Pointer to a size_t variable to store the size of the loaded data.
|
||||
* @param filename The path of the file to load.
|
||||
* @return A pointer to the allocated memory in internal DMA-capable memory, or NULL on failure.
|
||||
*/
|
||||
#define load_file_dma(pSz, filename) load_file(MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA, pSz, filename)
|
||||
|
||||
/**
|
||||
* @brief Erases files matching a specified pattern within a path.
|
||||
*
|
||||
* This function deletes files that match the given wildcard pattern in the specified path.
|
||||
* If the 'restricted' flag is set to true, the function will not delete files that match
|
||||
* any of the paths specified in the restricted paths list.
|
||||
*
|
||||
* @param path A string representing the path and wildcard pattern of files to be deleted.
|
||||
* Example: "folder/test*.txt" will attempt to delete all test*.txt files in the
|
||||
* 'folder' directory.
|
||||
* @param restricted A boolean flag indicating whether to apply restrictions on file deletion.
|
||||
* If true, files matching the restricted paths will not be deleted.
|
||||
* @return Returns true if all files matching the pattern are successfully deleted or skipped (in
|
||||
* case of restrictions). Returns false if there is an error in deleting any of the files or if the
|
||||
* directory cannot be opened.
|
||||
*/
|
||||
bool erase_path(const char* path, bool restricted);
|
||||
|
||||
/**
|
||||
* @brief Checks if a given filename matches any of the restricted paths.
|
||||
*
|
||||
* This function determines whether the provided filename matches any pattern specified
|
||||
* in the list of restricted paths. It is typically used to prevent certain files or directories
|
||||
* from being modified or deleted.
|
||||
*
|
||||
* @param filename The name of the file to check against the restricted paths.
|
||||
* @return Returns true if the filename matches any of the restricted paths.
|
||||
* Returns false otherwise.
|
||||
*/
|
||||
bool is_restricted_path(const char* filename);
|
||||
|
||||
bool in_file_binding(pb_istream_t* stream, pb_byte_t* buf, size_t count);
|
||||
bool out_file_binding(pb_ostream_t* stream, const uint8_t* buf, size_t count);
|
||||
|
||||
/**
|
||||
* @brief Writes binary data to a file.
|
||||
*
|
||||
* This function writes a given array of bytes (data) to a file. The file path
|
||||
* is constructed from a variable number of string arguments.
|
||||
*
|
||||
* @param data Pointer to the data array to be written.
|
||||
* @param sz Size of the data array in bytes.
|
||||
* @param filename The file path to write to
|
||||
* @return bool True if the file is written successfully, false otherwise.
|
||||
*
|
||||
* @note This function initializes the SPIFFS before writing and logs errors.
|
||||
*/
|
||||
|
||||
bool write_file(const uint8_t* data, size_t sz, const char* filename);
|
||||
|
||||
void listFiles(const char* path_requested);
|
||||
|
||||
/**
|
||||
* @brief Prints the content of a specified file.
|
||||
*
|
||||
* This function reads the content of the file specified by `filename` and prints it to the standard
|
||||
* output. Each byte of the file is checked to determine if it is a printable ASCII character. If a
|
||||
* byte is printable, it is printed as a character; otherwise, it is printed in its hexadecimal
|
||||
* representation.
|
||||
*
|
||||
* The function utilizes `load_file_psram` to load the file into memory. It is assumed that
|
||||
* `load_file_psram` handles the opening and closing of the file, as well as memory allocation and
|
||||
* error handling.
|
||||
*
|
||||
* @param filename The path of the file to be printed. It should be a null-terminated string.
|
||||
*
|
||||
* @return Returns `true` if the file is successfully read and printed. Returns `false` if the file
|
||||
* cannot be opened, read, or if any other error occurs during processing.
|
||||
*
|
||||
* @note The function prints a hexadecimal representation (prefixed with \x) for non-printable
|
||||
* characters. For example, a byte with value 0x1F is printed as \x1F.
|
||||
*
|
||||
* @warning The function assumes that `load_file_psram` returns a `NULL` pointer if the file cannot
|
||||
* be loaded or if any error occurs. Ensure that `load_file_psram` adheres to this behavior.
|
||||
*/
|
||||
bool cat_file(const char* filename);
|
||||
|
||||
/**
|
||||
* @brief Dumps the given data to standard output.
|
||||
*
|
||||
* This function prints the provided data array to the standard output.
|
||||
* If the data is printable (as per the `isprint` standard function), it is printed
|
||||
* as a character. Otherwise, it is printed in hexadecimal format. The function
|
||||
* also checks for null data or zero length and reports it before returning false.
|
||||
*
|
||||
* @param pData Pointer to the data array to be dumped.
|
||||
* @param length The length of the data array.
|
||||
*
|
||||
* @return Returns `true` if the data is valid (not null and non-zero length), `false` otherwise.
|
||||
*
|
||||
* @note The function prints a newline character after dumping the entire data array.
|
||||
*/
|
||||
bool dump_data(const uint8_t* pData, size_t length);
|
||||
|
||||
/**
|
||||
* @brief Encodes a protobuf structure and dumps the encoded data to the console.
|
||||
*
|
||||
* This method takes a protobuf message structure, encodes it using NanoPB, and then dumps
|
||||
* the encoded data to the console. It is useful for debugging purposes to visualize
|
||||
* the encoded protobuf data.
|
||||
*
|
||||
* @param fields Pointer to the field descriptions array (generated by NanoPB).
|
||||
* @param src_struct Pointer to the structure instance to be encoded.
|
||||
*
|
||||
* @return Returns `true` if the data was successfully encoded and dumped, `false` otherwise.
|
||||
*/
|
||||
bool dump_structure(const pb_msgdesc_t* fields, const void* src_struct);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -40,81 +40,4 @@ mem_usage_trace_for_thread_t* memtrace_get_thread_entry(TaskHandle_t task) {
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
void memtrace_add_thread_entry(TaskHandle_t task) {
|
||||
if(!task) {
|
||||
ESP_LOGE(TAG, "memtrace_get_thread_entry: task is NULL");
|
||||
return ;
|
||||
}
|
||||
mem_usage_trace_for_thread_t* it = memtrace_get_thread_entry(task);
|
||||
if (it) {
|
||||
ESP_LOGW(TAG, "memtrace_add_thread_entry: thread already in list");
|
||||
return;
|
||||
}
|
||||
it = (mem_usage_trace_for_thread_t*)malloc_init_external(sizeof(mem_usage_trace_for_thread_t));
|
||||
if (!it) {
|
||||
ESP_LOGE(TAG, "memtrace_add_thread_entry: malloc failed");
|
||||
return;
|
||||
}
|
||||
it->task = task;
|
||||
it->malloc_int_last = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
it->malloc_spiram_last = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
|
||||
it->malloc_dma_last = heap_caps_get_free_size(MALLOC_CAP_DMA);
|
||||
it->name = pcTaskGetName(task);
|
||||
SLIST_INSERT_HEAD(&s_memtrace, it, next);
|
||||
return;
|
||||
}
|
||||
// void memtrace_print_delta(){
|
||||
// TaskHandle_t task = xTaskGetCurrentTaskHandle();
|
||||
// mem_usage_trace_for_thread_t* it = memtrace_get_thread_entry(task);
|
||||
// if (!it) {
|
||||
// memtrace_add_thread_entry(task);
|
||||
// ESP_LOGW(TAG, "memtrace_print_delta: added new entry for task %s",STR_OR_ALT(pcTaskGetName(task ), "unknown"));
|
||||
// return;
|
||||
// }
|
||||
// size_t malloc_int_delta = heap_caps_get_free_size(MALLOC_CAP_INTERNAL) - it->malloc_int_last;
|
||||
// size_t malloc_spiram_delta = heap_caps_get_free_size(MALLOC_CAP_SPIRAM) - it->malloc_spiram_last;
|
||||
// size_t malloc_dma_delta = heap_caps_get_free_size(MALLOC_CAP_DMA) - it->malloc_dma_last;
|
||||
// ESP_LOGD(TAG, "Heap internal:%zu (min:%zu) external:%zu (min:%zu) dma:%zu (min:%zu)",
|
||||
// heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
|
||||
// heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
|
||||
// heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
|
||||
// heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
|
||||
// heap_caps_get_free_size(MALLOC_CAP_DMA),
|
||||
// heap_caps_get_minimum_free_size(MALLOC_CAP_DMA));
|
||||
// ESP_LOGW(TAG, "memtrace_print_delta: %s: malloc_int_delta=%d, malloc_spiram_delta=%d, malloc_dma_delta=%d",
|
||||
// STR_OR_ALT(it->name, "unknown"),
|
||||
// malloc_int_delta,
|
||||
// malloc_spiram_delta,
|
||||
// malloc_dma_delta);
|
||||
// it->malloc_int_last = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
// it->malloc_spiram_last = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
|
||||
// it->malloc_dma_last = heap_caps_get_free_size(MALLOC_CAP_DMA);
|
||||
|
||||
// }
|
||||
size_t malloc_int = 0;
|
||||
size_t malloc_spiram =0;
|
||||
size_t malloc_dma = 0;
|
||||
void memtrace_print_delta(const char * msg, const char * tag, const char * function){
|
||||
size_t malloc_int_delta = heap_caps_get_free_size(MALLOC_CAP_INTERNAL) - malloc_int;
|
||||
size_t malloc_spiram_delta = heap_caps_get_free_size(MALLOC_CAP_SPIRAM) - malloc_spiram;
|
||||
size_t malloc_dma_delta = heap_caps_get_free_size(MALLOC_CAP_DMA) - malloc_dma;
|
||||
ESP_LOGW(TAG, "Heap internal:%zu (min:%zu)(chg:%d)/external:%zu (min:%zu)(chg:%d)/dma:%zu (min:%zu)(chg:%d) : %s%s%s%s%s",
|
||||
heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
|
||||
heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
|
||||
malloc_int_delta,
|
||||
heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
|
||||
heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
|
||||
malloc_spiram_delta,
|
||||
heap_caps_get_free_size(MALLOC_CAP_DMA),
|
||||
heap_caps_get_minimum_free_size(MALLOC_CAP_DMA),
|
||||
malloc_dma_delta,
|
||||
STR_OR_BLANK(tag),
|
||||
tag?" ":"",
|
||||
STR_OR_BLANK(function),
|
||||
function?" ":"",
|
||||
STR_OR_BLANK(msg)
|
||||
);
|
||||
malloc_int = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||
malloc_spiram = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
|
||||
malloc_dma = heap_caps_get_free_size(MALLOC_CAP_DMA);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,47 @@
|
||||
/*
|
||||
* Squeezelite for esp32
|
||||
/*
|
||||
*
|
||||
* (c) Sebastien 2019
|
||||
* Philippe G. 2019, philippe_44@outlook.com
|
||||
* Sebastien L. 2023, sle118@hotmail.com
|
||||
* Philippe G. 2023, philippe_44@outlook.com
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*
|
||||
* License Overview:
|
||||
* ----------------
|
||||
* The MIT License is a permissive open source license. As a user of this software, you are free to:
|
||||
* - Use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of this software.
|
||||
* - Use the software for private, commercial, or any other purposes.
|
||||
*
|
||||
* Conditions:
|
||||
* - You must include the above copyright notice and this permission notice in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* The MIT License offers a high degree of freedom and is well-suited for both open source and
|
||||
* commercial applications. It places minimal restrictions on how the software can be used,
|
||||
* modified, and redistributed. For more details on the MIT License, please refer to the link above.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef ENABLE_MEMTRACE
|
||||
void memtrace_print_delta(const char * msg, const char * tag, const char * function);
|
||||
#define MEMTRACE_PRINT_DELTA() memtrace_print_delta(NULL,TAG,__FUNCTION__);
|
||||
#define MEMTRACE_PRINT_DELTA_MESSAGE(x) memtrace_print_delta(x,TAG,__FUNCTION__);
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#ifdef CONFIG_HEAP_TRACING
|
||||
#define TRACE_INIT \
|
||||
if (!is_recovery_running) { \
|
||||
ESP_ERROR_CHECK(heap_trace_init_tohost()); \
|
||||
}
|
||||
#define TRACE_START \
|
||||
if (!is_recovery_running) { \
|
||||
ESP_ERROR_CHECK(heap_trace_start(HEAP_TRACE_ALL)); \
|
||||
} \
|
||||
#define TRACE_STOP if (!is_recovery_running) { ESP_ERROR_CHECK(heap_trace_stop()); }
|
||||
#else
|
||||
#define MEMTRACE_PRINT_DELTA()
|
||||
#define MEMTRACE_PRINT_DELTA_MESSAGE(x) ESP_LOGD(TAG,"%s",x);
|
||||
#endif
|
||||
#define TRACE_START
|
||||
#define TRACE_STOP
|
||||
#define TRACE_INIT
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user