Українська DIY-консоль Лілка. Повний гайд

Усім привіт. Нещодавно вийшов перший реліз української навчально-хакерської консолі Лілка на базі мікроконтролера ESP32-S3 від Андерсона та його друзів. І, як на мене, спричинив немалий ажіотаж в українській IT-спільноті. Я в повному захваті від цього проєкту. Тож у цій статті докладно ознайомлю вас із Лілкою та її можливостями.

Лілка (GitHub) це open source портативна консоль для розробки на базі мікроконтролера ESP32-S3-wroom1, зібрана з доступних компонентів. Лілка має власну друковану плату, операційну систему, готову бібліотеку для спрощення розробки, програми, ігри, демо, детальну документацію та чудову спільноту.

Де придбати Лілку

Орієнтовна вартість усіх деталей базової версії — 500-700 гривень.

  • Будь-хто може легко зібрати Лілку, придбавши комплект для збірки (на цей момент дві перші партії розкупили, коли буде наступна — невідомо).
  • Якщо наборів Лілки немає у продажі можна написати на електрону пошту магазину Imrad ([email protected]) та зробити передзамовлення наступної партії.
  • Можна запитати, чи немає у когось зайвої плати Лілки у загальній гілці Discord-спільноти. Проте компоненти потрібно купити самостійно. Або написати в гілку купівля-продаж.
  • Плату Лілки також можна надрукувати самостійно за допомогою китайських сайтів JLC PCB, або PCBWay. Для цього потрібно завантажити усі .gbr, .drl та .gbrjob-файли звідси. Додати їх в ZIP-архів та завантажити на сайт. Налаштування друку плати можна подивитись тут. Виготовлення займає приблизно тиждень, дорога ще два тижні.
  • Якщо хочеться швидше, можна надрукувати в Україні — pcb24.com.ua. Для цього також потрібно завантажити усі .gbr, .drl та .gbrjob-файли звідси та відправити їх на електрону пошту компанії.
  • Отримати допомогу та поради стосовно самостійного замовлення плати Лілки можна у гілці Лілкаv2: розробка PCB.

Що може Лілка

Лілку вже називають українським Flipper Zero або ігровою консоллю. Для мене відео Aндерсона має вайб епохи компʼютерів ZX-Spectrum та Comadore. А Лілка асоціюється з часом, коли я намагався отримати максимум можливостей від свого Java-телефону. Для вас цей проєкт може мати свої асоціації.

Сама Лілка позиціонується як народна портативна навчально-хакерська консоль для технічної творчості. І як багатофункціональна зручна платформа для розробки, що дає набір інструментів, а ви можете перетворити її на те, на що забажаєте.

Лілка може бути використана як:

  • Інструмент для самостійного вивчення мов програмування.
  • Платформа для створення пет-проєкту.
  • Пристрій для навчання Arduino та мікроконтролерам на уроках інформатики у школах та олімпіадах.
  • Пристрій для моніторингу та вимірювань (температури, вологості, якості повітря тощо).
  • Пристрій для вивчення схемотехніки, мереж, алгоритмів, автоматизацій, розробки інтелектуальних пристроїв в університетах.
  • Контролер ЧПУ-верстатів (числове програмне керування, CNC).
  • Пристрій для створення та запуску ігор.
  • Бездротовий Bluetooth-геймпад.
  • Інструмент пентестингу та хакерства (Wi-Fi jamming, bad USB тощо).
  • SDR (RFID, NFC, 443).
  • Пристрій для керування кліматом, освітленням, безпекою, інтеграції з MQTT, Rest, Post, ESPHome, Home Assistant тощо.
  • Контролер для створення автономних дронів чи інших транспортних засобів.
  • Пристрій для написання та прослуховування трекерної музики.
  • Пристрій для демосцени.
  • Пристрій для створення інтерактивних артінсталяцій або музичних інструментів.

І це не повний список того, чим може бути Лілка. Межа сфер використання залежить від вашої фантазії.

