mirror of
https://github.com/m5stack/M5PaperColor-UserDemo.git
synced 2026-05-20 11:53:00 -07:00
update firmware v1.0.1
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(PROJECT_VER "1.0.1")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
|
||||
|
||||
@@ -689,7 +689,7 @@ static void app_task(void* param)
|
||||
// ---- Startup ----
|
||||
esp_err_t app_manager_start()
|
||||
{
|
||||
ESP_LOGI(g_tag, "Software version: V%u", APP_SW_VERSION);
|
||||
ESP_LOGI(g_tag, "Software version: V%s", APP_SW_VERSION);
|
||||
|
||||
// ── First boot guide image ──
|
||||
nvs_handle_t h;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#define WIFI_AP_AUTO_OFF_TIMEOUT_MIN (10)
|
||||
|
||||
/** @brief Current application software version. */
|
||||
#define APP_SW_VERSION (0x01)
|
||||
#define APP_SW_VERSION "1.0.1"
|
||||
|
||||
/**
|
||||
* @brief Displays the boot guide image.
|
||||
|
||||
@@ -54,7 +54,7 @@ extern const char _binary_index_html_end[] asm("_binary_index_html_end");
|
||||
|
||||
#define SCAN_TIMEOUT_MS (8000)
|
||||
#define CONNECT_TIMEOUT_MS (15000)
|
||||
#define UPLOAD_MAX_SIZE (512 * 1024)
|
||||
#define UPLOAD_MAX_SIZE (2 * 1024 * 1024)
|
||||
#define PHOTOS_PER_PAGE (16)
|
||||
#define MAX_PHOTOS (500)
|
||||
|
||||
@@ -138,7 +138,8 @@ static bool url_encode_component(const char *src, char *dst, size_t dst_sz)
|
||||
|
||||
for (size_t i = 0; src[i]; i++) {
|
||||
unsigned char c = (unsigned char)src[i];
|
||||
bool safe = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' ||
|
||||
|
||||
bool safe = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' ||
|
||||
c == '_' || c == '.' || c == '~';
|
||||
|
||||
if (safe) {
|
||||
|
||||
@@ -1592,6 +1592,10 @@ let modeList = [];
|
||||
let confirmCallback = null;
|
||||
let photoPage = 1;
|
||||
let photoTotalPages = 1;
|
||||
let photoPageCache = {}; // { [page]: { total_pages, names: [...] } }
|
||||
let photoCache = {}; // { [name]: { name, url } }
|
||||
let photoTotal = 0;
|
||||
let photoPageLoading = false;
|
||||
|
||||
// ==================== API Helper ====================
|
||||
async function apiCall(method, url, body, isFormData) {
|
||||
@@ -1600,15 +1604,23 @@ async function apiCall(method, url, body, isFormData) {
|
||||
opts.body = isFormData ? body : JSON.stringify(body);
|
||||
if (!isFormData) opts.headers = { "Content-Type": "application/json" };
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
const res = await fetch(url, opts);
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.message || `HTTP ${res.status}`);
|
||||
return data;
|
||||
res = await fetch(url, opts);
|
||||
} catch (e) {
|
||||
if (e.message && !e.message.startsWith("HTTP")) throw e;
|
||||
throw e;
|
||||
}
|
||||
|
||||
let data = null;
|
||||
try {
|
||||
data = await res.json();
|
||||
} catch (e) {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
throw new Error("Network request failed");
|
||||
}
|
||||
|
||||
if (!res.ok) throw new Error(data.message || `HTTP ${res.status}`);
|
||||
return data;
|
||||
}
|
||||
|
||||
// ==================== Toast ====================
|
||||
@@ -2654,11 +2666,6 @@ function applyModeConfig() {
|
||||
requestAnimationFrame(() => {
|
||||
rerenderCompositionLayout(false);
|
||||
});
|
||||
|
||||
if (currentMode === "mode_1") {
|
||||
photoPage = 1;
|
||||
loadPhotoList(photoPage);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== Unified Settings Modal ====================
|
||||
@@ -3271,10 +3278,17 @@ async function uploadOnly() {
|
||||
formData.append("action", "upload_only");
|
||||
formData.append("algorithm", displayMode);
|
||||
showToast("Uploading...", "");
|
||||
await apiCall("POST", "/api/photos/upload", formData, true);
|
||||
const resp = await apiCall("POST", "/api/photos/upload", formData, true);
|
||||
showToast("Uploaded successfully", "success");
|
||||
photoPage = 9999;
|
||||
loadPhotoList(photoPage);
|
||||
if (displayMode === "nearest" && resp && resp.name) {
|
||||
// Nearest photo inserts at front — all page boundaries shift, clear page cache
|
||||
photoPageCache = {};
|
||||
const m = resp.name.match(/^imageN0*(\d+)/i);
|
||||
if (m) photoPage = Math.ceil(Number(m[1]) / getPhotoPerPage());
|
||||
} else {
|
||||
photoPage = 9999;
|
||||
}
|
||||
await loadPhotoList(photoPage, /* forceRefresh */ true);
|
||||
loadStorage();
|
||||
} catch(e) { showToast(e.message, "error"); }
|
||||
}
|
||||
@@ -3300,10 +3314,18 @@ async function uploadWithDisplay() {
|
||||
formData.append("algorithm", displayMode);
|
||||
|
||||
showToast("Uploading...", "");
|
||||
await apiCall("POST", "/api/photos/upload", formData, true);
|
||||
const resp = await apiCall("POST", "/api/photos/upload", formData, true);
|
||||
showToast("Uploaded & display updated", "success");
|
||||
photoPage = 9999;
|
||||
loadPhotoList(photoPage);
|
||||
// Compute which page the new photo landed on from its name
|
||||
if (displayMode === "nearest" && resp && resp.name) {
|
||||
// Nearest photo inserts at front — all pages shift, clear page cache
|
||||
photoPageCache = {};
|
||||
const m = resp.name.match(/^imageN0*(\d+)/i);
|
||||
if (m) photoPage = Math.ceil(Number(m[1]) / getPhotoPerPage());
|
||||
} else {
|
||||
photoPage = 9999; // Dither appends at end, only last page changes
|
||||
}
|
||||
await loadPhotoList(photoPage, /* forceRefresh */ true);
|
||||
loadStorage();
|
||||
} catch(e) { showToast(e.message, "error"); }
|
||||
}
|
||||
@@ -3315,20 +3337,43 @@ function getPhotoPerPage() {
|
||||
return 12;
|
||||
}
|
||||
|
||||
async function loadPhotoList(page) {
|
||||
async function loadPhotoList(page, forceRefresh = false) {
|
||||
page = page || photoPage;
|
||||
const perPage = getPhotoPerPage();
|
||||
|
||||
// Serve from cache if this page was loaded before
|
||||
if (!forceRefresh && photoPageCache[page]) {
|
||||
photoPage = page;
|
||||
renderPhotoGridFromNames(photoPageCache[page]);
|
||||
renderPagination();
|
||||
return;
|
||||
}
|
||||
|
||||
photoPageLoading = true;
|
||||
try {
|
||||
const data = await apiCall("GET", `/api/photos/list?page=${page}&per_page=${perPage}`);
|
||||
photoPage = data.page;
|
||||
photoTotalPages = data.total_pages;
|
||||
renderPhotoGrid(data.photos, data.total);
|
||||
renderPagination();
|
||||
if (data.page === page || page === photoPage) {
|
||||
// Store per-photo data globally
|
||||
const names = [];
|
||||
data.photos.forEach(p => {
|
||||
photoCache[p.name] = { name: p.name, url: p.url };
|
||||
names.push(p.name);
|
||||
});
|
||||
photoPageCache[data.page] = names;
|
||||
photoPage = data.page;
|
||||
photoTotalPages = data.total_pages;
|
||||
photoTotal = data.total;
|
||||
renderPhotoGridFromNames(names);
|
||||
renderPagination();
|
||||
}
|
||||
} catch(e) { /* silent */ }
|
||||
photoPageLoading = false;
|
||||
}
|
||||
|
||||
function goPhotoPage(page) {
|
||||
if (page < 1 || page > photoTotalPages) return;
|
||||
photoPage = page;
|
||||
renderPagination();
|
||||
loadPhotoList(page);
|
||||
}
|
||||
|
||||
@@ -3348,25 +3393,26 @@ function renderPagination() {
|
||||
next.disabled = photoPage >= photoTotalPages;
|
||||
}
|
||||
|
||||
function renderPhotoGrid(photos, total) {
|
||||
function renderPhotoGridFromNames(names) {
|
||||
const grid = document.getElementById("photo-grid");
|
||||
const count = document.getElementById("photo-count");
|
||||
count.textContent = total > 0 ? `(${total})` : "";
|
||||
count.textContent = photoTotal > 0 ? `(${photoTotal})` : "";
|
||||
|
||||
if (photos.length === 0) {
|
||||
if (names.length === 0) {
|
||||
grid.innerHTML = `<div style="color:#999;font-size:14px;text-align:center;grid-column:1/-1;padding:20px;">No photos</div>`;
|
||||
return;
|
||||
}
|
||||
grid.innerHTML = photos.map(p =>
|
||||
`<div class="photo-card">
|
||||
grid.innerHTML = names.map(name => {
|
||||
const p = photoCache[name] || { name, url: "" };
|
||||
return `<div class="photo-card">
|
||||
<img class="thumb" src="${escapeHtml(p.url)}" alt="${escapeHtml(p.name)}" loading="lazy">
|
||||
<div class="photo-info"><div class="photo-name" title="${escapeHtml(p.name)}">${escapeHtml(p.name)}</div></div>
|
||||
<div class="photo-actions">
|
||||
<button class="btn btn-outline btn-sm" onclick="displayPhoto('${escapeAttr(p.name)}')">View</button>
|
||||
<button class="btn btn-danger btn-sm" onclick="deletePhoto('${escapeAttr(p.name)}')">Del</button>
|
||||
</div>
|
||||
</div>`
|
||||
).join("");
|
||||
</div>`;
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function escapeAttr(s) { return s.replace(/'/g, "\\'").replace(/"/g, """); }
|
||||
@@ -3385,13 +3431,23 @@ function deletePhoto(name) {
|
||||
await apiCall("DELETE", "/api/photos/delete?name=" + encodeURIComponent(name));
|
||||
showToast("Deleted", "success");
|
||||
loadStorage();
|
||||
// 如果当前页删空则回退一页
|
||||
if (photoPage > 1) {
|
||||
// Remove only this photo from caches
|
||||
delete photoCache[name];
|
||||
photoTotal = Math.max(0, photoTotal - 1);
|
||||
for (const key of Object.keys(photoPageCache)) {
|
||||
photoPageCache[key] = photoPageCache[key].filter(n => n !== name);
|
||||
}
|
||||
// If the current page is now empty, step back
|
||||
const names = photoPageCache[photoPage];
|
||||
if (names && names.length === 0 && photoPage > 1) {
|
||||
photoPage--;
|
||||
} else if (!names && photoPage > 1) {
|
||||
// Page not cached — check server whether it's now empty
|
||||
const perPage = getPhotoPerPage();
|
||||
const data = await apiCall("GET", `/api/photos/list?page=${photoPage}&per_page=${perPage}`);
|
||||
if (data.photos.length === 0 && photoPage > 1) { photoPage--; }
|
||||
if (data.photos.length === 0) { photoPage--; }
|
||||
}
|
||||
loadPhotoList(photoPage);
|
||||
loadPhotoList(photoPage, /* forceRefresh */ true);
|
||||
} catch(e) { showToast(e.message, "error"); }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,9 +22,77 @@
|
||||
|
||||
static const char *TAG = "Slideshow";
|
||||
|
||||
// Define an invalid index to represent “no photo is currently displayed”
|
||||
static constexpr uint8_t RX8130_RAM_INDEX_CURRENT_STORAGE = 0;
|
||||
|
||||
// Define an invalid index to represent "no photo is currently displayed"
|
||||
#define NO_PHOTO 0xFFFF
|
||||
|
||||
namespace {
|
||||
|
||||
void resetPhotoIndexState(uint16_t &pending_index)
|
||||
{
|
||||
pending_index = NO_PHOTO;
|
||||
hal.rx8130RamWrite(RX8130_RAM_INDEX_CURRENT_STORAGE, 0);
|
||||
hal.rx8130RamWrite(RX8130_RAM_INDEX_CURRENT_STORAGE + 1, 0);
|
||||
}
|
||||
|
||||
esp_err_t selectStorageMedia(bool sd_inserted, bool &sd_fallback_locked)
|
||||
{
|
||||
sd_fallback_locked = false;
|
||||
|
||||
if (sd_inserted) {
|
||||
esp_err_t ret = hal_storage_init(APP_STORAGE_MEDIA_SDMMC);
|
||||
if (ret == ESP_OK) {
|
||||
return ESP_OK;
|
||||
}
|
||||
sd_fallback_locked = true;
|
||||
ESP_LOGW(TAG, "SD init failed, fallback to SPI flash: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
return hal_storage_init(APP_STORAGE_MEDIA_SPIFLASH);
|
||||
}
|
||||
|
||||
void ensureStorageMedia(bool sd_inserted, bool &last_sd_inserted, bool &sd_fallback_locked, uint16_t &pending_index)
|
||||
{
|
||||
if (!sd_inserted) {
|
||||
last_sd_inserted = false;
|
||||
sd_fallback_locked = false;
|
||||
} else if (!last_sd_inserted) {
|
||||
last_sd_inserted = true;
|
||||
sd_fallback_locked = false;
|
||||
}
|
||||
|
||||
hal_storage_media_t target_media =
|
||||
(sd_inserted && !sd_fallback_locked) ? APP_STORAGE_MEDIA_SDMMC : APP_STORAGE_MEDIA_SPIFLASH;
|
||||
if (hal_storage_get_media() == target_media) {
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t ret = hal_storage_switch(target_media);
|
||||
if (ret == ESP_OK) {
|
||||
resetPhotoIndexState(pending_index);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!sd_inserted) {
|
||||
ESP_LOGW(TAG, "SPI flash switch failed: %s", esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
sd_fallback_locked = true;
|
||||
ESP_LOGW(TAG, "SD switch failed, fallback to SPI flash: %s", esp_err_to_name(ret));
|
||||
if (hal_storage_get_media() != APP_STORAGE_MEDIA_SPIFLASH) {
|
||||
esp_err_t fallback_ret = hal_storage_switch(APP_STORAGE_MEDIA_SPIFLASH);
|
||||
if (fallback_ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "SPI flash fallback switch failed: %s", esp_err_to_name(fallback_ret));
|
||||
return;
|
||||
}
|
||||
}
|
||||
resetPhotoIndexState(pending_index);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/* ---------- millis() ---------- */
|
||||
static inline uint32_t millis_()
|
||||
{
|
||||
@@ -43,19 +111,15 @@ bool PhotoSlideshow::init(const char *dir_path, uint8_t interval_min)
|
||||
_needs_refresh = false;
|
||||
_last_btn_c = false;
|
||||
_last_btn_b = false;
|
||||
_last_sd_inserted = hal.isSDCardInserted();
|
||||
_sd_fallback_locked = false;
|
||||
_photo_list.clear();
|
||||
_scr_w = hal.Canvas->width();
|
||||
_scr_h = hal.Canvas->height();
|
||||
|
||||
M5.Speaker.setVolume(120);
|
||||
|
||||
if (hal.isSDCardInserted()) {
|
||||
// Use the SD card at startup
|
||||
hal_storage_init(APP_STORAGE_MEDIA_SDMMC);
|
||||
} else {
|
||||
// Use APP storage at startup
|
||||
hal_storage_init(APP_STORAGE_MEDIA_SPIFLASH);
|
||||
}
|
||||
selectStorageMedia(_last_sd_inserted, _sd_fallback_locked);
|
||||
|
||||
scanPhotos();
|
||||
hal.statusEventSend(OPERATION_EVENT_STARTUP_SUCCESS);
|
||||
@@ -238,23 +302,7 @@ void PhotoSlideshow::update()
|
||||
|
||||
syncSettings();
|
||||
|
||||
// If an SD card is inserted and FLASH storage is currently in use, switch to the SD card
|
||||
// If no SD card is inserted and SD storage is currently in use, switch to FLASH storage
|
||||
if (hal.isSDCardInserted()) {
|
||||
if (hal_storage_get_media() != APP_STORAGE_MEDIA_SDMMC) {
|
||||
hal_storage_switch(APP_STORAGE_MEDIA_SDMMC);
|
||||
_pending_index = NO_PHOTO;
|
||||
hal.rx8130RamWrite(RX8130_RAM_INDEX_CURRENT, 0);
|
||||
hal.rx8130RamWrite(RX8130_RAM_INDEX_CURRENT + 1, 0);
|
||||
}
|
||||
} else {
|
||||
if (hal_storage_get_media() != APP_STORAGE_MEDIA_SPIFLASH) {
|
||||
hal_storage_switch(APP_STORAGE_MEDIA_SPIFLASH);
|
||||
_pending_index = NO_PHOTO;
|
||||
hal.rx8130RamWrite(RX8130_RAM_INDEX_CURRENT, 0);
|
||||
hal.rx8130RamWrite(RX8130_RAM_INDEX_CURRENT + 1, 0);
|
||||
}
|
||||
}
|
||||
ensureStorageMedia(hal.isSDCardInserted(), _last_sd_inserted, _sd_fallback_locked, _pending_index);
|
||||
|
||||
// Check buttons
|
||||
handleButtons();
|
||||
|
||||
@@ -158,6 +158,8 @@ private:
|
||||
bool _last_btn_c = false;
|
||||
bool _last_btn_b = false;
|
||||
bool _last_btn_a = false;
|
||||
bool _last_sd_inserted = false;
|
||||
bool _sd_fallback_locked = false;
|
||||
|
||||
/* ---- Internal helpers ---- */
|
||||
void syncSettings();
|
||||
|
||||
@@ -178,6 +178,8 @@ void Hal::init()
|
||||
s_spi_bus_inited = true;
|
||||
pm1.begin(&M5.In_I2C, M5PM1_DEFAULT_ADDR, M5PM1_I2C_FREQ_100K);
|
||||
pm1.setI2cConfig(0);
|
||||
pm1.pinMode(SD_DET_EN, OUTPUT);
|
||||
pm1.digitalWrite(SD_DET_EN, HIGH);
|
||||
pm1.pinMode(SD_DEC, INPUT_PULLUP);
|
||||
pm1.pinMode(EPD_EN, OUTPUT);
|
||||
pm1.digitalWrite(EPD_EN, HIGH);
|
||||
|
||||
@@ -134,6 +134,7 @@ private:
|
||||
static void LedStatusIndicateTask(void* task_parameters);
|
||||
static constexpr m5pm1_gpio_num_t SD_DEC = M5PM1_GPIO_NUM_1;
|
||||
static constexpr m5pm1_gpio_num_t EPD_EN = M5PM1_GPIO_NUM_0;
|
||||
static constexpr m5pm1_gpio_num_t SD_DET_EN = M5PM1_GPIO_NUM_4;
|
||||
static constexpr gpio_num_t SYS_SCL_PIN = GPIO_NUM_2;
|
||||
static constexpr gpio_num_t SYS_SDA_PIN = GPIO_NUM_3;
|
||||
TaskHandle_t _led_status_indicate_task_handle = nullptr;
|
||||
|
||||
@@ -158,9 +158,46 @@ static void usb_event_cb(tinyusb_event_t *event, void *arg)
|
||||
}
|
||||
}
|
||||
|
||||
static void _mount(void)
|
||||
static esp_err_t install_tinyusb_device_driver(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(tinyusb_msc_set_storage_mount_point(s_ctx.storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_APP));
|
||||
if (s_ctx.driver_installed) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG();
|
||||
tusb_cfg.descriptor.device = &descriptor_config;
|
||||
tusb_cfg.descriptor.full_speed_config = msc_fs_configuration_desc;
|
||||
tusb_cfg.descriptor.string = string_desc_arr;
|
||||
tusb_cfg.descriptor.string_count = sizeof(string_desc_arr) / sizeof(string_desc_arr[0]);
|
||||
tusb_cfg.event_cb = usb_event_cb;
|
||||
|
||||
esp_err_t ret = tinyusb_driver_install(&tusb_cfg);
|
||||
if (ret == ESP_OK) {
|
||||
s_ctx.driver_installed = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t uninstall_tinyusb_device_driver(void)
|
||||
{
|
||||
if (!s_ctx.driver_installed) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ret = tinyusb_driver_uninstall();
|
||||
if (ret == ESP_OK) {
|
||||
s_ctx.driver_installed = false;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t _mount(void)
|
||||
{
|
||||
esp_err_t ret = tinyusb_msc_set_storage_mount_point(s_ctx.storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_APP);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to switch storage mount point to APP: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "ls %s:", BASE_PATH);
|
||||
DIR *directory_handle = opendir(BASE_PATH);
|
||||
@@ -168,16 +205,20 @@ static void _mount(void)
|
||||
if (!directory_handle) {
|
||||
if (errno == ENOENT) {
|
||||
ESP_LOGE(TAG, "Directory doesn't exist %s", BASE_PATH);
|
||||
ret = ESP_ERR_NOT_FOUND;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unable to read directory %s", BASE_PATH);
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
return;
|
||||
tinyusb_msc_set_storage_mount_point(s_ctx.storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_USB);
|
||||
return ret;
|
||||
}
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(directory_handle)) != NULL) {
|
||||
printf(" %s\n", entry->d_name);
|
||||
}
|
||||
closedir(directory_handle);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t storage_init_spiflash(wl_handle_t *wl_handle)
|
||||
@@ -352,26 +393,37 @@ static esp_err_t _bringup(hal_storage_media_t media)
|
||||
ESP_LOGE(TAG, "set_storage_callback fail: %s", esp_err_to_name(ret));
|
||||
goto err_after_storage;
|
||||
}
|
||||
_mount();
|
||||
{
|
||||
tinyusb_config_t tusb_cfg = TINYUSB_DEFAULT_CONFIG();
|
||||
tusb_cfg.descriptor.device = &descriptor_config;
|
||||
tusb_cfg.descriptor.full_speed_config = msc_fs_configuration_desc;
|
||||
tusb_cfg.descriptor.string = string_desc_arr;
|
||||
tusb_cfg.descriptor.string_count = sizeof(string_desc_arr) / sizeof(string_desc_arr[0]);
|
||||
tusb_cfg.event_cb = usb_event_cb;
|
||||
ret = tinyusb_driver_install(&tusb_cfg);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "tinyusb_driver_install fail: %s", esp_err_to_name(ret));
|
||||
goto err_after_storage;
|
||||
}
|
||||
s_ctx.driver_installed = true;
|
||||
ret = _mount();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Storage mount to APP failed: %s", esp_err_to_name(ret));
|
||||
goto err_after_storage;
|
||||
}
|
||||
|
||||
ret = install_tinyusb_device_driver();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "tinyusb_driver_install fail: %s", esp_err_to_name(ret));
|
||||
goto err_after_storage;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
err_after_storage:
|
||||
uninstall_tinyusb_device_driver();
|
||||
if (s_ctx.storage_hdl) {
|
||||
tinyusb_msc_delete_storage(s_ctx.storage_hdl);
|
||||
tinyusb_msc_mount_point_t cur;
|
||||
if (tinyusb_msc_get_storage_mount_point(s_ctx.storage_hdl, &cur) == ESP_OK &&
|
||||
cur == TINYUSB_MSC_STORAGE_MOUNT_APP) {
|
||||
esp_err_t unmount_ret =
|
||||
tinyusb_msc_set_storage_mount_point(s_ctx.storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_USB);
|
||||
if (unmount_ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to switch storage mount point to USB during cleanup: %s",
|
||||
esp_err_to_name(unmount_ret));
|
||||
}
|
||||
}
|
||||
esp_err_t delete_ret = tinyusb_msc_delete_storage(s_ctx.storage_hdl);
|
||||
if (delete_ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "delete_storage during cleanup failed: %s", esp_err_to_name(delete_ret));
|
||||
}
|
||||
s_ctx.storage_hdl = NULL;
|
||||
}
|
||||
s_ctx.storage_created = false;
|
||||
@@ -409,13 +461,24 @@ esp_err_t hal_storage_switch(hal_storage_media_t media)
|
||||
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
ret = uninstall_tinyusb_device_driver();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "tinyusb_driver_uninstall fail: %s", esp_err_to_name(ret));
|
||||
hal_storage_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (s_ctx.storage_created && s_ctx.storage_hdl) {
|
||||
tinyusb_msc_mount_point_t cur;
|
||||
if (tinyusb_msc_get_storage_mount_point(s_ctx.storage_hdl, &cur) == ESP_OK) {
|
||||
if (cur != TINYUSB_MSC_STORAGE_MOUNT_APP) {
|
||||
tinyusb_msc_set_storage_mount_point(s_ctx.storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_APP);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
if (tinyusb_msc_get_storage_mount_point(s_ctx.storage_hdl, &cur) == ESP_OK &&
|
||||
cur == TINYUSB_MSC_STORAGE_MOUNT_APP) {
|
||||
ret = tinyusb_msc_set_storage_mount_point(s_ctx.storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_USB);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "switch mount point to USB fail: %s", esp_err_to_name(ret));
|
||||
hal_storage_unlock();
|
||||
return ret;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
ret = tinyusb_msc_delete_storage(s_ctx.storage_hdl);
|
||||
@@ -484,7 +547,55 @@ esp_err_t hal_storage_switch(hal_storage_media_t media)
|
||||
ESP_LOGW(TAG, "set_storage_callback fail: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
_mount();
|
||||
ret = _mount();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Storage mount to APP failed after switch: %s", esp_err_to_name(ret));
|
||||
if (s_ctx.storage_hdl) {
|
||||
esp_err_t delete_ret = tinyusb_msc_delete_storage(s_ctx.storage_hdl);
|
||||
if (delete_ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "delete_storage after mount failure failed: %s", esp_err_to_name(delete_ret));
|
||||
}
|
||||
s_ctx.storage_hdl = NULL;
|
||||
}
|
||||
s_ctx.storage_created = false;
|
||||
if (media == APP_STORAGE_MEDIA_SPIFLASH) {
|
||||
storage_deinit_spiflash();
|
||||
} else {
|
||||
storage_deinit_sdmmc();
|
||||
}
|
||||
hal_storage_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = install_tinyusb_device_driver();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "tinyusb_driver_install fail after switch: %s", esp_err_to_name(ret));
|
||||
if (s_ctx.storage_hdl) {
|
||||
tinyusb_msc_mount_point_t cur;
|
||||
if (tinyusb_msc_get_storage_mount_point(s_ctx.storage_hdl, &cur) == ESP_OK &&
|
||||
cur == TINYUSB_MSC_STORAGE_MOUNT_APP) {
|
||||
esp_err_t unmount_ret =
|
||||
tinyusb_msc_set_storage_mount_point(s_ctx.storage_hdl, TINYUSB_MSC_STORAGE_MOUNT_USB);
|
||||
if (unmount_ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to switch storage mount point to USB during install cleanup: %s",
|
||||
esp_err_to_name(unmount_ret));
|
||||
}
|
||||
}
|
||||
esp_err_t delete_ret = tinyusb_msc_delete_storage(s_ctx.storage_hdl);
|
||||
if (delete_ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "delete_storage after install failure failed: %s", esp_err_to_name(delete_ret));
|
||||
}
|
||||
s_ctx.storage_hdl = NULL;
|
||||
}
|
||||
s_ctx.storage_created = false;
|
||||
if (media == APP_STORAGE_MEDIA_SPIFLASH) {
|
||||
storage_deinit_spiflash();
|
||||
} else {
|
||||
storage_deinit_sdmmc();
|
||||
}
|
||||
hal_storage_unlock();
|
||||
return ret;
|
||||
}
|
||||
hal_storage_unlock();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cstring>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
static bool get_bmp_size_mem(const uint8_t *data, size_t len, int *width, int *height)
|
||||
{
|
||||
@@ -82,18 +83,28 @@ bool get_image_size_from_file(const char *path, int *width, int *height)
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return false;
|
||||
|
||||
uint8_t buf[2048];
|
||||
size_t n = fread(buf, 1, sizeof(buf), f);
|
||||
constexpr size_t BUF_SIZE = 65536;
|
||||
uint8_t *buf = (uint8_t *)malloc(BUF_SIZE);
|
||||
if (!buf) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
size_t n = fread(buf, 1, BUF_SIZE, f);
|
||||
fclose(f);
|
||||
|
||||
if (n == 0) return false;
|
||||
|
||||
if (strcasecmp(dot, ".jpg") == 0 || strcasecmp(dot, ".jpeg") == 0) {
|
||||
return get_jpg_size_mem(buf, n, width, height);
|
||||
} else if (strcasecmp(dot, ".bmp") == 0) {
|
||||
return get_bmp_size_mem(buf, n, width, height);
|
||||
} else if (strcasecmp(dot, ".png") == 0) {
|
||||
return get_png_size_mem(buf, n, width, height);
|
||||
if (n == 0) {
|
||||
free(buf);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
bool ok = false;
|
||||
if (strcasecmp(dot, ".jpg") == 0 || strcasecmp(dot, ".jpeg") == 0) {
|
||||
ok = get_jpg_size_mem(buf, n, width, height);
|
||||
} else if (strcasecmp(dot, ".bmp") == 0) {
|
||||
ok = get_bmp_size_mem(buf, n, width, height);
|
||||
} else if (strcasecmp(dot, ".png") == 0) {
|
||||
ok = get_png_size_mem(buf, n, width, height);
|
||||
}
|
||||
free(buf);
|
||||
return ok;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user