added SD card support

This commit is contained in:
Bogdan Pilyugin 2024-05-07 14:12:46 +02:00
parent 428bb65c7b
commit 4eaaff65fd
10 changed files with 261 additions and 73 deletions

View File

@ -25,6 +25,7 @@ idf_component_register(
"src/MQTT.c"
"src/CronTimers.c"
"src/SerialPort.c"
src/sdcard.c
src/FileBlockHandler.c
src/OTA.c
src/RestApiHandler.c
@ -45,6 +46,7 @@ idf_component_register(
REQUIRES nvs_flash
spiffs
fatfs
esp_http_server
mbedtls
lwip

51
Kconfig
View File

@ -260,6 +260,57 @@ menu "WebGUIApp"
endif
endmenu
menu "SDCARD settings"
config SDCARD_ENABLE
bool "Enabled SDCARD interface"
default n
help
Set enabled SDCARD
if SDCARD_ENABLE
config SDCARD_SPI_HOST
int "SPI Host Number"
range 0 2
default 2
help
Set the SPI host used.
config SDCARD_SPI_CS_GPIO
int "SDCARD SPI CS GPIO number"
range GPIO_RANGE_MIN GPIO_RANGE_MAX
default 0 if IDF_TARGET_ESP32
default 0 if IDF_TARGET_ESP32S3
help
Set the GPIO number used by SPI SCLK.
config SDCARD_SPI_SCLK_GPIO
int "SDCARD SPI SCLK GPIO number"
range GPIO_RANGE_MIN GPIO_RANGE_MAX
default 9 if IDF_TARGET_ESP32
default 9 if IDF_TARGET_ESP32S3
help
Set the GPIO number used by SPI SCLK.
config SDCARD_SPI_MOSI_GPIO
int "SDCARD SPI MOSI GPIO number"
range GPIO_RANGE_MIN GPIO_RANGE_MAX
default 34 if IDF_TARGET_ESP32
default 44 if IDF_TARGET_ESP32S3
help
Set the GPIO number used by SPI MOSI.
config SDCARD_SPI_MISO_GPIO
int "SDCARD SPI MISO GPIO number"
range GPIO_RANGE_MIN GPIO_RANGE_MAX
default 33 if IDF_TARGET_ESP32
default 43 if IDF_TARGET_ESP32S3
help
Set the GPIO number used by SPI MISO.
endif
endmenu
menu "WiFi settings"

View File

@ -45,8 +45,8 @@
/* Max length a file path can have on storage */
#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + CONFIG_SPIFFS_OBJ_NAME_LEN)
#define MAX_FILE_SIZE (1000*1024) // 200 KB
#define MAX_FILE_SIZE_STR "1MB"
#define MAX_FILE_SIZE (100*1000*1024) // 200 KB
#define MAX_FILE_SIZE_STR "100MB"
/* Scratch buffer size */
#define SCRATCH_BUFSIZE EXPECTED_MAX_DATA_SIZE
@ -109,6 +109,7 @@ esp_err_t upload_post_handler(httpd_req_t *req);
esp_err_t delete_post_handler(httpd_req_t *req);
esp_err_t ParseBlockDataObject(char *argres, blockdata_transaction_t *ft);
void FileBlockHandler(char *argres, int rw);
void FileBlockHandler(char *argres, int rw, const char* path);
void FileListHandler(char *argres, int rw, const char* path);
#endif /* COMPONENTS_WEB_GUI_APPLICATION_INCLUDE_HTTPSERVER_H_ */

View File

@ -111,6 +111,7 @@ typedef struct
}UART_DATA_SEND_STRUCT;
void InitSerialPort(void);
void InitSysSDCard();
esp_err_t TransmitSerialPort(char *data, int ln);
esp_err_t GetConfVar(char* name, char* val, rest_var_types *tp);

View File

@ -42,7 +42,6 @@
#define DELETE_ORERATION 2
#define WRITE_ORERATION 3
static const char *dirpath = "/data/";
static blockdata_transaction_t FileTransaction = {
.opertype = 0
@ -78,9 +77,9 @@ esp_err_t ParseBlockDataObject(char *argres, blockdata_transaction_t *ft)
if (result.elements == 1)
{
ft->parts = atoi((char*) result.pValue);
if (ft->parts < 0 || ft->parts > 500)
if (ft->parts < 0 || ft->parts > 20000)
{
snprintf(argres, VAR_MAX_VALUE_LENGTH, "\"ERROR:'parts' value not in [0...500]\"");
snprintf(argres, VAR_MAX_VALUE_LENGTH, "\"ERROR:'parts' value not in [0...20000]\"");
return ESP_ERR_INVALID_ARG;
}
}
@ -140,7 +139,7 @@ esp_err_t ParseBlockDataObject(char *argres, blockdata_transaction_t *ft)
}
void FileBlockHandler(char *argres, int rw)
void FileBlockHandler(char *argres, int rw, const char* path)
{
if (ParseBlockDataObject(argres, &FileTransaction) != ESP_OK)
@ -155,7 +154,7 @@ void FileBlockHandler(char *argres, int rw)
else if (FileTransaction.part == (FileTransaction.parts - 1))
FileTransaction.operphase = 2; //Last block of multipart data (close file)
strcpy(FileTransaction.filepath, dirpath);
strcpy(FileTransaction.filepath, path);
strcat(FileTransaction.filepath, FileTransaction.mem_object);
if (FileTransaction.operphase == 1 || FileTransaction.operphase == 3)
@ -297,3 +296,43 @@ void FileBlockHandler(char *argres, int rw)
}
}
void FileListHandler(char *argres, int rw, const char* path)
{
char entrypath[FILE_PATH_MAX];
char entrysize[16];
const char *entrytype = "file";
struct dirent *entry;
struct stat entry_stat;
DIR *dir = opendir(path);
const size_t dirpath_len = strlen(path);
strlcpy(entrypath, path, sizeof(entrypath));
if (!dir)
{
ESP_LOGE("FILE_API", "Failed to stat dir : %s", path);
snprintf(argres, VAR_MAX_VALUE_LENGTH, "\"ERROR:DIR_NOT_FOUND\"");
return;
}
struct jWriteControl jwc;
jwOpen(&jwc, argres, VAR_MAX_VALUE_LENGTH, JW_ARRAY, JW_COMPACT);
while ((entry = readdir(dir)) != NULL)
{
strlcpy(entrypath + dirpath_len, entry->d_name, sizeof(entrypath) - dirpath_len);
entrytype = (entry->d_type == DT_DIR ? "directory" : "file");
if (stat(entrypath, &entry_stat) == -1)
{
ESP_LOGE("FILE_API", "Failed to stat %s : %s", entrytype, entry->d_name);
continue;
}
jwArr_object(&jwc);
jwObj_raw(&jwc, "sel", "false");
jwObj_string(&jwc, "name", (char*) entry->d_name);
jwObj_int(&jwc, "size", entry_stat.st_size);
jwEnd(&jwc);
}
jwClose(&jwc);
}

