diff --git a/firmware-demo/platformio.ini b/firmware-demo/platformio.ini index ee294385..31a49b42 100644 --- a/firmware-demo/platformio.ini +++ b/firmware-demo/platformio.ini @@ -30,5 +30,6 @@ board = lilka_v2 framework = arduino lib_deps = https://github.com/moononournation/arduino-nofrendo.git + mischief/lua@^0.1.1 ; Define LILKA_BREADBOARD to lower SPI speed to 40 MHz (instead of normal 80) build_flags = -D LILKA_BREADBOARD diff --git a/firmware-demo/sdcard/test.lua b/firmware-demo/sdcard/test.lua new file mode 100644 index 00000000..a6e97ea5 --- /dev/null +++ b/firmware-demo/sdcard/test.lua @@ -0,0 +1,26 @@ +local display = require("display") +local console = require("console") +local controller = require("controller") +local util = require("util") + +console.print("Printing stuff to console, yay!") + +display.fill_rect(50, 50, 200, 200, 0x159C) +display.draw_line(0, 0, 240, 280, 0xC951) + +display.set_cursor(64, 64) +display.print("Hello, World!") +display.set_cursor(64, 128) +display.print("Press A to quit.") + +local key = controller.get_state() +-- Loop while key.a.just_pressed is false +while not key.a.just_pressed do + key = controller.get_state() + local x1 = util.random(240) + local y1 = util.random(280) + local x2 = util.random(240) + local y2 = util.random(280) + local color = util.random(0xFFFF) + display.draw_line(x1, y1, x2, y2, color) +end diff --git a/firmware-demo/src/demo_wifi_scan.cpp b/firmware-demo/src/demo_wifi_scan.cpp new file mode 100644 index 00000000..187f8c42 --- /dev/null +++ b/firmware-demo/src/demo_wifi_scan.cpp @@ -0,0 +1,29 @@ +#include +#include + +void demo_scan_wifi() { + lilka::display.fillScreen(lilka::display.color565(0, 0, 0)); + lilka::display.setCursor(4, 150); + + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + + lilka::display.println("Скануємо мережі WiFi..."); + + int16_t count = WiFi.scanNetworks(false); + // while (count == WIFI_SCAN_RUNNING) { + // delay(250); + // lilka::display.print("."); + // count = WiFi.scanComplete(); + // } + String networks[count]; + for (int16_t i = 0; i < count; i++) { + networks[i] = WiFi.SSID(i); + } + + lilka::ui_menu("Мережі", networks, count, 0); + + // while (!lilka::controller.getState().a.justPressed) { + // delay(10); + // } +} diff --git a/firmware-demo/src/lua_runner.cpp b/firmware-demo/src/lua_runner.cpp new file mode 100644 index 00000000..3dcea193 --- /dev/null +++ b/firmware-demo/src/lua_runner.cpp @@ -0,0 +1,198 @@ +#include +#include "lauxlib.h" +#include + +int lualilka_print(lua_State* L) { + int n = lua_gettop(L); + for (int i = 1; i <= n; i++) { + if (lua_isstring(L, i)) { + printf("%s", lua_tostring(L, i)); + } else if (lua_isnumber(L, i)) { + printf("%g", lua_tonumber(L, i)); + } else { + printf("%s", lua_typename(L, lua_type(L, i))); + } + if (i < n) { + printf("\t"); + } + } + printf("\n"); + return 0; +} + +int lualilka_display_setCursor(lua_State* L) { + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + lilka::display.setCursor(x, y); + return 0; +} + +int lualilka_display_print(lua_State* L) { + int n = lua_gettop(L); + for (int i = 1; i <= n; i++) { + if (lua_isstring(L, i)) { + lilka::display.print(lua_tostring(L, i)); + } else if (lua_isnumber(L, i)) { + lilka::display.print(lua_tonumber(L, i)); + } else { + lilka::display.print(lua_typename(L, lua_type(L, i))); + } + } + return 0; +} + +int lualilka_display_drawLine(lua_State* L) { + int x0 = luaL_checkinteger(L, 1); + int y0 = luaL_checkinteger(L, 2); + int x1 = luaL_checkinteger(L, 3); + int y1 = luaL_checkinteger(L, 4); + uint16_t color = luaL_checkinteger(L, 5); + lilka::display.drawLine(x0, y0, x1, y1, color); + return 0; +} + +int lualilka_display_fillRect(lua_State* L) { + int x = luaL_checkinteger(L, 1); + int y = luaL_checkinteger(L, 2); + int w = luaL_checkinteger(L, 3); + int h = luaL_checkinteger(L, 4); + uint16_t color = luaL_checkinteger(L, 5); + lilka::display.fillRect(x, y, w, h, color); + return 0; +} + +static const luaL_Reg lualilka_display[] = { + {"set_cursor", lualilka_display_setCursor}, {"print", lualilka_display_print}, {"draw_line", lualilka_display_drawLine}, {"fill_rect", lualilka_display_fillRect}, {NULL, NULL}, +}; + +int luaopen_lilka_display(lua_State* L) { + luaL_newlib(L, lualilka_display); + return 1; +} + +int lualilka_console_print(lua_State* L) { + int n = lua_gettop(L); + for (int i = 1; i <= n; i++) { + if (lua_isstring(L, i)) { + Serial.printf("%s", lua_tostring(L, i)); + } else if (lua_isnumber(L, i)) { + Serial.printf("%g", lua_tonumber(L, i)); + } else { + Serial.printf("%s", lua_typename(L, lua_type(L, i))); + } + if (i < n) { + Serial.printf("\t"); + } + } + return 0; +} + +int lualilka_console_println(lua_State* L) { + lualilka_console_print(L); + Serial.printf("\n"); + return 0; +} + +static const luaL_Reg lualilka_console[] = { + {"print", lualilka_console_print}, + {"println", lualilka_console_println}, + {NULL, NULL}, +}; + +int luaopen_lilka_console(lua_State* L) { + luaL_newlib(L, lualilka_console); + return 1; +} + +int lualilka_controller_getState(lua_State* L) { + lilka::State state = lilka::controller.getState(); + lua_createtable(L, 0, 10); + // Push up, down, left, right, a, b, c, d, select, start to the table + const char* keys[] = {"up", "down", "left", "right", "a", "b", "c", "d", "select", "start"}; + for (int i = 0; i < 10; i++) { + lua_pushstring(L, keys[i]); + // Push value as table with keys {pressed, just_pressed, just_released} + lua_createtable(L, 0, 2); + lua_pushstring(L, "pressed"); + lua_pushboolean(L, state.buttons[i].pressed); + lua_settable(L, -3); + lua_pushstring(L, "just_pressed"); + lua_pushboolean(L, state.buttons[i].justPressed); + lua_settable(L, -3); + lua_pushstring(L, "just_released"); + lua_pushboolean(L, state.buttons[i].justReleased); + lua_settable(L, -3); + lua_settable(L, -3); + } + + // Return table + return 1; +} + +static const luaL_Reg lualilka_controller[] = { + {"get_state", lualilka_controller_getState}, + {NULL, NULL}, +}; + +int luaopen_lilka_controller(lua_State* L) { + luaL_newlib(L, lualilka_controller); + return 1; +} + +int lualilka_util_random(lua_State* L) { + // If no args - return random value from 0 to max int + // If 1 arg - return random value from 0 to arg + // If 2 args - return random value from arg1 to arg2 + + int n = lua_gettop(L); + + if (n == 0) { + lua_pushinteger(L, random()); + return 1; + } else if (n == 1) { + int max = luaL_checkinteger(L, 1); + lua_pushinteger(L, random(max)); + return 1; + } else if (n == 2) { + int min = luaL_checkinteger(L, 1); + int max = luaL_checkinteger(L, 2); + lua_pushinteger(L, random(min, max)); + return 1; + } else { + return luaL_error(L, "Invalid number of arguments"); + } + + return 0; +} + +static const luaL_Reg lualilka_util[] = { + {"random", lualilka_util_random}, + {NULL, NULL}, +}; + +int luaopen_lilka_util(lua_State* L) { + luaL_newlib(L, lualilka_util); + return 1; +} + +int lua_run(String path) { + lua_State* L = luaL_newstate(); + luaL_openlibs(L); + luaL_requiref(L, "display", luaopen_lilka_display, 1); + lua_pop(L, 1); + luaL_requiref(L, "console", luaopen_lilka_console, 1); + lua_pop(L, 1); + luaL_requiref(L, "controller", luaopen_lilka_controller, 1); + lua_pop(L, 1); + luaL_requiref(L, "util", luaopen_lilka_util, 1); + lua_pop(L, 1); + + int retCode = luaL_dofile(L, path.c_str()); + if (retCode) { + const char* err = lua_tostring(L, -1); + lilka::ui_alert("Lua", String("Помилка: ") + err); + } + + lua_close(L); + return retCode; +} diff --git a/firmware-demo/src/main.cpp b/firmware-demo/src/main.cpp index 65729fd2..a6fd4075 100644 --- a/firmware-demo/src/main.cpp +++ b/firmware-demo/src/main.cpp @@ -25,6 +25,7 @@ extern void demo_ball(); extern void demo_letris(); extern void demo_user_spi(); extern void demo_scan_i2c(); +extern int lua_run(String path); void setup() { lilka::begin(); @@ -49,6 +50,72 @@ void demos_menu() { } } +void select_file(String path) { + if (path.endsWith(".rom") || path.endsWith(".nes")) { + char *argv[1]; + char fullFilename[256]; + strcpy(fullFilename, path.c_str()); + argv[0] = fullFilename; + + TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCPU(0); + esp_task_wdt_delete(idle_0); + + Serial.print("NoFrendo start! Filename: "); + Serial.println(argv[0]); + nofrendo_main(1, argv); + Serial.println("NoFrendo end!\n"); + } else if (path.endsWith(".bin")) { + int error; + lilka::LoaderHandle *handle = lilka::loader.createHandle(path); + error = handle->start(); + if (error) { + lilka::ui_alert("Помилка", String("Етап: 1\nКод: ") + error); + return; + } + lilka::display.fillScreen(lilka::display.color565(0, 0, 0)); + lilka::display.setTextColor(lilka::display.color565(255, 255, 255), lilka::display.color565(0, 0, 0)); + lilka::display.setFont(u8g2_font_10x20_t_cyrillic); + lilka::display.setTextBound(16, 0, LILKA_DISPLAY_WIDTH - 16, LILKA_DISPLAY_HEIGHT); + while ((error = handle->process()) != 0) { + float progress = (float)handle->getBytesWritten() / handle->getBytesTotal(); + lilka::display.setCursor(16, LILKA_DISPLAY_HEIGHT / 2 - 10); + lilka::display.printf("Завантаження (%d%%)\n", (int)(progress * 100)); + lilka::display.println(path); + String buf = String(handle->getBytesWritten()) + " / " + handle->getBytesTotal(); + int16_t x, y; + uint16_t w, h; + lilka::display.getTextBounds(buf, lilka::display.getCursorX(), lilka::display.getCursorY(), &x, &y, &w, &h); + lilka::display.fillRect(x, y, w, h, lilka::display.color565(0, 0, 0)); + lilka::display.println(buf); + lilka::display.fillRect(16, LILKA_DISPLAY_HEIGHT / 2 + 40, LILKA_DISPLAY_WIDTH - 32, 5, lilka::display.color565(64, 64, 64)); + lilka::display.fillRect(16, LILKA_DISPLAY_HEIGHT / 2 + 40, (LILKA_DISPLAY_WIDTH - 32) * progress, 5, lilka::display.color565(255, 128, 0)); + } + if (error) { + lilka::ui_alert("Помилка", String("Етап: 2\nКод: ") + error); + return; + } + error = handle->finishAndReboot(); + if (error) { + lilka::ui_alert("Помилка", String("Етап: 3\nКод: ") + error); + return; + } + } else if (path.endsWith(".lua")) { + int retCode = lua_run(path); + lilka::ui_alert("Lua", String("Код завершення: ") + retCode); + } else { + // Get file size + FILE *file = fopen(path.c_str(), "r"); + if (!file) { + lilka::ui_alert("Помилка", "Не вдалося відкрити файл"); + return; + } + fseek(file, 0, SEEK_END); + long size = ftell(file); + fclose(file); + lilka::ui_alert(path, String("Розмір:\n") + size + " байт"); + } +} + void sd_browser_menu(String path) { if (!lilka::sdcard.available()) { lilka::ui_alert("Помилка", "SD-карта не знайдена"); @@ -80,56 +147,8 @@ void sd_browser_menu(String path) { } if (entries[cursor].type == lilka::EntryType::ENT_DIRECTORY) { sd_browser_menu(path + entries[cursor].name + "/"); - } else if (entries[cursor].name.endsWith(".rom") || entries[cursor].name.endsWith(".nes")) { - char *argv[1]; - char fullFilename[256]; - strcpy(fullFilename, lilka::sdcard.abspath(entries[cursor].name).c_str()); - argv[0] = fullFilename; - - TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCPU(0); - esp_task_wdt_delete(idle_0); - - Serial.print("NoFrendo start! Filename: "); - Serial.println(argv[0]); - nofrendo_main(1, argv); - Serial.println("NoFrendo end!\n"); - } else if (entries[cursor].name.endsWith(".bin")) { - int error; - lilka::LoaderHandle *handle = lilka::loader.createHandle(path + entries[cursor].name); - error = handle->start(); - if (error) { - lilka::ui_alert("Помилка", String("Етап: 1\nКод: ") + error); - continue; - } - lilka::display.fillScreen(lilka::display.color565(0, 0, 0)); - lilka::display.setTextColor(lilka::display.color565(255, 255, 255), lilka::display.color565(0, 0, 0)); - lilka::display.setFont(u8g2_font_10x20_t_cyrillic); - lilka::display.setTextBound(16, 0, LILKA_DISPLAY_WIDTH - 16, LILKA_DISPLAY_HEIGHT); - while ((error = handle->process()) != 0) { - float progress = (float)handle->getBytesWritten() / handle->getBytesTotal(); - lilka::display.setCursor(16, LILKA_DISPLAY_HEIGHT / 2 - 10); - lilka::display.printf("Завантаження (%d%%)\n", (int)(progress * 100)); - lilka::display.println(entries[cursor].name); - String buf = String(handle->getBytesWritten()) + " / " + handle->getBytesTotal(); - int16_t x, y; - uint16_t w, h; - lilka::display.getTextBounds(buf, lilka::display.getCursorX(), lilka::display.getCursorY(), &x, &y, &w, &h); - lilka::display.fillRect(x, y, w, h, lilka::display.color565(0, 0, 0)); - lilka::display.println(buf); - lilka::display.fillRect(16, LILKA_DISPLAY_HEIGHT / 2 + 40, LILKA_DISPLAY_WIDTH - 32, 5, lilka::display.color565(64, 64, 64)); - lilka::display.fillRect(16, LILKA_DISPLAY_HEIGHT / 2 + 40, (LILKA_DISPLAY_WIDTH - 32) * progress, 5, lilka::display.color565(255, 128, 0)); - } - if (error) { - lilka::ui_alert("Помилка", String("Етап: 2\nКод: ") + error); - continue; - } - error = handle->finishAndReboot(); - if (error) { - lilka::ui_alert("Помилка", String("Етап: 3\nКод: ") + error); - continue; - } } else { - lilka::ui_alert(entries[cursor].name, "Розмір:\n" + String(entries[cursor].size) + " байт"); + select_file(lilka::sdcard.abspath(path + entries[cursor].name)); } } } @@ -161,20 +180,7 @@ void spiffs_browser_menu() { if (cursor == numEntries - 1) { return; } - if (filenames[cursor].endsWith(".rom") || filenames[cursor].endsWith(".nes")) { - char *argv[1]; - char fullFilename[256]; - strcpy(fullFilename, lilka::filesystem.abspath(filenames[cursor]).c_str()); - argv[0] = fullFilename; - - TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCPU(0); - esp_task_wdt_delete(idle_0); - - Serial.print("NoFrendo start! Filename: "); - Serial.println(argv[0]); - nofrendo_main(1, argv); - Serial.println("NoFrendo end!\n"); - } + select_file(lilka::filesystem.abspath(filenames[cursor])); } } diff --git a/lib/lilka/src/lilka/loader.cpp b/lib/lilka/src/lilka/loader.cpp index e1f79274..5c8de0d3 100644 --- a/lib/lilka/src/lilka/loader.cpp +++ b/lib/lilka/src/lilka/loader.cpp @@ -85,14 +85,17 @@ int LoaderHandle::start() { // String abspath = sdcard.abspath(path); // TODO: Use sdcard instead of SD - file = SD.open(path, FILE_READ); - if (!file) { + file = fopen(path.c_str(), "r"); + if (file == NULL) { serial_err("Failed to open file: %s", path.c_str()); return -2; } bytesWritten = 0; - bytesTotal = file.size(); + // Get file size + fseek(file, 0, SEEK_END); + bytesTotal = ftell(file); + fseek(file, 0, SEEK_SET); const esp_partition_t *current_partition = esp_ota_get_running_partition(); serial_log("Current partition: %s, type: %d, subtype: %d, size: %d", current_partition->label, current_partition->type, current_partition->subtype, current_partition->size); @@ -103,7 +106,7 @@ int LoaderHandle::start() { return -3; } - esp_err_t err = esp_ota_begin(ota_partition, file.size(), &ota_handle); + 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; @@ -118,9 +121,10 @@ int LoaderHandle::process() { // Записуємо 32 КБ. for (int i = 0; i < 32; i++) { - int len = file.readBytes(buf, sizeof(buf)); + // Read 1024 bytes + int len = fread(buf, 1, sizeof(buf), file); if (len == 0) { - file.close(); + fclose(file); return 0; } diff --git a/lib/lilka/src/lilka/loader.h b/lib/lilka/src/lilka/loader.h index d9b41775..bab8deb5 100644 --- a/lib/lilka/src/lilka/loader.h +++ b/lib/lilka/src/lilka/loader.h @@ -31,7 +31,7 @@ class LoaderHandle { private: String path; - File file; + FILE *file; esp_ota_handle_t ota_handle; const esp_partition_t *ota_partition; int bytesWritten;