Для збірки Лілки потрібна друкована плата проєкту та набір електронних компонентів. Їх можна придбати у готовому наборі (на момент написання статті розкупили), запитати у Discord-спільноті, чи немає в кого зайвої плати, або надрукувати самостійно, завантаживши окремий Gerber-файл або весь каталог. В Україні можна роздрукувати тут: pcb24, у Китаї популярні сайти pcbway та jlcpcb. Мій знайомий друкував на jlc pcb 10 плат за 25 доларів.

Повний список усіх деталей можна знайти у документації проєкту Лілка. Мені дуже сподобалось, що є посилання, де купити в Україні на Ali та Mouser.

Для здешевлення вартості проєкту існує три різних комплектації.

  1. Базова — має дисплей, кнопки, п’єзо-динамік, слот micro sd, живиться від кабеля usb type-c.
  2. Базова + Батарея — має все те саме, що і базова комплектація, але дозволяє приєднати акумулятор, щоб консоль стала портативною, не привʼязаною до дротів. Можна використати будь-який акумулятор на 3.7 вольт. 18650, bl 5c, АА акумулятори або будь-який інший зручний для вас варіант.
  3. Базова + Звук — має все те саме, що й базова комплектація, але додатково ще й підсилювач MAX98357 та маленький динамік 8 Ohm 1 W або порт 3.5 для навушників.

Мене здивувало, чому потрібно обирати. Дуже круто, що увесь процес збірки покроково документований.

Для дисплея рекомендується спочатку припаяти розʼєм до плати, а потім вставити в нього дисплей. Це полегшить його монтаж і демонтаж за необхідності.

У переліку деталей не вказані стійки під дисплей. На сайті імрад вказані такі деталі стійки: TFF-M2.5×10 (6 шт), TFM-M2×10 (3 шт). Гвинти та гайки: Гайка M2 (3 шт), Гвинт M2.5×6 (12 шт), Гвинт M2×6 (3 шт). Під дисплей використовуються стійки гайки та гвинти М2 — ті, що в кількості 3 шт.

Також в переліку трохи оманливе посилання на кнопку SW11 на AliExpress. Вона не кутова, коли припаюється вимикач, дивиться не в ту сторону. На Ali є бічні кнопки, називаються вони MSK-05G2.0 або horizontal slide switch dip. Якщо ви плануєте друкувати корпус, перегляньте розділ про нього, бо пайка type c та дисплею має бути висока.

Як прошити Лілку

Після того, як пристрій зібрано, при під’єднанні його кабелем usb type-c (для передачі даних) він має відобразитись як USB-пристрій. Якщо так і є, можна його прошивати.

Якщо Лілка не відображається в компʼютері, можливо, щось залишилось недопаяне. Ви можете сфотографувати свою плату з обох боків та надіслати в загальну гілку Discord-спільноти. Там можна запитати, чому не працює, та отримати допомогу.

1. Завантажте та встановіть Visual Studio Code з офіційного сайту.

2. Перейдіть у розділ Extensions та встановіть плагін PlatformIO IDE.

3. Перейдіть у розділ Source Control (за необхідності завантажте Git) >> натисніть Clone Repository >> вставте посилання github.com/lilka-dev/keira (посилання оновлено 25.06.25) і завантажте код проєкту.

4. Перейдіть до File >> Open Folder >> lilka >> firmware >> kiera.

5. Встановіть драйвери СЗ210x USB to UART.

6. Під’єднайте Лілку кабелем usb type-c — type для передачі даних до компʼютера. Обовʼязково потрібен кабель type c — type a, оскільки usb 3.0 і кабелі type-c type-c не підтримуються.

У Linux можна перегянути підключені USB-пристрої за допомогою команд sudo dmesg -wH або lsusb.

У MacOS: sudo dmesg або ls /dev/tty.* /dev/cu.*ioreg -p IOUSB — виводить інформацію про всі підключені USB-пристрої, використовуючи дерево IORegistry.

