Đang tải quảng cáo...

Hướng dẫn tạo ứng dụng trên thẻ nhớ SD cho Loa AI (Lua)

adminQuản trị viên
👁️ 305👍 0
Giới thiệu

Cách tạo app/game Lua trên thẻ SD cho Loa AI EchoEar: cấu trúc thư mục ungdung, game.json, main.lua, API gfx/http/sd, layout màn tròn 360×360 và checklist đưa lên máy.

Đang tải quảng cáo...

Loa AI (EchoEar E1 / VC16 / VC32) có thể chạy ứng dụng và game viết bằng Lua 5.4 ngay từ thẻ nhớ TF/SD. Firmware chỉ cần bật Lua SD; bạn copy thư mục app lên thẻ, mở Menu → Thẻ nhớ → tab Ứng dụng là chạy được — không cần biên dịch lại firmware mỗi lần đổi app.

Bài này hướng dẫn từ A–Z: chuẩn bị thẻ, cấu trúc thư mục, file game.json / main.lua, API sẵn có, layout màn tròn 1.85", xử lý ảnh/âm thanh, và checklist trước khi mang lên máy.

Mua Loa AI / xem sản phẩm: https://loaai.me · Flash firmware: https://loaai.me/flash

Loa AI EchoEar màn tròn chạy ứng dụng Lua từ thẻ nhớ SD

1. Cần gì trước khi bắt đầu?

  • Máy Loa AI có khe SD: EchoEar E1 / VC16 / VC32 (firmware có CONFIG_ENABLE_LUA_SD).
  • Thẻ TF/SD định dạng FAT32, bật long filename (tên file dài).
  • Máy tính để soạn main.lua, tạo icon.png, chuẩn hóa ảnh.
  • (Tuỳ app) WiFi nếu dùng http, tts, audio.play, hoặc web server local.

Interpreter Lua nằm trong firmware; script, ảnh, WAV, JSON nằm trên thẻ. Lua không có io / os / socket tự do — chỉ dùng API firmware gắn sẵn.

2. Cấu trúc thư mục trên thẻ (quan trọng)

Chuẩn hiện tại (khuyến nghị):

/sdcard/ungdung/<thu_muc>/<id>/
  main.lua       ← bắt buộc (điểm vào)
  game.json      ← tên, version, icon trên launcher
  icon.png       ← 64×64 PNG/JPG
  ...            ← txt, json, ảnh, wav, fonts/…

Ví dụ thực tế:

  • /sdcard/ungdung/tienich/calc/ — máy tính
  • /sdcard/ungdung/games/altp/ — Ai là triệu phú
  • /sdcard/ungdung/hoctap/flashcard/ — flashcard + TTS

Legacy vẫn chạy: /sdcard/games/<id>/ (một cấp, không có thư mục thể loại).

<thu_muc> và <id> chỉ gồm chữ, số, _, -, . (tối đa ~32 ký tự). Không dùng ...

Firmware quét tối đa khoảng 16 app có main.lua (hoặc main.luac). Không bắt buộc có JSON — thiếu thì dùng tên folder + badge chữ cái.

Loa AI Văn Phòng — tạo và mở app Lua trên thẻ TF

3. File game.json (manifest launcher)

{
  "id": "calc",
  "name": "Máy tính",
  "version": "1.1.0",
  "icon": "icon.png"
}
Trường Bắt buộc? Ghi chú
id nên có trùng tên folder; dùng so sánh/update sau này
name nên có hiện trên lưới Ứng dụng
version nên có semver ngắn, tối đa ~15 ký tự; launcher hiện Tên · v1.1.0
icon không mặc định tìm icon.png / .jpg / .jpeg / .bmp

Khi chạy app, firmware gắn sẵn các biến toàn cục:

  • GAME_DIR — đường dẫn tuyệt đối thư mục app trên SD
  • GAME_ID, GAME_NAME, GAME_VERSION — từ manifest (thiếu version → "0.0.0")

4. Mở app trên Loa AI

  1. Copy cả folder app vào thẻ (đúng cấu trúc ở mục 2).
  2. Gắn thẻ vào máy, bật nguồn; bật WiFi nếu app cần mạng.
  3. Vào Menu → Thẻ nhớ.
  4. Tab Ứng dụng → chạm icon app.
  5. Thoát: nút X giữa mép trên, hoặc giữ tay ~3 giây (thanh “GIỮ 3S”).
  6. Tab Tập tin: xem ảnh / AVI / MP3 như trình duyệt file cũ.

5. Skeleton main.lua tối thiểu (màn tròn)