View File

@ -460,7 +460,11 @@ esp_err_t start_file_server(void)
return ESP_ERR_NO_MEM;
}
strlcpy(server_data->base_path, "/", sizeof("/"));
#if CONFIG_SDCARD_ENABLE
strlcpy(server_data->base_path2, "/sdcard", sizeof("/sdcard"));
#else
strlcpy(server_data->base_path2, "/data", sizeof("/data"));
#endif
server = start_webserver();
return ESP_OK;
}

View File

@ -21,7 +21,6 @@
* \copyright Apache License, Version 2.0
*/
#include "SystemApplication.h"
#include <SysConfiguration.h>
#include <webguiapp.h>
@ -216,7 +215,6 @@ static void funct_wifiscanres(char *argres, int rw)
strcpy(argres, "\"SYS_ERROR_UNKNOWN\"");
}
#if CONFIG_WEBGUIAPP_GPRS_ENABLE
void funct_gsm_module(char *argres, int rw)
{
@ -255,7 +253,6 @@ void funct_gsm_rssi(char *argres, int rw)
}
#endif
#ifdef CONFIG_WEBGUIAPP_LORAWAN_ENABLE
void funct_lora_stat(char *argres, int rw)
{
@ -283,7 +280,6 @@ void funct_lora_appkey(char *argres, int rw)
}
#endif
static void funct_ota_state(char *argres, int rw)
{
snprintf(argres, VAR_MAX_VALUE_LENGTH, "\"%s\"", GetUpdateStatus());
@ -358,13 +354,11 @@ static void funct_serial_mode(char *argres, int rw)
}
static void funct_objsinfo(char *argres, int rw)
{
GetObjectsInfo(argres);
}
static void funct_exec(char *argres, int rw)
{
if (rw)
@ -373,54 +367,28 @@ static void funct_exec(char *argres, int rw)
snprintf(argres, VAR_MAX_VALUE_LENGTH, "\"EXECUTED\"");
}
static const char *dirpath = "/data/";
static void funct_file_list(char *argres, int rw)
{
char entrypath[FILE_PATH_MAX];
char entrysize[16];
const char *entrytype = "file";
struct dirent *entry;
struct stat entry_stat;
DIR *dir = opendir(dirpath);
const size_t dirpath_len = strlen(dirpath);
strlcpy(entrypath, dirpath, sizeof(entrypath));
if (!dir)
{
ESP_LOGE("FILE_API", "Failed to stat dir : %s", dirpath);
snprintf(argres, VAR_MAX_VALUE_LENGTH, "\"ERROR:DIR_NOT_FOUND\"");
return;
}
struct jWriteControl jwc;
jwOpen(&jwc, argres, VAR_MAX_VALUE_LENGTH, JW_ARRAY, JW_COMPACT);
while ((entry = readdir(dir)) != NULL)
{
strlcpy(entrypath + dirpath_len, entry->d_name, sizeof(entrypath) - dirpath_len);
entrytype = (entry->d_type == DT_DIR ? "directory" : "file");
if (stat(entrypath, &entry_stat) == -1)
{
ESP_LOGE("FILE_API", "Failed to stat %s : %s", entrytype, entry->d_name);
continue;
}
jwArr_object(&jwc);
jwObj_raw(&jwc, "sel", "false");
jwObj_string(&jwc, "name", (char*) entry->d_name);
jwObj_int(&jwc, "size", entry_stat.st_size);
jwEnd(&jwc);
}
jwClose(&jwc);
FileListHandler(argres, rw, "/data/");
}
static void funct_file_block(char *argres, int rw)
{
FileBlockHandler(argres, rw);
FileBlockHandler(argres, rw, "/data/");
}
#if CONFIG_SDCARD_ENABLE
static void funct_sd_list(char *argres, int rw)
{
FileListHandler(argres, rw, "/sdcard/");
}
static void funct_sd_block(char *argres, int rw)
{
FileBlockHandler(argres, rw, "/sdcard/");
}
#endif
const int hw_rev = CONFIG_BOARD_HARDWARE_REVISION;
const bool VAR_TRUE = true;
const bool VAR_FALSE = false;
@ -428,7 +396,7 @@ const bool VAR_FALSE = false;
const rest_var_t SystemVariables[] =
{
{ 0, "exec", &funct_exec, VAR_FUNCT, RW, 0, 0 },
{ 0, "exec", &funct_exec, VAR_FUNCT, RW, 0, 0 },
{ 0, "time", &funct_time, VAR_FUNCT, R, 0, 0 },
{ 0, "uptime", &funct_uptime, VAR_FUNCT, R, 0, 0 },
{ 0, "free_ram", &funct_fram, VAR_FUNCT, R, 0, 0 },
@ -442,14 +410,12 @@ const rest_var_t SystemVariables[] =
{ 0, "hw_rev", ((int*) &hw_rev), VAR_INT, R, 1, 1024 },
//{ 0, "hw_opt", CONFIG_BOARD_HARDWARE_OPTION, VAR_STRING, R, 1, 256 },
{ 0, "net_bios_name", &SysConfig.NetBIOSName, VAR_STRING, RW, 3, 31 },
{ 0, "sys_name", &SysConfig.SysName, VAR_STRING, RW, 3, 31 },
{ 0, "sys_pass", &SysConfig.SysPass, VAR_PASS, RW, 3, 31 },
{ 0, "primary_color", CONFIG_WEBGUIAPP_ACCENT_COLOR, VAR_STRING, RW, 3, 31 },
{ 0, "dark_theme", (bool*) (&VAR_TRUE), VAR_BOOL, R, 0, 1 },
{ 0, "ota_url", &SysConfig.OTAURL, VAR_STRING, RW, 3, 128 },
{ 0, "ota_auto_int", &SysConfig.OTAAutoInt, VAR_INT, RW, 0, 65535 },
{ 0, "ota_state", &funct_ota_state, VAR_FUNCT, R, 0, 0 },
@ -540,7 +506,7 @@ const rest_var_t SystemVariables[] =
{ 0, "wifi_scan_res", &funct_wifiscanres, VAR_FUNCT, R, 0, 0 },
{ 0, "wifi_level", &funct_wifi_level, VAR_FUNCT, R, 0, 0 },
#endif
#endif
#if CONFIG_WEBGUIAPP_GPRS_ENABLE
{ 0, "gsm_enab", &SysConfig.gsmSettings.Flags1.bIsGSMEnabled, VAR_BOOL, RW, 0, 1 },
@ -566,7 +532,7 @@ const rest_var_t SystemVariables[] =
{ 0, "gsm_visible", (bool*) (&VAR_TRUE), VAR_BOOL, R, 0, 1 },
#else
{ 0, "gsm_visible", (bool*) (&VAR_FALSE), VAR_BOOL, R, 0, 1 },
#endif
#endif
#ifdef CONFIG_WEBGUIAPP_UART_TRANSPORT_ENABLE
{ 0, "serial_enab", &SysConfig.serialSettings.Flags.IsSerialEnabled, VAR_BOOL, RW, 0, 1 },
@ -577,8 +543,7 @@ const rest_var_t SystemVariables[] =
{ 0, "serial_visible", (bool*) (&VAR_TRUE), VAR_BOOL, R, 0, 1 },
#else
{ 0, "serial_visible", (bool*) (&VAR_FALSE), VAR_BOOL, R, 0, 1 },
#endif
#endif
#ifdef CONFIG_WEBGUIAPP_LORAWAN_ENABLE
{ 0, "lora_enab", &SysConfig.lorawanSettings.Flags1.bIsLoRaWANEnabled, VAR_BOOL, RW, 0, 1 },
@ -590,12 +555,7 @@ const rest_var_t SystemVariables[] =
#else
{ 0, "lora_visible", (bool*) (&VAR_FALSE), VAR_BOOL, R, 0, 1 },
#endif
#endif
#ifdef CONFIG_WEBGUIAPP_MBTCP_ENABLED
{ 0, "mbtcp_enab", &SysConfig.modbusSettings.IsModbusTCPEnabled, VAR_BOOL, RW, 0, 1 },
@ -603,16 +563,23 @@ const rest_var_t SystemVariables[] =
{ 0, "mbtcp_visible", (bool*) (&VAR_TRUE), VAR_BOOL, R, 0, 1 },
#else
{ 0, "mbtcp_visible", (bool*) (&VAR_FALSE), VAR_BOOL, R, 0, 1 },
#endif
#endif
{ 0, "cronrecs", &funct_cronrecs, VAR_FUNCT, RW, 0, 0 },
{ 0, "objsinfo", &funct_objsinfo, VAR_FUNCT, R, 0, 0 },
{ 0, "file_list", &funct_file_list, VAR_FUNCT, R, 0, 0 },
{ 0, "file_block", &funct_file_block, VAR_FUNCT, R, 0, 0 },
#if CONFIG_SDCARD_ENABLE
{ 0, "sd_list", &funct_sd_list, VAR_FUNCT, R, 0, 0 },
{ 0, "sd_block", &funct_sd_block, VAR_FUNCT, R, 0, 0 },
{ 0, "sd_visible", (bool*) (&VAR_TRUE), VAR_BOOL, R, 0, 1 }
#else
{ 0, "sd_visible", (bool*) (&VAR_FALSE), VAR_BOOL, R, 0, 1 },
#endif
};
esp_err_t SetConfVar(char *name, char *val, rest_var_types *tp)
{
rest_var_t *V = NULL;

View File

@ -99,6 +99,10 @@ esp_err_t WebGuiAppInit(void)
#if CONFIG_WEBGUIAPP_I2C_ENABLE
InitSysI2C();
#endif
#if CONFIG_SDCARD_ENABLE
InitSysSDCard();
#endif
esp_err_t err = nvs_flash_init();
ESP_ERROR_CHECK(esp_netif_init());

119
src/sdcard.c Normal file
View File

@ -0,0 +1,119 @@
/* Copyright 2024 Bogdan Pilyugin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* File name: sdcard.c
* Project: webguiapp
* Created on: 2024-05-07
* Author: bogd
* Description:
*/
#include <string.h>
#include <sys/unistd.h>
#include <sys/stat.h>
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"
#include "SysConfiguration.h"
#if CONFIG_SDCARD_ENABLE
static const char *TAG = "sdcard";
#define MOUNT_POINT "/sdcard"
// Pin assignments can be set in menuconfig, see "SD SPI Example Configuration" menu.
// You can also change the pin assignments here by changing the following 4 lines.
#define PIN_NUM_MISO CONFIG_SDCARD_SPI_MISO_GPIO
#define PIN_NUM_MOSI CONFIG_SDCARD_SPI_MOSI_GPIO
#define PIN_NUM_CLK CONFIG_SDCARD_SPI_SCLK_GPIO
#define PIN_NUM_CS CONFIG_SDCARD_SPI_CS_GPIO
void InitSysSDCard()
{
esp_err_t ret;
// Options for mounting the filesystem.
// If format_if_mount_failed is set to true, SD card will be partitioned and
// formatted in case when mounting fails.
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
#ifdef CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED
.format_if_mount_failed = true,
#else
.format_if_mount_failed = false,
#endif // EXAMPLE_FORMAT_IF_MOUNT_FAILED
.max_files = 5,
.allocation_unit_size = 16 * 1024
};
sdmmc_card_t *card;
const char mount_point[] = MOUNT_POINT;
ESP_LOGI(TAG, "Initializing SD card");
// Use settings defined above to initialize SD card and mount FAT filesystem.
// Note: esp_vfs_fat_sdmmc/sdspi_mount is all-in-one convenience functions.
// Please check its source code and implement error recovery when developing
// production applications.
ESP_LOGI(TAG, "Using SPI peripheral");
// By default, SD card frequency is initialized to SDMMC_FREQ_DEFAULT (20MHz)
// For setting a specific frequency, use host.max_freq_khz (range 400kHz - 20MHz for SDSPI)
// Example: for fixed frequency of 10MHz, use host.max_freq_khz = 10000;
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
host.slot = CONFIG_SDCARD_SPI_HOST;
spi_bus_config_t bus_cfg = {
.mosi_io_num = PIN_NUM_MOSI,
.miso_io_num = PIN_NUM_MISO,
.sclk_io_num = PIN_NUM_CLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4000,
};
ret = spi_bus_initialize(host.slot, &bus_cfg, SDSPI_DEFAULT_DMA);
if (ret != ESP_OK)
{
ESP_LOGE(TAG, "Failed to initialize bus.");
return;
}
// This initializes the slot without card detect (CD) and write protect (WP) signals.
// Modify slot_config.gpio_cd and slot_config.gpio_wp if your board has these signals.
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
slot_config.gpio_cs = PIN_NUM_CS;
slot_config.host_id = host.slot;
ESP_LOGI(TAG, "Mounting filesystem");
ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card);
if (ret != ESP_OK)
{
if (ret == ESP_FAIL)
{
ESP_LOGE(
TAG,
"Failed to mount filesystem. "
"If you want the card to be formatted, set the CONFIG_EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option.");
}
else
{
ESP_LOGE(TAG, "Failed to initialize the card (%s). "
"Make sure SD card lines have pull-up resistors in place.",
esp_err_to_name(ret));
}
return;
}
ESP_LOGI(TAG, "Filesystem mounted");
// Card has been initialized, print its properties
sdmmc_card_print_info(stdout, card);
}
#endif

View File

@ -35,8 +35,8 @@ function upload() {
/* Max size of an individual file. Make sure this
* value is same as that set in file_server.c */
var MAX_FILE_SIZE = 1000*1024;
var MAX_FILE_SIZE_STR = "1MB";
var MAX_FILE_SIZE = 100*1000*1024;
var MAX_FILE_SIZE_STR = "100MB";
if (fileInput.length == 0) {
alert("No file selected!");
@ -46,8 +46,8 @@ function upload() {
alert("File path on server cannot have spaces!");
} else if (filePath[filePath.length-1] == '/') {
alert("File name not specified after path!");
} else if (fileInput[0].size > 1000*1024) {
alert("File size must be less than 1MB!");
} else if (fileInput[0].size > MAX_FILE_SIZE) {
alert("File size must be less than 100MB!");
} else {
document.getElementById("newfile").disabled = true;
document.getElementById("filepath").disabled = true;