У Windows — можна переглянути через диспетчер пристроїв.

7. Перейдіть у вкладку з мурахою PlatformIO. Розкрийте каталог v2 >> General >> Build та зачекайте збірки прошивки Лілки.

При успішній збірці отримаєте повідомлення success.

8. Тепер вам потрібно натиснути та утримувати кнопку SELECT і увімкнути Лілку. Після цього можна відпустити кнопку SELECT. Це переведе Лілку в режим завантаження (bootloader mode): в ньому ви можете завантажити в неї нову прошивку.

9. Розкрийте каталог v2 >> General >> Upload.

Keira OS

Keira (Операційна Система «Кіра») — це прошивка та операційна система, що базується на проєкті FreeRTOS і містить основні функції для демонстрації можливостей пристрою. Вона підтримує мультизадачність (одночасне виконання кількох завдань), мережу, запуск програм та прошивок прямо з SD-карти.

Примітка: зазвичай прошивкою називається будь-яка скомпільована програма, розроблена для мікроконтролера.

Усі програми Kiera можна поділити на дві категорії.

Вбудовані (постійні) програми — це програми, написані на C++, які компілюються в прошивку і зберігаються в пам’яті ESP. Вони є частиною основної системи та завжди доступні для виконання.

Зовнішні (завантажувані) програми зберігаються на microSD-картці. Їх можна запускати без змін у прошивці, що дозволяє додавати нові функції або пробувати нові програми без необхідності перепрошивати пристрій.

Написання програм на C++

Keira написана на C++, і вона містить ряд вбудованих програм. Всі програми є вбудованими, тобто вони мають бути частиною Keira. Це означає, що ви не можете додати свою програму до Keira, якщо не зміните код Keira і не перепрошиєте Лілку новим кодом.

Усі програми у Лілці зберігаються за адресою firmware/keira/src/apps. Найпростіша програма складається з двох файлів: myapp.h та myapp.cpp. Переглянути приклад програми можна тут.

  • myapp.cpp — це файл, де знаходиться основний текст вашої програми. Тут описано, що саме вона робить.
  • myapp.h — це файл, де записано список важливих частин програми (наприклад, назв функцій), які можуть знадобитися в інших місцях.

Основна програма Кіри — Launcher. Вона відображає меню програм, налаштувань, інформації й запускає програми. Щоб ваша програма з’явилася в меню, потрібно зареєструвати її в Launcher. Найпростіше — додати її до меню застосунків. Для цього знайдіть у файлі launcher.cpp код зі списком програм і додайте вашу програму до нього.

Бібліотека lilka

Хоча для програмування Лілки на C++ можна використовувати функції Arduino або ESP-IDF, бібліотека lilka забезпечує значно простіший і зручніший спосіб роботи з усіма ключовими компонентами пристрою. Вона дозволяє легко працювати з дисплеєм, кнопками, звуком, акумулятором, файлами, SPI та іншими функціями. Використання цієї бібліотеки не тільки спрощує написання коду, але й допомагає зменшити кількість рядків програми та ймовірність помилок.

Бібліотека вже доступна в репозиторії PlatformIO, тож її легко додати до вашого проєкту. Детальніше про усі функції бібліотеки можна прочитати тут.

Завантажувані програми та скрипти

Бінарні файли (.bin) — це скомпільовані програми, які містять машинний код, готовий до виконання мікроконтролером. Вони дозволяють запускати вторинні прошивки або програми без перепрошивки основної пам’яті пристрою.

Lua-скрипти (.lua) — це текстові файли з кодом, які виконуються безпосередньо на Лілці завдяки вбудованому інтерпретатору Lua версії 5.4.6. Їх не потрібно компілювати заздалегідь — достатньо скопіювати файл на SD-картку та запустити. Це економить час і дозволяє легко ділитися скриптами для використання на інших пристроях. Усі функції Lua можна зручно переглянути на цій сторінці.

