У другій частині гайду про Лілку досліджуємо, як застосовувати DIY-консоль у реальному житті: ігри, сенсори, STEM-навчання, автоматика. Приклади проєктів та ресурси для розвитку.
Лілка не позиціонується як ігрова консоль, проте це не заважає писати та грати ігри на ній.
NES — під Keira портований проєкт Nofrendo, що дозволяє запускати NES (денді) ігри у форматах .rom або .nes з SD-карти.
Doom — згідно з популярною фразою «If it has a processor, it can run Doom», Лілка також може запускати Doom. Для цього потрібно скомпілювати прошивку в каталозі firmware/doom, використовуючи shareware-версію Doom.
Тамагочі — гра-симулятор догляду за віртуальним улюбленцем. Гравець має годувати, розважати, лікувати та підтримувати його щастя й здоров’я, щоб улюбленець був задоволений.
Стугна — стратегічний 2D-слешер про російсько-українську війну (відео). Головний персонаж — дівчина з прізвищем Стугна, що попадає в епіцентр бойових дій без навчання, але швидко вчиться та перетворюється на машину смерті. Розробку цієї гри наразі призупинено.
Астероїди — клон Asteroids 1979. У цій аркадній грі гравець керує космічним кораблем, який повинен знищувати астероїди та уникати зіткнень з ними.
Лілтрекер — відео. Триканальний трекер для Лілки. Підтримує різні ефекти, такі як тремоло, вібрато, арпеджіо. Також можна зберігати написані треки у форматі .lt та .waw на SD-карті. Звучання Лілтрекера нагадує суміш комодорівського SID та нінтендівського 2a03. ESP32 не має вбудованих звукових чіпів, увесь звук синтезується програмно на льоту. Андерсон написав декілька демонстраційних треків.
Modplayer — простий програвач MOD-файлів на базі бібліотеки ESP8266Audio. Не плутайте з попередньою програмою Лілтрекером. Mod-файли можна завантажити тут.
Погода — при підключенні до інтернету показує прогноз погоди за вказаними координатами.
BLE Геймпад — дозволяє використовувати Лілку у якості бездротового геймпада (джойстика).
GPS Tracker — модуль для відображення мапи з GPS-координатами.
Li’l Video — це конвертер відео для Lilka, що перетворює відеофайли у формат, сумісний із пристроєм. Він зменшує роздільну здатність і оптимізує дані для відтворення на Lilka, забезпечуючи ефективне кодування під обмеження консолі. Приклад роботи відеоплеєра. Скріншот — при затиснені клавіші select + start робить знімок з екрана.
FTP-сервер — запускає FTP-сервер, доступний з локальної мережі. дозволяє бездротово надсилати файли на Лілку. примітка: наразі FTP сервер має особливість при передачі файлів в обидві сторони. Для передачі потрібно вимкнути та увімкнути FTP сервіс в меню Kiera, або повторно натиснути на кнопку швидке з’єднання у FileZilla.
Pastebin — завантаження файлів з сайту pastebin.
Runner — невеличка гра-раннер, де персонажу потрібно ухилятися від перешкод або виконувати інші завдання.
Live Lua дозволяє запускати Lua-код на Лілці без необхідності зберігати файли на SD-картці. Підключивши Лілку до комп’ютера через USB, ви можете тестувати програму без зайвих кроків з копіюванням файлів. Однак, якщо програма використовує додаткові ресурси (зображення, звуки), їх потрібно завантажити на SD-картку вручну. Для використання Live Lua потрібно встановити розширення Serial Monitor для Visual Studio Code.
Lua REPL (Read-Eval-Print Loop) — це інтерактивне середовище, яке дозволяє вам вводити та виконувати Lua-код на Лілці прямо з комп’ютера через USB-кабель, по одному рядку за раз. Це зручно для тестування функцій, вивчення API або відлагодження.
Щоб використовувати REPL, відкрийте меню «Розробка» -> «Lua REPL» на Лілці, потім у Visual Studio Code відкрийте «Serial Monitor», виберіть потрібний COM-порт і натисніть «Start Monitoring». Після цього ви можете вводити код, і результат з’явиться в консолі.
Letris — клон класичного тетріс.
Клавіатура — дозволяє відкрити екрану клавіатуру та набирати текст.
Тест SPI — демо-програма для роботи зі SPI на Lilka. Вона ініціалізує SPI-з’єднання, надсилає та приймає дані, демонструючи обмін із зовнішніми пристроями. Це корисно для тестування периферії та налагодження взаємодії через SPI.
I2C сканер — це демо-програма для сканування пристроїв на шині I2C. Вона ініціалізує з’єднання, перебирає всі можливі адреси та надсилає запити, перевіряючи, які пристрої відповідають. Виявлені адреси виводяться у зручному форматі, що дозволяє швидко визначити підключені I2C-девайси.
Лінії — демо-програма для виведення ліній на екрані Lilka. Вона малює лінії різної довжини, напрямку та кольору, демонструючи можливості графічного рендерингу. Це дозволяє перевірити коректність роботи відображення та протестувати продуктивність малювання графіки. Відео.
М’ячик — програма анімує м’ячик, що падає під дією гравітації та відскакує від країв екрана. Користувач може керувати її рухом стрілками, а кнопка «A» завершує роботу. На екрані відображається FPS, анімація оновлюється в реальному часі.
ЛілКаталог — каталог Lua програм, скриптів та прошивок для Лілки. Має зручний графічний інтерфейс та дозволяє в один клік заватнтажувати ігри програми та прошивки. Якщо ви створили гру ви можете звернутись до автора, або в загальну гілку Discord спільноти для включення вашої програми в каталог.
Щоб зрозуміти, як написати програму, пропоную поглянути на приклад наявних програм. До прикладу, Сat — проста програма з котиком у стилі Bongo Cat, який б’є по столу, коли натискаєш кнопочки. Відео гри можна переглянути тут.
Спочатку ми завантажуємо чотири зображення котика. У цьому випадку зображення на весь екран. Воно має розмір 280×240. Підтримуються зображення у форматі .bmp, а з недавніх пір ще й .png. Також ми придумуємо змінні no, left, right, both і задаємо цим змінним зображення котика без лапок на столі, з лівою лапкою, з правою лапкою та з обома.
state = controller.get_state() записує значення натиснених кнопок.
function lilka.update(delta) — функція, що покадрово оновлює стан гри; delta — це час у секундах з моменту останнього виклику, що дозволяє створювати рухи й анімації, незалежні від швидкості програми. За ідеальних обставин delta дорівнює приблизно 0.0333 секунди.
Після знову викликається state = controller.get_state(), щоб дізнатись, чи натиснена кнопка перед кожним відмальованим кадром гри.
Якщо була натиснута кнопка вліво (буква D), або кнопка вправо (кнопка B), відтворити писк п’єзо динаміка (англ. buzzer).
if state.b.just_pressed or state.d.just_pressed then buzzer.play(40, 100) end
При натиснені кнопки вгору (A) програма завершує роботу:
-- Завершуємо програму при натисканні кнопки "A" if state.a.pressed then util.exit() end end
Функція lilka.draw() малює зображення на екрані залежно від стану кнопок:
Спочатку відображає базове зображення no.
Якщо натиснута кнопка D, малює зображення left.
Якщо натиснута кнопка B, малює зображення right.
Якщо одночасно натиснуті B і D, малює зображення both.
Якщо натиснута кнопка C, відтворює звук, коротко показує both, а потім повертається до no.
-- Завантажуємо зображення
no = resources.load_image("no.bmp")
left = resources.load_image("left.bmp")
right = resources.load_image("right.bmp")
both = resources.load_image("both.bmp")
-- Створюємо змінну в яку буде записуватись стан кнопок
state = controller.get_state()
function lilka.update(delta)
-- Оновлюємо змінну з станом кнопок
state = controller.get_state()
-- Якщо була натиснута кнопка - відтворюємо звук
if state.b.just_pressed or state.d.just_pressed then
buzzer.play(40, 100)
end
-- Завершуємо програму при натисканні кнопки "A"
if state.a.pressed then
util.exit()
end
end
function lilka.draw()
-- Перевіряємо стан кнопок і відмальовуємо відповідне зображення
display.draw_image(no, 0, 0)
if state.d.pressed then
display.draw_image(left, 0, 0)
end
if state.b.pressed then
display.draw_image(right, 0, 0)
end
if state.b.pressed and state.d.pressed then
display.draw_image(both, 0, 0)
end
if state.c.pressed then
util.sleep(0.1)
display.draw_image(both, 0, 0)
buzzer.play(40, 100)
display.queue_draw()
util.sleep(0.1)
display.draw_image(no, 0, 0)
end
end
Розглянемо приклад ще однієї простої програми — гру Кубики. Гра розрахована на двох людей без участі комп’ютера. Також цікаво, що гра була написана за допомогою штучного інтелекту claude.ai на базі гри Астероїди.
Розберемо, як працює lilkacubes.lua.
Ігровий процес:
Старт. Гра починається з екрану привітання. Гравець натискає A, щоб перейти до гри.
Кидок кубиків. Гравці кидають кубики одночасно, натиснувши A. Кубики анімуються (змінюють значення) протягом короткого часу.
Результат. Після завершення кидка визначається переможець:
Якщо значення кубиків однакові, це нічия.
Якщо значення різні, переможець визначається за функцією get_winner.
Новий раунд: гравці можуть натиснути A для повторного раунду або B для виходу з гри.
Спочатку оголошуються кольори у форматі RGB, які будуть використані для відображення кубиків та фону.
ROLL_SOUND — звук кидання кубиків — це набір MIDI-нот, де кожна нота задається у вигляді частоти в Герцах і тривалості. Інакше кажучи, це проста інструкція для п’єзо-динаміка (англ. buzzer), як відтворити мелодію. Аналогічно працює звук вибору — SELECT_SOUND.
Клас Dice моделює кубик із певними характеристиками, як-от координати, розмір, колір, поточне значення та статус (кидається чи ні). Кубик також може «пам’ятати», чи є він переможцем.
Метод Dice:new(o) створює новий об’єкт кубика, зберігаючи передані параметри або використовуючи значення за замовчуванням.
Метод Dice:roll(). Цей метод запускає «кидання» кубика. Якщо кубик не кидається, він змінює свій статус на «кидається» (is_rolling = true), зберігає час початку кидка (roll_start_time) і скидає статус переможця (is_winner = false).
Метод Dice:update() оновлює стан кубика: якщо триває кидок, встановлює випадкове значення від 1 до 6; якщо час закінчився — фіксує результат і зупиняє кидок.
Метод Dice:draw() відповідає за візуалізацію кубика. Якщо кубик виграв, додається виділення. На екран виводиться зображення, що відповідає його значенню.
Логіка гри. У грі є два стани: привітання (HELLO) і сам процес гри (IN_GAME).
Функція setup_game(). Ця функція налаштовує гру. Завантажуються необхідні зображення, а також створюються два кубики — по одному для кожного гравця. Вони розташовані симетрично на екрані.
Функція lilka.update(delta). Це головна функція, яка виконується під час роботи гри. Її завдання залежить від поточного стану гри:
У стані HELLO вона чекає, поки гравець натисне кнопку A, щоб почати гру.
У стані IN_GAME вона оновлює стан кубиків, перевіряє, чи натиснув гравець A для нового кидка або B для виходу.
Коли обидва кубики закінчили кидатися, визначається переможець за допомогою функції get_winner. Переможцю присвоюється статус is_winner.
Функція lilka.draw(). Ця функція малює все, що відбувається в грі. У стані HELLO вона відображає екран привітання. У стані IN_GAME вона малює кубики, імена гравців, а також результат раунду після завершення кидка. Додатково виводяться інструкції з кнопками.
--[[
Гра "Кубики"
Гра з вибором кількості кубиків для консолі lilka.dev
]]
WHITE = display.color565(255, 255, 255)
BLACK = display.color565(0, 0, 0)
YELLOW = display.color565(255, 255, 0)
RED = display.color565(255, 0, 0)
-------------------------------------------------------------------------------
-- Завантаження ресурсів
-------------------------------------------------------------------------------
-- Звук кидання кубиків
ROLL_SOUND = {
{440, 8},
{523, 8},
{659, 8},
{784, 8},
{880, 8},
}
-- Звук вибору
SELECT_SOUND = {
{660, 8},
{880, 8},
}
-------------------------------------------------------------------------------
-- Клас кубика
-------------------------------------------------------------------------------
Dice = {
x = 0,
y = 0,
size = 80,
color = WHITE,
current_value = 1,
is_rolling = false,
roll_start_time = 0,
roll_duration = 1,
}
function Dice:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Dice:roll()
if not self.is_rolling then
self.is_rolling = true
self.roll_start_time = util.time()
end
end
function Dice:update()
if self.is_rolling then
local time_elapsed = util.time() - self.roll_start_time
if time_elapsed < self.roll_duration then
-- Під час анімації швидко змінюємо значення
self.current_value = math.floor(math.random() * 6) + 1
else
-- Зупиняємо анімацію і встановлюємо фінальне значення
self.current_value = math.floor(math.random() * 6) + 1
self.is_rolling = false
end
end
end
function Dice:draw()
-- Малюємо квадрат кубика
display.fill_rect(self.x - self.size/2, self.y - self.size/2, self.size, self.size, self.color)
-- Малюємо крапки відповідно до значення
local dot_size = 8
local padding = 18
if self.current_value == 1 then
-- Центральна крапка
display.fill_circle(self.x, self.y, dot_size, BLACK)
elseif self.current_value == 2 then
-- Дві крапки по діагоналі
display.fill_circle(self.x - padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y + padding, dot_size, BLACK)
elseif self.current_value == 3 then
-- Три крапки по діагоналі
display.fill_circle(self.x - padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x, self.y, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y + padding, dot_size, BLACK)
elseif self.current_value == 4 then
-- Чотири крапки по кутах
display.fill_circle(self.x - padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x - padding, self.y + padding, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y + padding, dot_size, BLACK)
elseif self.current_value == 5 then
-- П'ять крапок
display.fill_circle(self.x - padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x, self.y, dot_size, BLACK)
display.fill_circle(self.x - padding, self.y + padding, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y + padding, dot_size, BLACK)
elseif self.current_value == 6 then
-- Шість крапок
display.fill_circle(self.x - padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y - padding, dot_size, BLACK)
display.fill_circle(self.x - padding, self.y, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y, dot_size, BLACK)
display.fill_circle(self.x - padding, self.y + padding, dot_size, BLACK)
display.fill_circle(self.x + padding, self.y + padding, dot_size, BLACK)
end
end
-------------------------------------------------------------------------------
-- Стани гри
-------------------------------------------------------------------------------
STATES = {
HELLO = 0, -- Початковий екран
SELECT = 1, -- Вибір кількості кубиків
IN_GAME = 2, -- Гра
}
-------------------------------------------------------------------------------
-- Змінні стану гри
-------------------------------------------------------------------------------
local game_state = STATES.HELLO
local selected_dice_count = 1 -- Вибрана кількість кубиків
local dice1 = nil
local dice2 = nil
-------------------------------------------------------------------------------
-- Головні цикли гри
-------------------------------------------------------------------------------
function setup_dice(count)
if count == 1 then
dice1 = Dice:new({
x = display.width/2,
y = display.height/2 - 50,
color = WHITE
})
dice2 = nil
else
dice1 = Dice:new({
x = display.width/2 - 70,
y = display.height/2 - 50,
color = WHITE
})
dice2 = Dice:new({
x = display.width/2 + 70,
y = display.height/2 - 50,
color = YELLOW
})
end
end
function lilka.update(delta)
local state = controller.get_state()
if game_state == STATES.HELLO then
if state.start.just_pressed then
game_state = STATES.SELECT
end
elseif game_state == STATES.SELECT then
-- Вибір кількості кубиків
if state.left.just_pressed or state.right.just_pressed then
selected_dice_count = selected_dice_count == 1 and 2 or 1
buzzer.play_melody(SELECT_SOUND, 400)
end
if state.start.just_pressed then
setup_dice(selected_dice_count)
game_state = STATES.IN_GAME
end
else
-- Оновлюємо стан кубиків
dice1:update()
if dice2 then
dice2:update()
end
-- Якщо натиснута кнопка A і кубики не крутяться
if state.a.just_pressed then
local can_roll = not dice1.is_rolling
if dice2 then
can_roll = can_roll and not dice2.is_rolling
end
if can_roll then
dice1:roll()
if dice2 then
dice2:roll()
end
buzzer.play_melody(ROLL_SOUND, 400)
end
end
-- Повернення до вибору кількості кубиків
if state.b.just_pressed then
game_state = STATES.SELECT
end
-- Вихід з гри
if state.start.just_pressed then
util.exit()
end
end
end
function lilka.draw()
if game_state == STATES.HELLO then
display.fill_screen(BLACK)
display.set_cursor(display.width/2 - 50, display.height/2 - 20)
display.print("ГРА КУБИКИ")
display.set_cursor(display.width/2 - 80, display.height/2 + 20)
display.print("Натисніть START")
elseif game_state == STATES.SELECT then
display.fill_screen(BLACK)
display.set_cursor(display.width/2 - 100, 30)
display.print("Оберіть кількість")
display.set_cursor(display.width/2 - 45, 50)
display.print("кубиків:")
-- Малюємо варіанти вибору
local y = display.height/2
-- Перший кубик
if selected_dice_count == 1 then
display.fill_rect(display.width/2 - 80, y - 15, 30, 30, RED)
else
display.fill_rect(display.width/2 - 80, y - 15, 30, 30, WHITE)
end
display.set_cursor(display.width/2 - 70, y + 30)
display.print("1")
-- Два кубики
if selected_dice_count == 2 then
display.fill_rect(display.width/2 + 50, y - 15, 30, 30, RED)
else
display.fill_rect(display.width/2 + 50, y - 15, 30, 30, WHITE)
end
display.set_cursor(display.width/2 + 60, y + 30)
display.print("2")
display.set_cursor(10, display.height - 60)
display.print("Ліво/Право - для вибору")
display.set_cursor(10, display.height - 40)
display.print("START - для підтвердження")
else
display.fill_screen(BLACK)
-- Малюємо кубики
dice1:draw()
if dice2 then
dice2:draw()
end
local instructions_y = display.height - 80
display.set_cursor(10, instructions_y)
if not dice1.is_rolling and (not dice2 or not dice2.is_rolling) then
local sum = dice1.current_value
if dice2 then
sum = sum + dice2.current_value
end
display.print("Сума: " .. sum)
else
display.print("Сума: --")
end
display.set_cursor(10, instructions_y + 20)
display.print("A - кинути кубики")
display.set_cursor(10, instructions_y + 40)
display.print("B - змінити кількість")
display.set_cursor(10, instructions_y + 60)
display.print("START - вихід")
end
end
Розглянемо, як працює Pastebin — це невелика програма для завантаження текстових файлів з вебсервісу Pastebin. Вона дозволяє користувачам вводити код посилання на файл, вказувати ім’я файлу, а потім завантажувати й зберігати вказаний текстовий файл на локальний носій.
Спочатку у програмі підключаються три бібліотеки.
#include «pastebinApp.h» — це окремий файл, інструкція для програми, яка описує її структуру. Детальніше тут.
#include <HTTPClient.h> — бібліотека для роботи з інтернетом. Вона дозволяє відправляти запити (GET, POST) і отримувати відповіді від будь-яких вебсайтів або серверів.
#include <lilka/config.h> — налаштування платформи Lilka для взаємодії з кнопками, екраном і файлами.
Коли програма створюється, викликається спеціальний блок коду, який називається конструктором. Він відповідає за початкове налаштування програми, задає її основні параметри.
pastebinApp::pastebinApp() : App("pastebin loader") {
}
Тут ми бачимо конструктор для нашої програми pastebinApp. Він каже: «Я створюю програму з назвою pastebin loader».
Уявіть, що це як наклеїти етикетку на коробку, де написано: «Це програма для завантаження файлів із Pastebin». Ця назва допоможе програмі зрозуміти, хто вона, а нам — легше її ідентифікувати, якщо буде багато таких коробок-програм.
Функція run() є точкою входу для програми. Коли програма починає виконання, система автоматично викликає цей метод. Це правило закладено в базовому класі App, який визначає загальну структуру роботи застосунків.
У функції run() є лише один рядок — uiLoop();. Ця команда повідомляє програмі: «Запусти меню і покажи його користувачеві».
void pastebinApp::run() {
uiLoop();
}
WiFiClientSecure client;
HTTPClient http;
String link_code = "";
String filename = "";
String path_pastebin_folder = "/pastebin";
String pastebin_url = "https://pastebin.com/raw/";
У цьому фрагменті коду ми оголошуємо змінні, щоб підготувати програму до роботи з інтернетом, файловою системою та обробкою даних. Кожна змінна має свою роль і допомагає програмі правильно виконувати свою роботу.
Перша змінна — WiFiClientSecure client. Тут ми створюємо «клієнта», який буде працювати з інтернетом. Оскільки програма має використовувати захищений доступ до сайтів (https), ми створюємо саме безпечного клієнта. Це дозволяє програмі безпечно з’єднуватися з сайтами, такими як Pastebin.
Друга змінна — HTTPClient http. Це так званий «посередник». Він допомагає програмі відправляти запити до Pastebin, щоб отримати необхідні файли. Уявіть це як людину, яка телефонує до магазину і запитує, чи є у них потрібний товар. Ось цей «посередник» і робить запити, щоб програма могла отримати те, що їй потрібно.
Далі йдуть змінні String link_code та String filename. Це просто порожні рядки або «коробки», де програма зберігатиме інформацію. У link_code ми будемо зберігати код посилання на файл, а у filename — ім’я файлу, яке ми хочемо завантажити.
Змінна String path_pastebin_folder вказує на папку, куди програма збереже файли, що будуть завантажені з Pastebin. Це як адреса на комп’ютері, де будуть зберігатися всі файли, щоб їх можна було легко знайти й відкрити.
І остання змінна — String pastebin_url. Тут зберігається адреса сайту Pastebin, куди програма буде надсилати запити для отримання файлів. Це схоже на те, коли ви вводите адресу вебсайту в браузері, щоб перейти на потрібну сторінку.
#include "pastebinApp.h"
#include <HTTPClient.h>
#include <lilka/config.h>
pastebinApp::pastebinApp() : App("pastebin loader") {
}
void pastebinApp::run() {
uiLoop();
}
void pastebinApp::uiLoop() {
WiFiClientSecure client;
HTTPClient http;
String link_code = "";
String filename = "";
String path_pastebin_folder = "/pastebin";
String pastebin_url = "https://pastebin.com/raw/";
while (1) {
lilka::Menu settingsMenu("Pastebin");
settingsMenu.addActivationButton(lilka::Button::B);
settingsMenu.addItem("Код", 0, 0, link_code);
settingsMenu.addItem("Назва", 0, 0, filename);
settingsMenu.addItem("Завантажити", 0, 0, "");
while (!settingsMenu.isFinished()) {
settingsMenu.update();
settingsMenu.draw(canvas);
queueDraw();
}
if (settingsMenu.getButton() == lilka::Button::B) {
} else {
if (settingsMenu.getCursor() == 0) {
lilka::InputDialog inputDialog(String("Введіть код"));
inputDialog.setValue(link_code);
while (!inputDialog.isFinished()) {
inputDialog.update();
inputDialog.draw(canvas);
queueDraw();
}
link_code = inputDialog.getValue();
} else if (settingsMenu.getCursor() == 1) {
lilka::InputDialog inputDialog(String("Введіть назву"));
inputDialog.setValue(filename);
while (!inputDialog.isFinished()) {
inputDialog.update();
inputDialog.draw(canvas);
queueDraw();
}
filename = inputDialog.getValue();
} else if (settingsMenu.getCursor() == 2) {
if (filename.length() == 0) {
filename = link_code;
continue;
}
FRESULT res = f_stat(path_pastebin_folder.c_str(), nullptr);
if (res == FR_NO_FILE) {
res = f_mkdir(path_pastebin_folder.c_str());
if (res != FR_OK) {
lilka::Alert alert("pastebin", "Помилка створення директорії");
alert.draw(canvas);
queueDraw();
while (!alert.isFinished()) {
alert.update();
}
printf("Помилка створення директорії %d\n", res);
}
} else if (res != FR_OK) {
lilka::Alert alert("pastebin", "Помилка створення директорії");
alert.draw(canvas);
queueDraw();
while (!alert.isFinished()) {
alert.update();
}
} else {
String url = pastebin_url + link_code;
String fullPath = path_pastebin_folder + "/" + filename;
client.setInsecure();
http.begin(client, url);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
// Open file for writing
FILE* file = fopen((lilka::fileutils.getSDRoot() + fullPath).c_str(), FILE_WRITE);
if (!file) {
lilka::Alert alert("pastebin", "Помилка відкривання файлу");
alert.draw(canvas);
queueDraw();
while (!alert.isFinished()) {
alert.update();
}
printf("Помилка відкривання файлу");
break;
}
fprintf(file, "%s", http.getString().c_str());
fclose(file);
delay(10);
lilka::Alert alert("pastebin", "Файл завантажено, та збережено");
alert.draw(canvas);
queueDraw();
while (!alert.isFinished()) {
alert.update();
}
printf("Файл завантажено, та збережено");
break;
} else {
lilka::Alert alert("pastebin", "HTTP GET failed, error: " + http.errorToString(httpCode));
alert.draw(canvas);
queueDraw();
while (!alert.isFinished()) {
alert.update();
}
printf("HTTP GET failed, error: %s\n", http.errorToString(httpCode).c_str());
}
}
}
}
}
}
При створені власних програм можна скористатись допомогою спільноти у гілці програмування. Також я використовував ChatGPT та Claude. Але перед тим, як писати якусь програму, я згодовую їм подібну існуючу програму або приклад програм з документації. Написаний код можна завантажити на Github, а посиланням поділитись в гілці Користувацькі програми та скрипти.
Існує дуже багато цікавих проєктів, які можна реалізувати на Лілці. Приклади, що я опишу — не дорожня карта проєкту Лілка, а просто мої власні побажання. Можливо, вони надихнуть вас на якийсь власний порт чи програму.
Допомогти з портуванням Python на Лілку. MicroPython — це легковагова реалізація Python, оптимізована для мікроконтролерів. Вона дозволяє писати зрозумілий і гнучкий код для вбудованих систем, підтримує роботу з периферією (GPIO, I2C, SPI, UART), файлову систему та багатозадачність.
Проєкт наразі потребує допомоги, оскільки портуванням займається лише одна людина. Якщо ви хочете долучитися, напишіть у відповідну гілку.
Допомогти з розробкою та розумінням принципів роботи .mjs-скриптів. На цей час документація проєкту не має достатньо інформації.
Магазин застосунків. Оскільки .lua та .mjs-скрипти можна запускати відразу з файлів, тоді можна створити магазин застосунків, що дозволить завантажувати такі скрипти. Це можна зробити з нуля або надихнутись існуючою програмою для Лілки Pastebin.
Pico8 — це сучасний ігровий рушій, що імітує обмеження ігрових систем 80-х, щоб заохотити творчість та винахідливість у виробництві ігор, не перевантажуючись багатьма можливостями сучасних інструментів та машин.
Такі обмеження також надають іграм PICO-8 особливий вигляд і відчуття. Існує порт Pico-8 на ESP32. Проте для його портування потрібно подолати певні технічні труднощі.
Micro Hydra — це простий лаунчер застосунків на базі MicroPython з деякими функціями, схожими на ОС. Основною функцією MicroHydra є забезпечення інтерфейсу для легкого перемикання між застосунками MicroPython. Гілка, присвячена MicroHydra.
Wi-Fi Duck — це пристрій, який працює як бездротова клавіатура. Він створює Wi-Fi точку доступу, через яку можна віддалено вводити текст або команди на підключеному пристрої.
Модуль та програма для зчитування та записування та емулювання RFID-ключів. Щоб можна було відкрити двері підʼїзду Лілкою. Щось на кшталт пристрою Chameleon.
Я цікавлюсь розумним будинком. І можна портувати MQTT-клієнт, сервер. Або програму, схожу на homeThings, або ESPWA. Універсальний пульт керування інфрачервоними пристроями (кондиціонером, телевізором, вентилятором, зволожувачем тощо). Пристрій для пошуку IBeacon, запуск україномовного голосового асистенту Voice Assistant.
Meshtastic — це проєкт для створення бездротової мережі без інтернету з використанням LoRa. Лілку можна використовувати як ноду для обміну повідомленнями на великі відстані. До речі, в Ураїні є окрема спільнота, присвячена цьому проєкту. Також можна приєднатись до обговорення Meshtastic на Лілці в окремій гілці спільноти.
Можливості Лілки величезні — вона відкриває простір для технічної творчості, дозволяючи кожному створювати щось унікальне. Вона може бути як простою, так і складною, залежно від ваших ідей та бажань. Лілка — це не просто набір деталей, а надзвичайно крутий український проєкт із власною ідеєю, баченням та спільнотою.