Màn EchoEar là LCD tròn Ø 1.85", nhưng canvas firmware là 360×360 vuông — bốn góc bị cắt. Nội dung chính nên nằm trong BOX 240×240 giữa màn (hoặc bán kính SAFE ≈ R−10).

-- Thoát: giữ 3 giây, hoặc nút X (firmware vẽ).
if not gfx or not gfx.open or not gfx.open() then
  print("Không mở được gfx")
  return
end

local W, H = gfx.width, gfx.height
local CX, CY = math.floor(W / 2), math.floor(H / 2)
local R = math.floor(math.min(W, H) / 2)
local SAFE = math.max(80, R - 10)
local BOX = 240
local BX = CX - math.floor(BOX / 2)  -- 60 trên 360
local BY = CY - math.floor(BOX / 2)
local HUD_Y = math.max(12, CY - R + 16)

local function text_c(s, y, col, sc)
  sc = sc or 2
  local w = (gfx.measure and gfx.measure(s, sc)) or (#tostring(s) * 6 * sc)
  gfx.text(tostring(s), math.floor((W - w) / 2), y, col, sc)
end

local function draw()
  gfx.clear(0x071428)
  gfx.circle(CX, CY, R - 2, 0xC9A227)   -- phủ CẢ đĩa
  gfx.circle(CX, CY, R - 9, 0x0B1B3A)
  text_c("UNG DUNG MAU", HUD_Y, 0xE8C547, 2)
  gfx.rect(BX, BY, BOX, BOX, 0x123056)
  text_c("BOX 240", CY - 4, 0xF8FAFC, 2)
end

while gfx.present() do
  if gfx.tapped() then
    local x, y = gfx.pos()
    -- xử lý chạm trong BOX / nút…
  end
  draw()
end

gfx.close()

Giao diện Loa AI khi mở ứng dụng / agent trên thiết bị

Số chuẩn layout màn tròn

Hạng mục Giá trị Ghi chú
Canvas 360×360 gfx.width / gfx.height
Tâm / bán kính 180 / R=180 chỉ đĩa nhìn thấy
SAFE R−10 ≈ 170 đừng dùng R×0.78 (~140) — trống lãng phí
BOX chơi 240 chia hết 2/3/4; tránh 252 (góc bị cắt)
Nút X thoát ≈ (180, 16) chạm trong BOX 240 không thoát
Thanh GIỮ 3S y ≈ 273 nút đáy đừng che hết
Icon launcher 64×64 icon.png
Ảnh trong app ~320×320 qua tool resize trước khi copy

Công thức chord (nửa bề rộng còn nhìn thấy tại hàng y) — dùng để giới hạn rộng nút:

local function chord(y, pad)
  local dy = y - CY
  local lim = SAFE - (pad or 0)
  local m = lim * lim - dy * dy
  if m <= 0 then return 0 end
  return math.sqrt(m)
end
-- max_w ≈ 2 * chord(btn_y + btn_h/2, 8)

6. API Lua có sẵn (tóm tắt dùng thật)

6.1 gfx — vẽ + chạm

  • gfx.open() / gfx.close() / while gfx.present() do … end (~30 fps)
  • gfx.clear(0xRRGGBB), rect, circle, text, measure
  • gfx.image("icon.png", x, y), gfx.sprite(…), gfx.size(path)
  • gfx.tapped() / gfx.touching() / gfx.pos() / gfx.ms() / gfx.quit()
  • gfx.exit_pos(cx, cy, hit), gfx.show_exit(false)
  • Font UTF-8 tiếng Việt tự chọn; IPA / ký tự đặc biệt: gfx.load_font("fonts/….bin")

Cache ảnh ~8 file LRU; ảnh tối đa ~360×360 sau scale; file đọc tối đa ~2 MB.

6.2 sd — đọc/ghi thẻ (chroot thư mục app)

if sd.exists("todo.json") then
  local txt = sd.read_file("todo.json")  -- ≤ 64 KB
end
sd.mkdir("assets")
sd.write_file("todo.json", json.encode(obj))
local names = sd.list("pics")  -- tối đa 64 tên

Path tương đối thư mục app (hoặc dưới /sdcard). Không dùng ... File lớn nên http.download, không sd.write_file.

6.3 http + json

local body, status, err = http.get("https://example.com/api.json")
local obj = json.decode(body)
local s = json.encode({ a = 1, tags = { "game" } })

-- File nhị phân / lớn: stream thẳng ra thẻ (≤ 8 MB)
local bytes, err = http.download("https://cdn.example.com/icon.png", "icon.png")
  • http.get / post: timeout 20s, body nhận ≤ 48 KB; POST gửi ≤ 32 KB
  • http.download: timeout 60s, ≤ 8 MB
  • http.snip: cắt đoạn nhỏ từ HTML lớn (không nhét cả trang vào Lua)

6.4 Âm thanh

  • sfx.play("win.wav") / sfx.loop("bgm.wav") / sfx.stop() — WAV PCM ngắn ≤ ~512 KB
  • audio.play(url, "Tiêu đề") — MP3/stream URL (cần WiFi)
  • tts.speak("hello", "en") / tts.speak("xin chào", "vi") — cache ~2 câu gần nhất

6.5 web — HTTP server trên IP WiFi nhà

Điện thoại cùng mạng mở http://<ip-máy>/. File tĩnh trong thư mục www/; tối đa 8 route Lua. Cần vòng gfx.present() để poll request. Demo: quán nước, weblocal.

Tùy chỉnh giao diện Loa AI — kết hợp app trên thẻ nhớ

7. Chuẩn hóa ảnh trước khi copy lên thẻ

Phone JPEG/HEIC/PNG 16-bit dễ lỗi trên ESP32. Nên chạy tool trong repo firmware:

python3 scripts/lua_img_resize.py anh.jpg -o pics/ --format auto
# → khoảng 320×320 PNG nhỏ hoặc JPEG baseline
  • Không copy HEIC / AVIF / JPEG progressive / PNG khổ lớn gốc.
  • Ảnh thật: gfx.sprite(..., false) (tắt chroma trắng).
  • Icon launcher: 64×64.

8. Ví dụ nhanh: app HTTP lưu JSON lên thẻ

local body, status, err = http.get("https://jsonplaceholder.typicode.com/todos/1")
if not body then
  print("HTTP lỗi", err)
  return
end
local todo, jerr = json.decode(body)
if not todo then
  print("JSON lỗi", jerr)
  return
end
sd.write_file("todo.json", json.encode(todo))
print("title =", todo.title)

App có UI đầy đủ tham khảo máy tính tienich/calc/, HTTP demo congcu/net/, giá vàng tienich/vang/, thời tiết tienich/weather/.

9. Checklist trước khi đưa lên thẻ

Màn tròn

  • Phủ cả đĩa bằng gfx.circle(CX, CY, R, …) — không “lỗ” trống giữa kính
  • Playfield / form dùng BOX 240 (không 252)
  • Nút đáy không che hết vùng y≈273 (GIỮ 3S)
  • Hit-test dùng cùng hằng số với lúc vẽ

Asset & manifest

  • main.lua + game.json (name, version) + icon.png 64×64
  • Ảnh đã qua resize; WAV PCM ngắn nếu có tiếng
  • Folder đúng /sdcard/ungdung/<mục>/<id>/

Hiệu năng

  • Lua heap ~128 KB — đừng http.get file lớn
  • Mỗi frame: vẽ lại scene gọn; tránh decode JSON / đọc SD liên tục trong loop

10. Việc thường làm sai

Sai Đúng
Đặt nút góc canvas 360×360 Trong BOX 240 / theo chord
SAFE = R * 0.78 SAFE = R - 10
Playfield 252 “to hơn” 240
Copy JPEG phone / HEIC lên SD lua_img_resize.py trước
http.get file lớn http.download
Hardcode hit-box lệch layout mới Một bộ hằng số dùng chung draw + touch

11. Gợi ý thể loại app

  • Game cảm ứng: playfield trong BOX/SAFE; HUD overlay lưỡi liềm trên.
  • Quiz / nhiều nút chữ: pill 2×2 nửa dưới đĩa (xem altp, toanchu).
  • Tiện ích form: keypad trong BOX 240; hàng đáy trước y≈265.
  • App mạng: màn “Đang tải…” trước http.get (blocking); chữ lỗi ngắn, căn giữa.
  • Vẽ kéo: lưu nét rồi vẽ lại mỗi frame (paint).

Kết

Tạo ứng dụng cho Loa AI trên thẻ nhớ = viết main.lua + game.json + asset, copy vào /sdcard/ungdung/…, mở từ tab Ứng dụng. Nhớ thiết kế cho đĩa tròn R=180, nội dung chính trong BOX 240, và dùng API gfx / sd / http / sfx / tts của firmware.

Có Loa AI EchoEar (có SD) là đủ để tự làm máy tính, quiz, flashcard, game cảm ứng… ngay trên máy.

Mua / xem Loa AI: https://loaai.me
Flash firmware: https://loaai.me/flash

Đang tải quảng cáo...
👁️305 lượt xem