mJS-скрипти (.js) — це мінімалістичний діалект JavaScript, що дозволяє запускати .js-файли безпосередньо з SD-картки на Лілці. Хоча це додає певної гнучкості, mJS має суттєві обмеження: відсутність класів, обмежений набір функцій і несумісність зі стандартними бібліотеками JavaScript.

Через ці недоліки команда проєкту рекомендує використовувати Lua, яка є більш функціональною, зручною та стабільною для роботи на Лілці.

Планується також портувати інтерпретатор MicroPython, але через ряд технічних складнощів ця ідея перебуває на ранній стадії реалізації.

Альтернативні прошивки Лілки

Проєкт Rustilka націлений на підтримку мови програмування Rust для платформ Lilka. Це мініпроєкт, який дозволяє розробникам використовувати Rust для створення прошивок для Lilka без залежності від офіційного SDK. Для використання Rust необхідно встановити спеціальну прошивку, що доступна на rust.lilka.dev.

Цей проєкт спрощує налаштування та розробку, а також надає бібліотеку для роботи з апаратними можливостями Lilka. Він орієнтований на розробників, які мають досвід роботи з Rust та базові знання в області embedded-систем. Ось гілка, присвячена Rustilka.

MeowUI — це альтернативна прошивка для Лілки v2 на основі фреймворку meowui. Призначена для створення гарних графічних інтерфейсів користувача на мікроконтролерах esp32, esp32-s3. MeowUI пропонує набір віджетів (меню, скролбар, клавіатура, перемикачі тощо), підтримує створення макетів вікон із контекстами, обробляє стани пінів, включає простий рушій для 2D-ігор і бібліотеки для роботи з периферією (i2s, spi). Віднедавна рушій дозволяє створювати багатокористувацькі ігри. Гілка присвячена MeowUI. Також нещодавно в проєкту зʼявилась гарна документація. У ній окремий розділ присвячений створенню гри з нуля.

Розʼєм розширення

Роз’єм розширення (англ. extension header) — дозволяє підключати до основної плати додаткові модулі, наприклад: мікрофон, реле, сервоприводи, GPS, гіроскоп, LTE, LoRa, ERLS тощо. Лілка має 12-контактний роз’єм розширення. Ці піни можна використовувати для будь-якої цілі. За бажанням для зручності можна припаяти до розʼєму гребінку (GPIO), або 2.54 конектор типу мама.

Корпус для Лілки

Костянтин Поліщук розробив дуже гарний корпус для Лілки — LilCase. Відео з ним можна переглянути тут. Наразі це вже третя версія корпусу. Для використання важливо запаювати дисплей і роз’єм живлення, як на фото, і стійки для дисплея 8 мм.

Нижньої частини немає — вона кастомна під різні типи акумуляторів. Автор має намір з часом проєктувати нижню кришечку під різні типи акумуляторів: 18650 або ті, що є в одноразках 18350, 17350, 16350, 13400. Поредагувати корпус онлайн можна тут. Детальніше про розробку корпусу можна дізнатись у гілці Лілка v2: корпус.

Slim Case — це компактний корпус для Лілки. Його розміри 24×58×149 мм, і він повністю сумісний з комплектуючими від Imrad без потреби у додаткових деталях. Конструкція передбачає використання тонкого акумулятора PR-285083. Процес складання потребує ретельної підгонки та паяння до латунних стійок.

Low profile case — компактний корпус (22мм), простий у друку, без зайвих деталей, вміщує акумулятор BL-5C/BL-6C (аккумулятор nokia) або аналогічний до 53×34мм. Дисплей встановлюється на стійках M2×5мм врівень із кнопками, можна припаяти напряму або через нестандартний роз’єм. USB-модуль припаяний впритул, microSD — з відступом. Кнопки можна використовувати різної висоти, вимикач — будь-який повзунковий, конденсатор С1 — не вище 5мм.

