diff --git a/docs/keira/features.rst b/docs/keira/features.rst index c0af79a5..66a4e49d 100644 --- a/docs/keira/features.rst +++ b/docs/keira/features.rst @@ -20,7 +20,7 @@ Keira підтримує запуск другорядних прошивок з - Вам не потрібно щоразу перепрошивати Лілку, щоб випробувати другорядні прошивки: просто скопіюйте їх на SD-картку. - Ви можете ділитися своїми скомпільованими прошивками (у вигляді ``.bin``-файлів) з іншими користувачами Лілки, без необхідності відправляти їм весь код вашої власної прошивки. -Найяскравіший приклад використання цієї функції - запуск Doom. Достатньо скопіювати в корінь SD-карти файли ``doom.bin`` і ``doom.wad`` (або ``doom1.wad``), і ви зможете грати в Doom! +Найяскравіший приклад використання цієї функції - запуск Doom. Достатньо скопіювати на SD-карту файли ``doom.bin`` і ``doom.wad`` (або ``doom1.wad``), і ви зможете грати в Doom! .. note:: Щоб отримати ``doom.bin``, вам потрібно скомпілювати прошивку, що знаходиться в папці ``firmware/doom``, і тоді скопіювати файл ``doom.bin`` на SD-картку. diff --git a/docs/keira/lua/reference/lilka.rst b/docs/keira/lua/reference/lilka.rst index 500922aa..697ccdd4 100644 --- a/docs/keira/lua/reference/lilka.rst +++ b/docs/keira/lua/reference/lilka.rst @@ -23,7 +23,7 @@ Ви повинні визначити її в своєму коді, якщо ви хочете використовувати її для оновлення вашої програми. -.. lua:function:: render() +.. lua:function:: draw() Ця функція автоматично викликається після `lilka.update`. Тут відбувається відображення графіки. @@ -32,4 +32,4 @@ .. lua:autoclass:: lilka .. Does not work with aliases... So we have to copy-paste the stuff above. - .. :members: init, update, render + .. :members: init, update, draw diff --git a/firmware/doom/.gitattributes b/firmware/doom/.gitattributes index 409cce88..d5e5d277 100644 --- a/firmware/doom/.gitattributes +++ b/firmware/doom/.gitattributes @@ -1 +1 @@ -lib/doomgeneric/** linguist-vendored +lib/** linguist-vendored diff --git a/firmware/doom/src/main.cpp b/firmware/doom/src/main.cpp index dcc632b6..d85c36e2 100644 --- a/firmware/doom/src/main.cpp +++ b/firmware/doom/src/main.cpp @@ -105,9 +105,22 @@ void setup() { char arg[] = "doomgeneric"; char arg2[] = "-iwad"; char arg3[64]; + + // Get firmware arg + String firmwareFile = lilka::multiboot.getFirmwarePath(); + lilka::serial_log("Firmware file: %s\n", firmwareFile.c_str()); + String firmwareDir; + if (firmwareFile.length()) { + // Get directory from firmware file + int lastSlash = firmwareFile.lastIndexOf('/'); + firmwareDir = firmwareFile.substring(0, lastSlash); + } else { + firmwareDir = "/"; + } + bool found = false; - // Find the wad file - File root = SD.open("/"); + // Find the WAD file + File root = SD.open(firmwareDir.c_str()); File file; while ((file = root.openNextFile())) { if (file.isDirectory()) { @@ -118,7 +131,7 @@ void setup() { name.toLowerCase(); lilka::serial_log("Checking file: %s\n", name.c_str()); if (name.startsWith("doom") && name.endsWith(".wad")) { - strcpy(arg3, (String("/sd/") + file.name()).c_str()); + strcpy(arg3, (String("/sd") + firmwareDir + "/" + file.name()).c_str()); lilka::serial_log("Found .WAD file: %s\n", arg3); found = true; file.close(); @@ -130,7 +143,7 @@ void setup() { if (!found) { lilka::Alert alert("Doom", "Не знайдено .WAD-файлу на картці пам'яті"); alert.draw(&lilka::display); - while (!alert.isDone()) { + while (!alert.isFinished()) { alert.update(); } esp_restart(); diff --git a/firmware/keira/.gitattributes b/firmware/keira/.gitattributes new file mode 100644 index 00000000..d5e5d277 --- /dev/null +++ b/firmware/keira/.gitattributes @@ -0,0 +1 @@ +lib/** linguist-vendored diff --git a/firmware/keira/lib/SimpleFTPServer/src/FtpServerKey.h b/firmware/keira/lib/SimpleFTPServer/src/FtpServerKey.h index e67c4306..bb3fba94 100644 --- a/firmware/keira/lib/SimpleFTPServer/src/FtpServerKey.h +++ b/firmware/keira/lib/SimpleFTPServer/src/FtpServerKey.h @@ -115,8 +115,8 @@ However, the last one, that will break anything that uses the ESP32 WiFi library #define FTP_TIME_OUT 5 * 60 -// Wait for authentication for 10 seconds (expressed in seconds) -#define FTP_AUTH_TIME_OUT 10 +// Wait for authentication for 30 seconds (expressed in seconds) +#define FTP_AUTH_TIME_OUT 30 // Size of file buffer for read/write diff --git a/firmware/keira/sdcard/counter/counter.lua b/firmware/keira/sdcard/counter/counter.lua index 29a4c542..9e896e62 100644 --- a/firmware/keira/sdcard/counter/counter.lua +++ b/firmware/keira/sdcard/counter/counter.lua @@ -8,5 +8,5 @@ state.counter = state.counter + 1 display.fill_screen(display.color565(64, 0, 64)) display.set_cursor(0, 64) display.print('Лічильник запусків\nпрограми: ', state.counter) -display.render() +display.queue_draw() util.sleep(0.5) diff --git a/firmware/keira/sdcard/test.lua b/firmware/keira/sdcard/test.lua index b175d9cb..9e461880 100644 --- a/firmware/keira/sdcard/test.lua +++ b/firmware/keira/sdcard/test.lua @@ -13,14 +13,11 @@ for i = 10, 1, -1 do local y = math.random(280 - 64) display.draw_image(face, x, y) - display.render() + display.queue_draw() util.sleep(0.25) end --- Now, we want to draw random lines & faces really fast directly to display, without any buffering! -display.set_buffered(false) - local key = controller.get_state() while not key.a.just_pressed do local x1 = math.random(240) @@ -34,5 +31,7 @@ while not key.a.just_pressed do local y = math.random(280 - 64) display.draw_image(face, x, y) + display.queue_draw() + key = controller.get_state() end diff --git a/firmware/keira/src/.gitattributes b/firmware/keira/src/.gitattributes index 53136079..efe7b954 100644 --- a/firmware/keira/src/.gitattributes +++ b/firmware/keira/src/.gitattributes @@ -1,3 +1,2 @@ keira_splash.h linguist-generated apps/icons/*.h linguist-generated -apps/mjs/mjs.* linguist-vendored diff --git a/firmware/keira/src/apps/demos/keyboard.cpp b/firmware/keira/src/apps/demos/keyboard.cpp index 6f81327f..27d5a31f 100644 --- a/firmware/keira/src/apps/demos/keyboard.cpp +++ b/firmware/keira/src/apps/demos/keyboard.cpp @@ -10,7 +10,7 @@ void KeyboardApp::run() { dialog.update(); dialog.draw(canvas); queueDraw(); - if (dialog.isDone()) { + if (dialog.isFinished()) { break; } } @@ -20,7 +20,7 @@ void KeyboardApp::run() { queueDraw(); while (true) { alert.update(); - if (alert.isDone()) { + if (alert.isFinished()) { break; } } diff --git a/firmware/keira/src/apps/demos/letris.cpp b/firmware/keira/src/apps/demos/letris.cpp index 0d3ce1aa..f4e913f4 100644 --- a/firmware/keira/src/apps/demos/letris.cpp +++ b/firmware/keira/src/apps/demos/letris.cpp @@ -295,7 +295,7 @@ void LetrisApp::run() { queueDraw(); while (1) { alert.update(); - if (alert.isDone()) { + if (alert.isFinished()) { break; } taskYIELD(); diff --git a/firmware/keira/src/apps/demos/scan_i2c.cpp b/firmware/keira/src/apps/demos/scan_i2c.cpp index a2060d47..9f113cb9 100644 --- a/firmware/keira/src/apps/demos/scan_i2c.cpp +++ b/firmware/keira/src/apps/demos/scan_i2c.cpp @@ -52,7 +52,7 @@ void ScanI2CApp::run() { lilka::Alert alert("Помилка", "Ця програма потребує Лілку версії 2 або вище."); alert.draw(canvas); queueDraw(); - while (!alert.isDone()) { + while (!alert.isFinished()) { alert.update(); } #endif diff --git a/firmware/keira/src/apps/demos/transform.cpp b/firmware/keira/src/apps/demos/transform.cpp index aec0b0c4..ce3f098e 100644 --- a/firmware/keira/src/apps/demos/transform.cpp +++ b/firmware/keira/src/apps/demos/transform.cpp @@ -12,7 +12,7 @@ void TransformApp::run() { lilka::Alert alert("Помилка", "Не вдалось завантажити face.bmp з SD-карти."); alert.draw(canvas); queueDraw(); - while (!alert.isDone()) { + while (!alert.isFinished()) { alert.update(); } return; diff --git a/firmware/keira/src/apps/launcher.cpp b/firmware/keira/src/apps/launcher.cpp index f025f5a6..a2a2324b 100644 --- a/firmware/keira/src/apps/launcher.cpp +++ b/firmware/keira/src/apps/launcher.cpp @@ -1,3 +1,5 @@ +#include + #include "launcher.h" #include "appmanager.h" @@ -45,10 +47,14 @@ void LauncherApp::run() { menu.addItem("Налаштування", &settings, lilka::display.color565(255, 200, 224)); while (1) { - menu.update(); - menu.draw(canvas); + // NOTE: most likely we'll have a needness in inserting here + // taskYIELD() to prevent some problems... + while (!menu.isFinished()) { + menu.update(); + menu.draw(canvas); + } queueDraw(); - int16_t index = menu.getSelectedIndex(); + int16_t index = menu.getCursor(); if (index != -1) { if (index == 0) { appsMenu(); @@ -86,17 +92,16 @@ void LauncherApp::appsMenu() { } menu.addItem("<< Назад"); while (1) { - menu.update(); - menu.draw(canvas); - queueDraw(); - int16_t index = menu.getSelectedIndex(); - if (index != -1) { - if (index == appCount) { - break; - } - AppManager::getInstance()->runApp(app_items[index].construct()); + while (!menu.isFinished()) { + menu.update(); + menu.draw(canvas); + queueDraw(); } - taskYIELD(); + int16_t index = menu.getCursor(); + if (index == appCount) { + break; + } + AppManager::getInstance()->runApp(app_items[index].construct()); } } @@ -186,10 +191,12 @@ void LauncherApp::fileBrowserMenu(String path, int fbrowserType) { menu.addItem("<< Назад", 0, 0); while (1) { - menu.update(); - menu.draw(canvas); - queueDraw(); - int16_t index = menu.getSelectedIndex(); + while (!menu.isFinished()) { + menu.update(); + menu.draw(canvas); + queueDraw(); + } + int16_t index = menu.getCursor(); if (index != -1) { if (index >= numEntries - 1) break; if (entries[index].type == lilka::EntryType::ENT_DIRECTORY) { @@ -211,15 +218,16 @@ void LauncherApp::selectFile(String path) { alert("Помилка", "Ця операція потребує Лілку 2.0"); return; #else + lilka::ProgressDialog dialog("Завантаження", path + "\n\nПочинаємо..."); + dialog.draw(canvas); + queueDraw(); int error; error = lilka::multiboot.start(path); if (error) { alert("Помилка", String("Етап: 1\nКод: ") + error); return; } - lilka::ProgressDialog dialog( - "Завантаження", path + "\nРозмір: " + String(lilka::multiboot.getBytesTotal()) + " Б" - ); + dialog.setMessage(path + "\n\nРозмір: " + String(lilka::multiboot.getBytesTotal()) + " Б"); dialog.draw(canvas); queueDraw(); while ((error = lilka::multiboot.process()) > 0) { @@ -273,16 +281,16 @@ void LauncherApp::devMenu() { } menu.addItem("<< Назад"); while (1) { - menu.update(); - menu.draw(canvas); - queueDraw(); - int16_t index = menu.getSelectedIndex(); - if (index != -1) { - if (index == appCount) { - return; - } - AppManager::getInstance()->runApp(app_items[index].construct()); + while (!menu.isFinished()) { + menu.update(); + menu.draw(canvas); + queueDraw(); + } + int16_t index = menu.getCursor(); + if (index == appCount) { + return; } + AppManager::getInstance()->runApp(app_items[index].construct()); } } @@ -292,6 +300,7 @@ void LauncherApp::settingsMenu() { "Про систему", "Інфо про пристрій", "Таблиця розділів", + "Форматування SD-карти", "Перезавантаження", "<< Назад", }; @@ -301,68 +310,98 @@ void LauncherApp::settingsMenu() { menu.addItem(titles[i]); } while (1) { - menu.update(); - menu.draw(canvas); - queueDraw(); - int16_t index = menu.getSelectedIndex(); - if (index != -1) { - if (index == count - 1) { - return; + while (!menu.isFinished()) { + menu.update(); + menu.draw(canvas); + queueDraw(); + } + int16_t index = menu.getCursor(); + if (index == count - 1) { + return; + } + if (index == 0) { + AppManager::getInstance()->runApp(new WiFiConfigApp()); + } else if (index == 1) { + alert("Keira OS", "by Андерсон & friends"); + } else if (index == 2) { + char buf[256]; + NetworkService* networkService = + static_cast(ServiceManager::getInstance()->getService()); + // TODO: use dynamic_cast and assert networkService != nullptr + sprintf( + buf, + "Модель: %s\n" + "Ревізія: %d\n" + "Версія ESP-IDF: %s\n" + "Частота: %d МГц\n" + "Кількість ядер: %d\n" + "IP: %s", + ESP.getChipModel(), + ESP.getChipRevision(), + esp_get_idf_version(), + ESP.getCpuFreqMHz(), + ESP.getChipCores(), + networkService->getIpAddr().c_str() + ); + alert("Інфо про пристрій", buf); + } else if (index == 3) { + String labels[16]; + int labelCount = lilka::sys.get_partition_labels(labels); + labels[labelCount++] = "<< Назад"; + lilka::Menu partitionMenu("Таблиця розділів"); + for (int i = 0; i < labelCount; i++) { + partitionMenu.addItem(labels[i]); } - if (index == 0) { - AppManager::getInstance()->runApp(new WiFiConfigApp()); - } else if (index == 1) { - alert("Keira OS", "by Андерсон & friends"); - } else if (index == 2) { - char buf[256]; - NetworkService* networkService = - static_cast(ServiceManager::getInstance()->getService()); - // TODO: use dynamic_cast and assert networkService != nullptr - sprintf( - buf, - "Модель: %s\n" - "Ревізія: %d\n" - "Версія ESP-IDF: %s\n" - "Частота: %d МГц\n" - "Кількість ядер: %d\n" - "IP: %s", - ESP.getChipModel(), - ESP.getChipRevision(), - esp_get_idf_version(), - ESP.getCpuFreqMHz(), - ESP.getChipCores(), - networkService->getIpAddr().c_str() - ); - alert("Інфо про пристрій", buf); - } else if (index == 3) { - String labels[16]; - int labelCount = lilka::sys.get_partition_labels(labels); - labels[labelCount++] = "<< Назад"; - lilka::Menu partitionMenu("Таблиця розділів"); - for (int i = 0; i < labelCount; i++) { - partitionMenu.addItem(labels[i]); - } - while (1) { + while (1) { + while (!partitionMenu.isFinished()) { partitionMenu.update(); partitionMenu.draw(canvas); queueDraw(); - int16_t partitionIndex = partitionMenu.getSelectedIndex(); - if (partitionIndex != -1) { - if (partitionIndex == labelCount - 1) { - break; - } - alert( - labels[partitionIndex], - String("Адреса: 0x") + - String(lilka::sys.get_partition_address(labels[partitionIndex].c_str()), HEX) + "\n" + - "Розмір: 0x" + - String(lilka::sys.get_partition_size(labels[partitionIndex].c_str()), HEX) - ); - } } - } else if (index == 4) { - esp_restart(); + int16_t partitionIndex = partitionMenu.getCursor(); + if (partitionIndex == labelCount - 1) { + break; + } + alert( + labels[partitionIndex], + String("Адреса: 0x") + + String(lilka::sys.get_partition_address(labels[partitionIndex].c_str()), HEX) + "\n" + + "Розмір: 0x" + String(lilka::sys.get_partition_size(labels[partitionIndex].c_str()), HEX) + ); + } + } else if (index == 4) { + if (!lilka::sdcard.available()) { + alert("Помилка", "SD-карта не знайдена"); + continue; + } + lilka::Alert confirm( + "Форматування", "УВАГА: Це очистить ВСІ дані з SD-карти!\n\nПродовжити?\n\nSTART - так\nA - скасувати" + ); + confirm.draw(canvas); + queueDraw(); + while (!confirm.isFinished()) { + confirm.update(); + taskYIELD(); + } + if (confirm.getButton() != lilka::Button::START) { + continue; + } + lilka::ProgressDialog dialog("Форматування", "Будь ласка, зачекайте..."); + dialog.draw(canvas); + queueDraw(); + const uint32_t workSize = FF_MAX_SS * 4; + void* work = ps_malloc(workSize + ); // Buffer (4 sectors), otherwise f_mkfs tries to allocate in stack and fails due to task stack size + FRESULT result = f_mkfs("/sd", FM_ANY, 0, work, workSize); // TODO - hardcoded mountpoint + free(work); + if (result != FR_OK) { + this->alert("Помилка", "Не вдалося сформатувати SD-карту, код помилки: " + String(result)); + continue; } + this->alert("Форматування", "Форматування SD-карти завершено!"); + + } else if (index == 5) { + esp_restart(); } } } @@ -371,11 +410,8 @@ void LauncherApp::alert(String title, String message) { lilka::Alert alert(title, message); alert.draw(canvas); queueDraw(); - while (1) { + while (!alert.isFinished()) { alert.update(); - if (alert.isDone()) { - break; - } taskYIELD(); } } diff --git a/firmware/keira/src/apps/lua/luarunner.cpp b/firmware/keira/src/apps/lua/luarunner.cpp index fc2f6cd5..2915af04 100644 --- a/firmware/keira/src/apps/lua/luarunner.cpp +++ b/firmware/keira/src/apps/lua/luarunner.cpp @@ -302,7 +302,7 @@ void LuaFileRunnerApp::run() { lilka::Alert alert("Lua", String("Помилка: ") + err); alert.draw(canvas); queueDraw(); - while (!alert.isDone()) { + while (!alert.isFinished()) { alert.update(); } } @@ -430,7 +430,7 @@ void LuaLiveRunnerApp::execSource(String source) { lilka::Alert alert("Lua", String("Помилка: ") + err); alert.draw(canvas); queueDraw(); - while (!alert.isDone()) { + while (!alert.isFinished()) { alert.update(); } } diff --git a/firmware/keira/src/apps/mjs/mjsrunner.cpp b/firmware/keira/src/apps/mjs/mjsrunner.cpp index 07d83e21..e5c346c8 100644 --- a/firmware/keira/src/apps/mjs/mjsrunner.cpp +++ b/firmware/keira/src/apps/mjs/mjsrunner.cpp @@ -18,7 +18,7 @@ void MJSApp::run() { lilka::Alert alert("mJS", String("Помилка: ") + err); alert.draw(canvas); queueDraw(); - while (!alert.isDone()) { + while (!alert.isFinished()) { alert.update(); } } diff --git a/firmware/keira/src/apps/wifi_config.cpp b/firmware/keira/src/apps/wifi_config.cpp index 271d75d5..c8d99ce0 100644 --- a/firmware/keira/src/apps/wifi_config.cpp +++ b/firmware/keira/src/apps/wifi_config.cpp @@ -38,7 +38,7 @@ void WiFiConfigApp::run() { lilka::Alert alert("Помилка", "Не вдалося сканувати мережі, код помилки: " + String(count)); alert.draw(canvas); queueDraw(); - while (!alert.isDone()) { + while (!alert.isFinished()) { alert.update(); } return; @@ -66,23 +66,22 @@ void WiFiConfigApp::run() { menu.addItem("<< Назад"); count++; while (1) { - int index = -1; - while (index == -1) { + while (!menu.isFinished()) { menu.update(); menu.draw(canvas); queueDraw(); - index = menu.getSelectedIndex(); } - if (index == count - 1) { + int cursor = menu.getCursor(); + if (cursor == count - 1) { return; } - String ssid = networks[index]; + String ssid = networks[cursor]; lilka::InputDialog passwordDialog("Введіть пароль:"); passwordDialog.setMasked(true); passwordDialog.setValue(networkService->getPassword(ssid)); - while (!passwordDialog.isDone()) { + while (!passwordDialog.isFinished()) { passwordDialog.update(); passwordDialog.draw(canvas); queueDraw(); @@ -124,7 +123,7 @@ void WiFiConfigApp::run() { alert.draw(canvas); queueDraw(); - while (!alert.isDone()) { + while (!alert.isFinished()) { alert.update(); } diff --git a/firmware/sample/src/main.cpp b/firmware/sample/src/main.cpp index e2ae2ccb..c6d51759 100644 --- a/firmware/sample/src/main.cpp +++ b/firmware/sample/src/main.cpp @@ -5,6 +5,17 @@ void setup() { } void loop() { - lilka::ui_alert("Привіт!", "Це - тестове повідомлення."); - lilka::ui_alert("Привіт!", "А це - ще одне тестове повідомлення."); + lilka::Alert alert1("Лілка", "Це - тестове повідомлення."); + alert1.draw(&lilka::display); + while (!alert1.isFinished()) { + alert1.update(); + delay(100); + } + + lilka::Alert alert2("Привіт!", "А це - ще одне тестове повідомлення."); + alert2.draw(&lilka::display); + while (!alert2.isFinished()) { + alert2.update(); + delay(100); + } } diff --git a/sdk/addons/lualilka/library/lilka.lua b/sdk/addons/lualilka/library/lilka.lua index bc74a9f5..756f6a67 100644 --- a/sdk/addons/lualilka/library/lilka.lua +++ b/sdk/addons/lualilka/library/lilka.lua @@ -17,6 +17,6 @@ lilka = {} ---Ця функція автоматично викликається після `lilka.update`. Тут відбувається відображення графіки. --- ---Ви повинні визначити її в своєму коді, якщо ви хочете використовувати її для малювання вашої програми. ----@alias lilka.render fun() +---@alias lilka.draw fun() return lilka diff --git a/sdk/addons/lualilka/library/state.lua b/sdk/addons/lualilka/library/state.lua index 4cb06f9f..52e0e408 100644 --- a/sdk/addons/lualilka/library/state.lua +++ b/sdk/addons/lualilka/library/state.lua @@ -23,6 +23,6 @@ --- display.fill_screen(display.color565(64, 0, 64)) --- display.set_cursor(0, 64) --- display.print('Лічильник запусків\nпрограми: ', state.counter) ---- display.render() +--- display.queue_draw() --- util.sleep(0.5) state = {} diff --git a/sdk/lib/lilka/library.json b/sdk/lib/lilka/library.json index 7a0e83b6..8a7b2e47 100644 --- a/sdk/lib/lilka/library.json +++ b/sdk/lib/lilka/library.json @@ -1,6 +1,6 @@ { "name": "Lilka", - "version": "1.3.1", + "version": "1.4.0", "license": "GPL-2.0", "repository": { "type": "git", diff --git a/sdk/lib/lilka/src/lilka/display.cpp b/sdk/lib/lilka/src/lilka/display.cpp index c0894ae7..4d0e1db0 100644 --- a/sdk/lib/lilka/src/lilka/display.cpp +++ b/sdk/lib/lilka/src/lilka/display.cpp @@ -207,7 +207,6 @@ void Canvas::drawImageTransformed(Image* image, int16_t destX, int16_t destY, Tr // Draw the transformed image to the new image. Transform inverse = transform.inverse(); - uint64_t start = esp_timer_get_time(); int_vector_t point{0, 0}; for (point.y = topLeft.y; point.y < bottomRight.y; point.y++) { for (point.x = topLeft.x; point.x < bottomRight.x; point.x++) { @@ -224,8 +223,6 @@ void Canvas::drawImageTransformed(Image* image, int16_t destX, int16_t destY, Tr } } } - uint64_t end = esp_timer_get_time(); - Serial.println("Transformed image in " + String(end - start) + " us"); // TODO: Draw directly to the canvas? drawImage(&destImage, destX + topLeft.x, destY + topLeft.y); diff --git a/sdk/lib/lilka/src/lilka/multiboot.cpp b/sdk/lib/lilka/src/lilka/multiboot.cpp index 879ca2a0..ff180c08 100644 --- a/sdk/lib/lilka/src/lilka/multiboot.cpp +++ b/sdk/lib/lilka/src/lilka/multiboot.cpp @@ -124,7 +124,7 @@ int MultiBoot::start(String path) { ota_partition = esp_ota_get_next_update_partition(current_partition); // get ota1 (we're in ota0 now) if (ota_partition == NULL) { serial_err("Failed to get next OTA partition"); - return -3; + return -4; } serial_log( "OTA partition: %s, type: %d, subtype: %d, size: %d", @@ -137,7 +137,21 @@ int MultiBoot::start(String path) { esp_err_t err = esp_ota_begin(ota_partition, bytesTotal, &ota_handle); if (err != ESP_OK) { serial_err("Failed to begin OTA: %d", err); - return -4; + return -5; + } + + // Write path to last 256 bytes of the OTA partition + size_t partition_size = ota_partition->size; + size_t offset = partition_size - 256; + String arg = path; + // Remove "/sd" prefix + // TODO: Maybe we should use absolute path (including "/sd")? + if (arg.startsWith("/sd/")) { + arg = arg.substring(3); + } + if (esp_partition_write(ota_partition, offset, arg.c_str(), arg.length() + 1) != ESP_OK) { + serial_err("Failed to write arg to OTA partition"); + return -6; } return 0; @@ -159,13 +173,12 @@ int MultiBoot::process() { esp_err_t err = esp_ota_write(ota_handle, buf, len); if (err != ESP_OK) { serial_err("Failed to write OTA: %d", err); - return -5; + return -7; } bytesWritten += len; } - serial_log("Written %d bytes", bytesWritten); return bytesWritten; } @@ -193,14 +206,14 @@ int MultiBoot::finishAndReboot() { esp_err_t err = esp_ota_end(ota_handle); if (err != ESP_OK) { serial_err("Failed to end OTA: %d", err); - return -6; + return -8; } // Перевстановлення активного розділу на OTA-розділ (його буде запущено лише один раз, після чого активним залишиться основний розділ). err = esp_ota_set_boot_partition(ota_partition); if (err != ESP_OK) { serial_err("Failed to set boot partition: %d", err); - return -7; + return -9; } // Запуск нової прошивки. @@ -209,6 +222,21 @@ int MultiBoot::finishAndReboot() { return 0; // unreachable } +String MultiBoot::getFirmwarePath() { + size_t partition_size = current_partition->size; + size_t offset = partition_size - 256; + char buf[256]; + if (esp_partition_read(current_partition, offset, buf, sizeof(buf)) != ESP_OK) { + serial_err("Failed to read firmware path from current partition"); + return ""; + } + if (static_cast(buf[0]) == 0xFF) { + serial_err("Firmware path is not set"); + return ""; + } + return String(buf); +} + MultiBoot multiboot; } // namespace lilka diff --git a/sdk/lib/lilka/src/lilka/multiboot.h b/sdk/lib/lilka/src/lilka/multiboot.h index 026346b6..c0cfcc45 100644 --- a/sdk/lib/lilka/src/lilka/multiboot.h +++ b/sdk/lib/lilka/src/lilka/multiboot.h @@ -63,6 +63,8 @@ class MultiBoot { /// \return <0 - у разі помилки. В разі успіху цей метод не повертається, оскільки пристрій перезавантажується. int finishAndReboot(); + String getFirmwarePath(); + private: String path; FILE* file; diff --git a/sdk/lib/lilka/src/lilka/ui.cpp b/sdk/lib/lilka/src/lilka/ui.cpp index c1997553..06d56118 100644 --- a/sdk/lib/lilka/src/lilka/ui.cpp +++ b/sdk/lib/lilka/src/lilka/ui.cpp @@ -17,8 +17,16 @@ namespace lilka { Menu::Menu(String title) { this->title = title; this->scroll = 0; - this->selectedIndex = -1; this->setCursor(0); + this->done = false; + this->iconImage = new Image(24, 24, display.color565(0, 0, 0), 12, 12); + this->iconCanvas = new Canvas(24, 24); + this->lastCursorMove = millis(); +} + +Menu::~Menu() { + delete iconImage; + delete iconCanvas; } void Menu::addItem(String title, const menu_icon_t* icon, uint16_t color) { @@ -48,15 +56,17 @@ void Menu::update() { } else { cursor--; } + lastCursorMove = millis(); } else if (state.down.justPressed) { // Move cursor down cursor++; if (cursor >= items.size()) { cursor = 0; } + lastCursorMove = millis(); } else if (state.a.justPressed) { // Execute selected function - this->selectedIndex = cursor; + done = true; } if (cursor < scroll) { @@ -108,9 +118,26 @@ void Menu::draw(Arduino_GFX* canvas) { // Cast icon to non-const uint16_t * to avoid warning // TODO: Had to do this because I switched to canvas (FreeRTOS experiment) // uint16_t *icon2 = (uint16_t *)icon; - canvas->draw16bitRGBBitmapWithTranColor( - 0, 80 + screenI * 24 - 20, const_cast(*icon), canvas->color565(0, 0, 0), 24, 24 - ); + // int16_t dx = 0; + // int16_t dy = 0; + // if (cursor == i) { + // dx = random(0, 3) - 1; + // dy = random(0, 3) - 1; + // } + if (cursor == i) { + memcpy(iconImage->pixels, *icon, sizeof(menu_icon_t)); + // Transform t = Transform().rotate(millis() * 30); + Transform t = Transform().rotate(sin((millis() - lastCursorMove) * PI / 1000) * 30); + iconCanvas->fillScreen(canvas->color565(0, 0, 0)); + iconCanvas->drawImageTransformed(iconImage, 12, 12, t); + canvas->draw16bitRGBBitmapWithTranColor( + 0, 80 + screenI * 24 - 20, iconCanvas->getFramebuffer(), canvas->color565(0, 0, 0), 24, 24 + ); + } else { + canvas->draw16bitRGBBitmapWithTranColor( + 0, 80 + screenI * 24 - 20, const_cast(*icon), canvas->color565(0, 0, 0), 24, 24 + ); + } } canvas->setCursor(32, 80 + screenI * 24); if (items[i].color && cursor != i) { @@ -133,16 +160,23 @@ void Menu::draw(Arduino_GFX* canvas) { } } -int16_t Menu::getSelectedIndex() { - int16_t index = selectedIndex; - selectedIndex = -1; - return index; +bool Menu::isFinished() { + if (done) { + done = false; + return true; + } + return false; +} + +int16_t Menu::getCursor() { + return cursor; } Alert::Alert(String title, String message) { this->title = title; this->message = message; this->done = false; + this->button = Button::COUNT; } void Alert::setTitle(String title) { @@ -155,7 +189,8 @@ void Alert::setMessage(String message) { void Alert::update() { State state = controller.getState(); - if (state.a.justPressed) { + if (state.a.justPressed || state.start.justPressed) { + button = state.a.justPressed ? Button::A : Button::START; done = true; } } @@ -187,7 +222,7 @@ void Alert::draw(Arduino_GFX* canvas) { canvas->println(message); } -bool Alert::isDone() { +bool Alert::isFinished() { if (done) { done = false; return true; @@ -195,6 +230,10 @@ bool Alert::isDone() { return false; } +Button Alert::getButton() { + return button; +} + ProgressDialog::ProgressDialog(String title, String message) { this->title = title; this->message = message; @@ -473,7 +512,7 @@ void InputDialog::draw(Arduino_GFX* canvas) { } } -bool InputDialog::isDone() { +bool InputDialog::isFinished() { if (done) { done = false; return true; diff --git a/sdk/lib/lilka/src/lilka/ui.h b/sdk/lib/lilka/src/lilka/ui.h index 7266c515..cf06408b 100644 --- a/sdk/lib/lilka/src/lilka/ui.h +++ b/sdk/lib/lilka/src/lilka/ui.h @@ -5,6 +5,7 @@ #include #include "Arduino_GFX.h" #include "display.h" +#include "controller.h" typedef uint16_t const menu_icon_t[576]; // 24x24px icon @@ -49,6 +50,7 @@ class Menu { /// /// @param title Заголовок меню. explicit Menu(String title); + ~Menu(); /// Додати пункт до меню. /// @param title Заголовок пункту. /// @param icon Іконка пункту (масив з ``uint16_t`` розміром 576 елементів, який представляє 24x24px зображення). За замовчуванням ``0`` (відсутня іконка). @@ -73,19 +75,22 @@ class Menu { /// // ... /// @endcode void draw(Arduino_GFX* canvas); - /// Отримати індекс обраного пункту меню. - /// - /// Якщо жоден пункт не обрано, повертається ``-1``. + /// Перевірити, чи обрано пункт меню. /// - /// Також ця функція очищує обраний пункт, тому щойно вона поверне індекс обраного пункту, вона почне повертати ``-1`` до тих пір, поки не буде обрано новий пункт. - int16_t getSelectedIndex(); + /// Якщо пункт обрано (користувач натиснув кнопку "A"), повертається ``true``, інакше ``false``. Після виклику цієї функції пункт перестає бути обраним. + bool isFinished(); + /// Отримати індекс обраного пункту меню. + int16_t getCursor(); private: int16_t cursor; int16_t scroll; String title; std::vector items; - int16_t selectedIndex; + bool done; + Image* iconImage; + Canvas* iconCanvas; + int64_t lastCursorMove; }; /// Клас для відображення сповіщення. @@ -104,7 +109,7 @@ class Menu { /// void loop() { /// lilka::Alert warning("Увага", "Повітряна тривога в москві, загроза балістичних ракет!"); /// warning.draw(&lilka::display); -/// while (!warning.isDone()) { +/// while (!warning.isFinished()) { /// warning.update(); /// } /// } @@ -141,13 +146,18 @@ class Alert { void draw(Arduino_GFX* canvas); /// Перевірити, чи користувач закрив сповіщення. /// - /// Якщо сповіщення закрито (користувач натиснув кнопку "A"), повертається ``true``, інакше ``false``. - bool isDone(); + /// Якщо сповіщення закрито (користувач натиснув кнопку "A" або "Start"), повертається ``true``, інакше ``false``. + bool isFinished(); + /// Отримати кнопку, якою користувач закрив сповіщення. + /// + /// Якщо сповіщення не закрито, результат буде невизначеним. Рекомендується використовувати цю функцію тільки після того, як ``isFinished()`` поверне ``true``. + Button getButton(); private: String title; String message; bool done; + Button button; }; /// Клас для відображення індикатора виконання. @@ -217,7 +227,7 @@ class InputDialog { void setValue(String value); void update(); void draw(Arduino_GFX* canvas); - bool isDone(); + bool isFinished(); String getValue(); private: diff --git a/sdk/lib/lilka/src/main.cpp b/sdk/lib/lilka/src/main.cpp index 5bbbe390..9d779365 100644 --- a/sdk/lib/lilka/src/main.cpp +++ b/sdk/lib/lilka/src/main.cpp @@ -8,10 +8,9 @@ void __attribute__((weak)) setup() { } void __attribute__((weak)) loop() { - // TODO: FreeRTOS experiment lilka::Alert alert("Лілка", "Ця прошивка не має\nжодного коду."); alert.draw(&lilka::display); - while (!alert.isDone()) { + while (!alert.isFinished()) { alert.update(); delay(100); }