Case for Lilka (Shashel) — Корпус для плати Лілка з припаяним дисплеєм на 5-мм стійках, без виходу на навушники та гребінки. Збирання: дисплей кріпиться, гребінка припаюється, зайві штирі обрізаються. Вплавлюються різьбові втулки, встановлюються кнопки й повзунок, плата монтується й перевіряється. Після припою динаміка й акумулятора корпус з’єднується гвинтами. Друк PET-G на Elegoo Neptune 4 MAX, шар 0,16 мм, сопло 0,6 мм, підтримки мінімальні.

Також ще одним варіантом захисту Лілки є використання задньої кришечки. Придбати її окремо від комплекту немає можливості, але можна надрукувати на 3D принтері, або замовити, вирізати з акрилу чи фанери.

Більше про корпуси, кнопки та акумулятори для Лілки можна подискутувати в гілці Лілка v2 корпус.

Форматування Micro SD для Лілки

Для роботи у Лілці Micro SD карта має бути форматована у форматі FAT32. Обʼєм памʼяті не важливий, обмежень немає. Користувачі запускали на Лілці картки на 128 GB. Форматувати картку можна за допомогою Windows, програми Rufus, Gparted або fdisk на Linux. Також можна форматувати за допомогою кнопкового мобільного телефону.

Проте у деяких користувачів періодично виникають проблеми з форматуванням Micro SD. Можливо, не усі картки підходять. Помилки з форматуванням досі трапляються, тому підтримку з налаштування карти можна знайти у гілці Проблема з SD-картами.

Також ви можете завантажити та записати на карту демонстраційні програми. Вони знаходяться в директорії firmware/keira/sdcard в репозиторії проєкту на GitHub.

Ігри Лілки

Лілка не позиціонується як ігрова консоль, проте це не заважає писати та грати ігри на ній.

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 спільноти для включення вашої програми в каталог.

    Як працюють Lua-програми на Лілці

    Щоб зрозуміти, як написати програму, пропоную поглянути на приклад наявних програм. До прикладу, С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() малює зображення на екрані залежно від стану кнопок:

    1. Спочатку відображає базове зображення no.
    2. Якщо натиснута кнопка D, малює зображення left.
    3. Якщо натиснута кнопка B, малює зображення right.
    4. Якщо одночасно натиснуті B і D, малює зображення both.
    5. Якщо натиснута кнопка 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 на Лілці в окремій гілці спільноти.

    Можливості Лілки величезні — вона відкриває простір для технічної творчості, дозволяючи кожному створювати щось унікальне. Вона може бути як простою, так і складною, залежно від ваших ідей та бажань. Лілка — це не просто набір деталей, а надзвичайно крутий український проєкт із власною ідеєю, баченням та спільнотою.

    Я вірю в його успіх і думаю, що з часом він стане ще більш популярним і затребуваним серед ентузіастів, розробників та дослідників. Лілка надихає на експерименти, відкриває нові можливості та об’єднує людей, які захоплюються інженерією та програмуванням. Це не просто платформа — це ідея, що розширює межі творчості та технічного розвитку. Я впевнений, що попереду ще багато цікавих відкриттів.

    А який ваш досвід взаємодії з Лілкою? Напишіть в коментарях.

    Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

    👍ПодобаєтьсяСподобалось48
    До обраногоВ обраному29
    LinkedIn
    Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
    Ctrl + Enter
    Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
    Ctrl + Enter

    Лілка тепер продається в магазині Radio Kit radio-kit.com/...​on=true&sub_category=true, а також в магазині Arduino ua arduino.ua/...​32-s3-wroom-1u-n16r8-k372

    На стрім завітав Андерсон (Андрій Дунай) і йому можна задати питання.

    стрім про створення інфрачервоного модуля Лілки www.youtube.com/live/JSV9CgKYNDU

    Круто.
    P.S. Зі сторінки магазину йде бите посилання на сайт розробника

    Збираю тут цікаві програми та новин проєкту Lilka community.smarthome.org.ua/t/lilka/139

    А як як екран впливає на очі? Він замалий

    А чому розмір екрану як такий важливий?
    Ось порівняйте розміри шрифтів:
    res.cloudinary.com/...​/jeef6crvavc7bgdg73k9.jpg
    На обох дефолт, розміри не змінені.
    Може, просто не слід пхати забагато на екран за раз?

    Бо така природа фізіології очей.

    Більше розміри — більше свободи розміщення будь-чого завгодно.

    Насправді ви можете це зробити. Оськільки код і усі деталі у вільному доступі. Також деякі користувачі Discord сервера піднімали це питання і здається навіть міняли дисплей на більший

    1) Навіщо? Захаращувати і відволікати увагу?
    2) До чого тут вплив на зір?

    До чого тут вплив на зір?

    У вас ідеальний зір? У більшости людей якісь проблеми.

    Навіщо? Захаращувати і відволікати увагу?

    Ну якщо ви не бачите варіанту не захаращити UI, я не знаю, що сказати...

    У вас ідеальний зір?

    Анітрохи, навпаки.
    Я питаю: в чому вплив полягає? Ви гілку читаєте взагалі, перш ніж писати?

    не захаращити UI

    До чого тут UI? На екрані лише UI відображається?

    Ви гілку читаєте взагалі, перш ніж писати?

    З того, що тут написано — читаю. На відміну від вас.

    Я питаю: в чому вплив полягає?

    В більших можливостях для UI. Будь-яких.

    На екрані лише UI відображається?

    Саме так. Чи ви не знаєте сенс цього акроніму? Ну так узнайте.

    Будь-яких.

    Яка чудова, а головне — конкретна відповідь.

    Ну так поки ви не читаєте, нема сенсу давати іншу.

    Читаю що? Я досі не бачу в цій гілці хоч якоїсь конкретики, як розмір екрану впливає на зір, і чому він важливіший за розміри інтерфейсу. Моє фото наочно показує, що якраз навпаки: оці ваші «більші можливості для UI» призводять до запихування на екран якнайбільше і дрібного. І це ще навіть не типова мобільна гра з купою дрібних елементів керування, об які зумери ламають очі.

    оє фото наочно показує, що якраз навпаки: оці ваші «більші можливості для UI» призводять до запихування на екран якнайбільше і дрібного.

    Тобто іконки, які достатньо великі, швидко і легко показують те, що треба показати, ви не бачите. І ті, що у вашому прикладі в головній частині екрану, і ті, що в треї зверху.

    Порівняйте шрифти у матрицях, наприклад, 7×5 (мінімум, який дає хоч якось відрізнити букви), 16×9 (стандарт IBM PC класичних часів) і те, що бачите у вашому прикладі як підписи до іконок.

    Маючи кращі ресурси, можна зробити для UI багато чого корисного.

    Ну а якщо ви думатимете тільки про те, як його заабʼюзити, наприклад, напхавши у екран таблицю тексту в 40 рядків... вибачте, це тільки ваші власні примхи.

    іконки, які достатньо великі

    Га?
    res.cloudinary.com/...​/rzg0wsxq4yhe6xpu4ko0.jpg

    швидко і легко показують те, що треба показати,

    А іконки потрібні взагалі? Що вони показувати мають? Не всі візуали. А текст під ними дрібний. Навіть якщо через налаштування a11y збільшити текст — він усе одно лишається меншим і більше певного порогу не сягає.

    головній частині екрану

    Це де?

    і ті, що в треї зверху

    Так і вони на сонерику більші, ліл. Що дотепно, навіть сукупна ширина статусбару приблизно однакова через моноброву.

    Порівняйте шрифти у матрицях, наприклад, 7×5 (мінімум, який дає хоч якось відрізнити букви), 16×9 (стандарт IBM PC класичних часів) і те, що бачите у вашому прикладі як підписи до іконок.

    На Series 20 шрифти десь 8×6, цілком читабельні. Бо задизайнені професіоналами, а не технарами абияк.

    Більш того, деякі користувачі досі використовують старі бітмапні шрифти штибу Tahoma, навіть на GNU/Linux видирають їх зі старих образів Windows попри ліцензію. Бо вони, знову ж, професійно задизайнені і файно виглядають без згладжування. Згладжування ж векторних шрифтів — це сорти гівна, за які десятиліттями ідуть баталії, чиї фломастери смачніші: сірі, веселкові чи жирні.

    Який узагалі практичний сенс у векторних шрифтах, окрім слідування друкованій традиції? Більш-менш пристойно вони виглядають лише на HiDPI, але HiDPI має свою ціну: швидше вижирання акумулятора.

    Це нагадує мені JPEG, побудований на математичній моделі, що оперує нецілими числами, розробленій ще для аналогового відеосигналу, і ми досі пожинаємо плоди цього. Банально через брак чогось ліпшого і тодішню (та що тодішню, досі так) прірву непорозуміння між математиками й інженерами, і роль програмістів як мавпочок, що просто перебивають із папірця до ЕОМ написаний математиками код. І це у часи примітивних процесорів із дуууууже дорогими обчисленнями дійсних чисел, а відповідно, там округлення на округленнях і округленнями поганяють. Сучасні відеокодеки вміють використовувати кольоровий простір YCgCo, що набагато ліпше кладеться на цілі числа і прості бітові зсуви, CABAC, підігнані під прості операції коефіціенти для МДКП/ДКП та інші поліпшення — але JPEG із нами ще надовго разом із його ***чими шакалами.

    Крутий проект!

    Я в захваті, крутезний проект!

    Давно слідкую за Богданом, дуже крута ініціатива і проєкт!

    Дуже крутий і якісний проект.

    Так, дійсно немає. Imrad робить їх малими партіями. Раджу підписатись на Discord та слідкувати за оновленнями. Тому що набори скуповують дуже швидко. Орієнтовний час між анонсами першої та другої партії 40 днів. Дата наступної партії невідома, але якщо час буде такий самий то десь 8-10 березня можна буде придбати. Є альтернативний варіант запитати у чаті чи хтось не продає плату і докупити комплектуючі самостійно, або роздрукувати плати на PCB сайтах.

    а самому таку плату витравити не можна? чи там багатошарова?

    Дивний вибір вони зробили- там наче зовсім мало компонентів, площа немала, воно і на двошарову мало влізло би, що б значно підвищило доступність...

    Ви також можете написати imrad-у на пошту [email protected] та домовитись за передзамовлення набору Лілки. Робочий варіант

    не розумію — для чого запихати стільки можливостей у такий маленький екран? 🤔

    res.cloudinary.com/...​/qya7nxdsnijr1fhigrrb.jpg

    Например, чтобы потом выпустить новую версию с большим экраном и продать второй раз.

    Хотя для обучения, наверное, лучше выбрать поделку на raspberry pi zero w 1.

    Например, чтобы потом выпустить новую версию с большим экраном и продать второй раз.

    ну... можна і так

    Например, чтобы потом выпустить новую версию с большим экраном и продать второй раз.

    Це відкритий проєкт, ви можете самостійно замінити екран на більший

    Перевага Лілки низька вартість. Базовий комплект коштує ~ 500-700 грн. А у випадку з Raspberry Pi Zero W лише сама SBC коштує ~ 1000 грн

    Думаю, что этот тот случай, когда переплата даст очень много возможностей. Зероу 1 кое-как и «рабочий стол» тянет в линуксе.

    лише сама SBC коштує ~ 1000 грн

    шалені гроші. відкривайте банку, трохи закину

    Наверное, для обучения можно было поставить стандартный экран без круглых углов, которые будут создавать лишние проблемы с позиционированием данных для отображения. В таких проектах, наверное, много экрана не бывает.

    Хтось з спільноти пробував використати екран 240×320 discord.com/...​64272/1340774356024033424

    Дякую за інформацію!
    Відкрив для себе Андерсона :)

    дрон-камікадзе на лілці коли

    Нам потрібна техстаття про це!

    Підписатись на коментарі