docs: stubs, agents & Makefile
This commit is contained in:
@@ -0,0 +1,441 @@
|
||||
---@meta
|
||||
|
||||
-- CrossPoint Lua API stub for LuaLS. GENERATED by scripts/gen_lua_stubs.py
|
||||
-- from the annotations in src/util/LuaManager.cpp; do not edit by hand.
|
||||
|
||||
---@alias CrossPointButton
|
||||
---| "back"
|
||||
---| "confirm"
|
||||
---| "left"
|
||||
---| "right"
|
||||
---| "up"
|
||||
---| "down"
|
||||
---| "page_back"
|
||||
---| "page_forward"
|
||||
|
||||
---@alias CrossPointOrientation
|
||||
---| "portrait"
|
||||
---| "portrait_inv"
|
||||
---| "landscape_cw"
|
||||
---| "landscape_ccw"
|
||||
|
||||
---@class app
|
||||
app = {}
|
||||
|
||||
---@class fs
|
||||
fs = {}
|
||||
|
||||
---@class gui
|
||||
---@field HINT_BACK string
|
||||
---@field HINT_OK string
|
||||
---@field HINT_PREV string
|
||||
---@field HINT_NEXT string
|
||||
gui = {}
|
||||
gui.HINT_BACK = "<<"
|
||||
gui.HINT_OK = "o"
|
||||
gui.HINT_PREV = "<"
|
||||
gui.HINT_NEXT = ">"
|
||||
|
||||
---@class http
|
||||
http = {}
|
||||
|
||||
---@class input
|
||||
input = {}
|
||||
|
||||
---@class log
|
||||
log = {}
|
||||
|
||||
---@class sys
|
||||
sys = {}
|
||||
|
||||
---@class timer
|
||||
timer = {}
|
||||
|
||||
---@class wifi
|
||||
wifi = {}
|
||||
|
||||
---@type integer
|
||||
COLOR_BLACK = 0
|
||||
---@type integer
|
||||
COLOR_CLEAR = 0
|
||||
---@type integer
|
||||
COLOR_DARK_GRAY = 0
|
||||
---@type integer
|
||||
COLOR_LIGHT_GRAY = 0
|
||||
---@type integer
|
||||
COLOR_WHITE = 0
|
||||
---@type integer
|
||||
FONT_BOOKERLY_12 = 0
|
||||
---@type integer
|
||||
FONT_BOOKERLY_14 = 0
|
||||
---@type integer
|
||||
FONT_BOOKERLY_16 = 0
|
||||
---@type integer
|
||||
FONT_NOTOSANS_12 = 0
|
||||
---@type integer
|
||||
FONT_NOTOSANS_14 = 0
|
||||
---@type integer
|
||||
FONT_NOTOSANS_16 = 0
|
||||
---@type integer
|
||||
FONT_SMALL = 0
|
||||
---@type integer
|
||||
FONT_UI_10 = 0
|
||||
---@type integer
|
||||
FONT_UI_12 = 0
|
||||
---@type integer
|
||||
REFRESH_FAST = 0
|
||||
---@type integer
|
||||
REFRESH_FULL = 0
|
||||
---@type integer
|
||||
REFRESH_HALF = 0
|
||||
---@type integer
|
||||
STYLE_BOLD = 0
|
||||
---@type integer
|
||||
STYLE_NORMAL = 0
|
||||
---@type integer
|
||||
STYLE_REGULAR = 0
|
||||
|
||||
-- app
|
||||
|
||||
--- Enables the on_tick() callback at a fixed interval (0 disables, minimum 33ms). Requires on_tick() to be defined.
|
||||
---@param intervalMs integer 0-3600000
|
||||
function app.setTickInterval(intervalMs) end
|
||||
|
||||
-- fs
|
||||
|
||||
--- Returns true if a file or directory exists.
|
||||
---@param path string Absolute path on the SD card
|
||||
---@return boolean
|
||||
function fs.exists(path) end
|
||||
|
||||
--- Returns the size of a file in bytes.
|
||||
---@param path string Absolute path on the SD card
|
||||
---@return integer? size Nil if the file is missing or a directory
|
||||
function fs.fileSize(path) end
|
||||
|
||||
--- Lists sorted sub-directory names under a path (hidden entries excluded).
|
||||
---@param path string Absolute path on the SD card
|
||||
---@return string[] names
|
||||
function fs.listDirs(path) end
|
||||
|
||||
--- Lists sorted file names under a path (hidden entries excluded).
|
||||
---@param path string Absolute path on the SD card
|
||||
---@return string[] names
|
||||
function fs.listFiles(path) end
|
||||
|
||||
--- Creates a directory.
|
||||
---@param path string Absolute path on the SD card
|
||||
---@return boolean? ok
|
||||
---@return string? error Present when ok is nil
|
||||
function fs.mkdir(path) end
|
||||
|
||||
--- Reads an entire file (capped at ~50KB).
|
||||
---@param path string Absolute path on the SD card
|
||||
---@return string? content Nil if the file is missing or unreadable
|
||||
function fs.readFile(path) end
|
||||
|
||||
--- Reads the first full line beginning at or after a byte offset, for paging large files.
|
||||
---@param path string Absolute path on the SD card
|
||||
---@param offset integer Byte offset (0-based); snaps forward to the next line boundary if mid-line
|
||||
---@return string? line Nil past end of file, or if the line exceeds 192 bytes
|
||||
---@return integer? nextOffset Byte offset of the following line
|
||||
function fs.readLineAt(path, offset) end
|
||||
|
||||
--- Deletes a file. /.apps and /.crosspoint are protected.
|
||||
---@param path string Absolute path on the SD card
|
||||
---@return boolean? ok
|
||||
---@return string? error Present when ok is nil
|
||||
function fs.remove(path) end
|
||||
|
||||
--- Recursively deletes a directory tree. /.apps and /.crosspoint are protected.
|
||||
---@param path string Absolute path on the SD card
|
||||
---@return boolean? ok
|
||||
---@return string? error Present when ok is nil
|
||||
function fs.removeTree(path) end
|
||||
|
||||
--- Renames (moves) a file or directory.
|
||||
---@param source string
|
||||
---@param destination string Must not already exist
|
||||
---@return boolean? ok
|
||||
---@return string? error Present when ok is nil
|
||||
function fs.rename(source, destination) end
|
||||
|
||||
--- Writes a file (creating or overwriting).
|
||||
---@param path string Absolute path on the SD card
|
||||
---@param content string
|
||||
---@return boolean ok
|
||||
function fs.writeFile(path, content) end
|
||||
|
||||
-- gui
|
||||
|
||||
--- Clears the framebuffer to white.
|
||||
function gui.clear() end
|
||||
|
||||
--- Draws a BMP file from the SD card, centered by default.
|
||||
---@param path string Absolute path on the SD card
|
||||
---@param x? integer Left edge (default centered)
|
||||
---@param y? integer Top edge (default centered)
|
||||
---@param maxWidth? integer Bounds the image is scaled into (default screen)
|
||||
---@param maxHeight? integer Bounds the image is scaled into (default screen)
|
||||
---@return boolean ok False if the file is missing or not a valid BMP
|
||||
function gui.drawBmp(path, x, y, maxWidth, maxHeight) end
|
||||
|
||||
--- Draws the standard four-button hint bar (use gui.HINT_* for arrow glyphs).
|
||||
---@param btn1? string Back button label (default "")
|
||||
---@param btn2? string Confirm button label (default "")
|
||||
---@param btn3? string Left button label (default "")
|
||||
---@param btn4? string Right button label (default "")
|
||||
function gui.drawButtonHints(btn1, btn2, btn3, btn4) end
|
||||
|
||||
--- Draws text centered horizontally on the screen.
|
||||
---@param font integer FONT_* constant
|
||||
---@param y integer Baseline Y
|
||||
---@param text string
|
||||
---@param color? integer COLOR_* constant (default COLOR_BLACK)
|
||||
---@param style? integer STYLE_* constant (default STYLE_REGULAR)
|
||||
function gui.drawCenteredText(font, y, text, color, style) end
|
||||
|
||||
--- Draws an unfilled circle.
|
||||
---@param cx integer Center X
|
||||
---@param cy integer Center Y
|
||||
---@param radius integer
|
||||
---@param width? integer Stroke width (default 1)
|
||||
---@param color? integer COLOR_* constant (default COLOR_BLACK)
|
||||
function gui.drawCircle(cx, cy, radius, width, color) end
|
||||
|
||||
--- Draws a line.
|
||||
---@param x1 integer Start X
|
||||
---@param y1 integer Start Y
|
||||
---@param x2 integer End X
|
||||
---@param y2 integer End Y
|
||||
---@param width? integer Stroke width (default 1)
|
||||
---@param color? integer COLOR_* constant (default COLOR_BLACK)
|
||||
function gui.drawLine(x1, y1, x2, y2, width, color) end
|
||||
|
||||
--- Draws a single pixel.
|
||||
---@param x integer
|
||||
---@param y integer
|
||||
---@param color? integer COLOR_* constant (default COLOR_BLACK)
|
||||
function gui.drawPixel(x, y, color) end
|
||||
|
||||
--- Draws an unfilled rectangle.
|
||||
---@param x integer Left edge
|
||||
---@param y integer Top edge
|
||||
---@param w integer Width
|
||||
---@param h integer Height
|
||||
---@param color? integer COLOR_* constant (default COLOR_BLACK)
|
||||
function gui.drawRect(x, y, w, h, color) end
|
||||
|
||||
--- Draws an unfilled rounded rectangle.
|
||||
---@param x integer Left edge
|
||||
---@param y integer Top edge
|
||||
---@param w integer Width
|
||||
---@param h integer Height
|
||||
---@param width? integer Stroke width (default 2)
|
||||
---@param radius? integer Corner radius (default 10)
|
||||
---@param color? integer COLOR_* constant (default COLOR_BLACK)
|
||||
function gui.drawRoundedRect(x, y, w, h, width, radius, color) end
|
||||
|
||||
--- Draws text at a position.
|
||||
---@param font integer FONT_* constant
|
||||
---@param x integer Left edge
|
||||
---@param y integer Baseline Y
|
||||
---@param text string
|
||||
---@param color? integer COLOR_* constant (default COLOR_BLACK)
|
||||
---@param style? integer STYLE_* constant (default STYLE_REGULAR)
|
||||
function gui.drawText(font, x, y, text, color, style) end
|
||||
|
||||
--- Draws a filled circle.
|
||||
---@param cx integer Center X
|
||||
---@param cy integer Center Y
|
||||
---@param radius integer
|
||||
---@param color? integer COLOR_* constant (default COLOR_BLACK)
|
||||
function gui.fillCircle(cx, cy, radius, color) end
|
||||
|
||||
--- Draws a filled polygon from parallel vertex arrays (needs 3+ points).
|
||||
---@param xs int[] X coordinates of each vertex
|
||||
---@param ys int[] Y coordinates of each vertex
|
||||
---@param color? integer COLOR_* constant (default COLOR_BLACK)
|
||||
function gui.fillPolygon(xs, ys, color) end
|
||||
|
||||
--- Draws a filled rectangle.
|
||||
---@param x integer Left edge
|
||||
---@param y integer Top edge
|
||||
---@param w integer Width
|
||||
---@param h integer Height
|
||||
---@param color? integer COLOR_* constant (default COLOR_BLACK)
|
||||
function gui.fillRect(x, y, w, h, color) end
|
||||
|
||||
--- Draws a filled rounded rectangle.
|
||||
---@param x integer Left edge
|
||||
---@param y integer Top edge
|
||||
---@param w integer Width
|
||||
---@param h integer Height
|
||||
---@param radius? integer Corner radius (default 10)
|
||||
---@param color? integer COLOR_* constant (supports gray values) (default COLOR_BLACK)
|
||||
function gui.fillRoundedRect(x, y, w, h, radius, color) end
|
||||
|
||||
--- Returns the rendered width of a string in pixels.
|
||||
---@param font integer FONT_* constant
|
||||
---@param text string
|
||||
---@param style? integer STYLE_* constant (default STYLE_REGULAR)
|
||||
---@return integer width
|
||||
function gui.getTextWidth(font, text, style) end
|
||||
|
||||
--- Returns the screen height in pixels for the current orientation.
|
||||
---@return integer
|
||||
function gui.height() end
|
||||
|
||||
--- Pushes the framebuffer to the panel.
|
||||
---@param mode? integer REFRESH_* constant (default REFRESH_FAST)
|
||||
function gui.refresh(mode) end
|
||||
|
||||
--- Changes the screen orientation (invalidates any layout assumptions).
|
||||
---@param mode string "portrait" | "portrait_inv" | "landscape_cw" | "landscape_ccw"
|
||||
function gui.setOrientation(mode) end
|
||||
|
||||
--- Returns the screen width in pixels for the current orientation.
|
||||
---@return integer
|
||||
function gui.width() end
|
||||
|
||||
-- http
|
||||
|
||||
--- Performs an HTTP DELETE request.
|
||||
---@param url string
|
||||
---@param headers? table<string,string> Optional request headers
|
||||
---@return string? body Nil on failure
|
||||
---@return integer status HTTP status code, or -1 if the request never happened
|
||||
function http.delete(url, headers) end
|
||||
|
||||
--- Downloads an HTTPS URL to a file with optional integrity checks.
|
||||
---@param url string HTTPS URL
|
||||
---@param destination string Absolute destination path on the SD card
|
||||
---@param options table { maxBytes: int (required, up to 16MB), expectedSize?: int, sha256?: string (64 hex chars) }
|
||||
---@return integer? bytesWritten Nil on failure
|
||||
---@return string? error Present when the download fails
|
||||
function http.download(url, destination, options) end
|
||||
|
||||
--- Performs an HTTP GET request.
|
||||
---@param url string
|
||||
---@param headers? table<string,string> Optional request headers
|
||||
---@return string? body Nil on failure (capped at ~50KB)
|
||||
---@return integer status HTTP status code, or -1 if the request never happened
|
||||
function http.get(url, headers) end
|
||||
|
||||
--- Performs an HTTP HEAD request.
|
||||
---@param url string
|
||||
---@param headers? table<string,string> Optional request headers
|
||||
---@return string? body Nil on failure
|
||||
---@return integer status HTTP status code, or -1 if the request never happened
|
||||
function http.head(url, headers) end
|
||||
|
||||
--- Performs an HTTP PATCH request.
|
||||
---@param url string
|
||||
---@param body? string Request body (default "")
|
||||
---@param headers? table<string,string> Optional request headers
|
||||
---@return string? body Nil on failure (capped at ~50KB)
|
||||
---@return integer status HTTP status code, or -1 if the request never happened
|
||||
function http.patch(url, body, headers) end
|
||||
|
||||
--- Performs an HTTP POST request.
|
||||
---@param url string
|
||||
---@param body? string Request body (default "")
|
||||
---@param headers? table<string,string> Optional request headers
|
||||
---@return string? body Nil on failure (capped at ~50KB)
|
||||
---@return integer status HTTP status code, or -1 if the request never happened
|
||||
function http.post(url, body, headers) end
|
||||
|
||||
--- Percent-encodes a string for use in a URL query.
|
||||
---@param input string
|
||||
---@return string encoded
|
||||
function http.urlencode(input) end
|
||||
|
||||
-- input
|
||||
|
||||
--- Returns true while any button is currently held down.
|
||||
---@return boolean
|
||||
function input.isAnyPressed() end
|
||||
|
||||
--- Returns true while the button is currently held down.
|
||||
---@param button string Button name
|
||||
---@return boolean
|
||||
function input.isPressed(button) end
|
||||
|
||||
--- Returns true if the button was pressed since the last input frame. Buttons: "back", "confirm", "left", "right", "up", "down", "page_back", "page_forward".
|
||||
---@param button string Button name
|
||||
---@return boolean
|
||||
function input.wasPressed(button) end
|
||||
|
||||
--- Returns true if the button was released since the last input frame.
|
||||
---@param button string Button name
|
||||
---@return boolean
|
||||
function input.wasReleased(button) end
|
||||
|
||||
-- log
|
||||
|
||||
--- Logs a debug message to serial output.
|
||||
---@param message string
|
||||
function log.debug(message) end
|
||||
|
||||
--- Logs an error message to serial output.
|
||||
---@param message string
|
||||
function log.error(message) end
|
||||
|
||||
--- Logs an info message to serial output.
|
||||
---@param message string
|
||||
function log.info(message) end
|
||||
|
||||
-- sys
|
||||
|
||||
--- Blocks the app for the given number of milliseconds.
|
||||
---@param ms integer
|
||||
function sys.delay(ms) end
|
||||
|
||||
--- Requests the app to exit back to the launcher.
|
||||
function sys.exit() end
|
||||
|
||||
--- Returns milliseconds since boot.
|
||||
---@return integer
|
||||
function sys.millis() end
|
||||
|
||||
--- Returns milliseconds since boot.
|
||||
---@return integer
|
||||
function sys.uptime() end
|
||||
|
||||
-- timer
|
||||
|
||||
--- Fires on_timer(id) once after the interval. Apps define on_timer(id) to receive it.
|
||||
---@param intervalMs integer 100-3600000
|
||||
---@param id string 1-31 letters, digits, underscores, or dashes
|
||||
function timer.after(intervalMs, id) end
|
||||
|
||||
--- Cancels a timer by id.
|
||||
---@param id string
|
||||
function timer.cancel(id) end
|
||||
|
||||
--- Fires on_timer(id) repeatedly at the interval until cancelled.
|
||||
---@param intervalMs integer 100-3600000
|
||||
---@param id string 1-31 letters, digits, underscores, or dashes
|
||||
function timer.every(intervalMs, id) end
|
||||
|
||||
-- wifi
|
||||
|
||||
--- Starts connecting to a stored Wi-Fi network (credentials from the reader settings). Poll wifi.status().
|
||||
function wifi.connect() end
|
||||
|
||||
--- Disconnects Wi-Fi if the app started it.
|
||||
function wifi.disconnect() end
|
||||
|
||||
--- Returns whether Wi-Fi is currently connected to an access point.
|
||||
---@return boolean connected
|
||||
function wifi.isConnected() end
|
||||
|
||||
--- Returns the local IP address of the Wi-Fi interface.
|
||||
---@return string ip "0.0.0.0" if not connected
|
||||
function wifi.localIP() end
|
||||
|
||||
--- Returns the Wi-Fi connection state, advancing any in-progress attempt.
|
||||
---@return string status "idle" | "connecting" | "connected" | "failed"
|
||||
function wifi.status() end
|
||||
|
||||
-- Callbacks a runtime app may define: draw(), on_tick(), on_button(name, state), on_timer(id).
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"runtime": { "version": "Lua 5.4" },
|
||||
"workspace": { "library": [".luals"] },
|
||||
"diagnostics": { "globals": ["app","COLOR_BLACK","COLOR_CLEAR","COLOR_DARK_GRAY","COLOR_LIGHT_GRAY","COLOR_WHITE","FONT_BOOKERLY_12","FONT_BOOKERLY_14","FONT_BOOKERLY_16","FONT_NOTOSANS_12","FONT_NOTOSANS_14","FONT_NOTOSANS_16","FONT_SMALL","FONT_UI_10","FONT_UI_12","fs","gui","http","input","log","REFRESH_FAST","REFRESH_FULL","REFRESH_HALF","STYLE_BOLD","STYLE_NORMAL","STYLE_REGULAR","sys","timer","wifi"] }
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
# AI Agent Guidelines — xteink-apps
|
||||
|
||||
Lua apps for CrossPoint Reader (Xteink X4, ESP32-C3, 800x480 e-ink). Repo layout, catalog/manifest
|
||||
formats, and limits are in [README.md](README.md); this file covers the API surface and the rules
|
||||
that are easy to get wrong.
|
||||
formats, and limits are in [README.md](README.md); this file covers the rules that are easy to get wrong.
|
||||
|
||||
The API below is the contract this repo targets. It is implemented by the CrossPoint Reader firmware
|
||||
in `src/util/LuaManager.cpp` (bindings) and `src/activities/util/LuaActivity.cpp` (lifecycle); consult
|
||||
those only if you have that repo checked out and suspect this document has drifted.
|
||||
**`.luals/crosspoint.lua` is the entire universe available to an app.** It is the LuaLS stub
|
||||
generated from the firmware bindings — every callable, constant, signature, and doc comment lives
|
||||
there. Read it for the API; anything absent from it does not exist on device. Regenerate with
|
||||
`make stubs` (needs the `crosspoint-reader` firmware repo as a sibling checkout; it also refreshes
|
||||
the `.luarc.json` globals list).
|
||||
|
||||
## Non-negotiable: regenerate manifests
|
||||
|
||||
@@ -14,8 +15,8 @@ Any change to a file under `apps/<AppId>/` changes its SHA-256, and the firmware
|
||||
downloaded file against `manifest.txt` / `catalog.txt`. Stale metadata means installs fail on device.
|
||||
|
||||
```bash
|
||||
scripts/update-manifests.py # regenerate manifests + catalog.txt
|
||||
scripts/update-manifests.py --check # verify they are current (CI-style)
|
||||
make manifests # regenerate manifests + catalog.txt
|
||||
make check # verify they are current (CI-style)
|
||||
```
|
||||
|
||||
Run it before every commit that touches app payloads, and commit payload + `manifest.txt` +
|
||||
@@ -71,90 +72,31 @@ lastScreen = screen
|
||||
gui.refresh(mode)
|
||||
```
|
||||
|
||||
## API surface
|
||||
## API gotchas
|
||||
|
||||
All globals; no `require`. Full Lua stdlib is open, but prefer the `fs` table over `io`/`os` —
|
||||
those bypass the firmware's SD mutex.
|
||||
Signatures live in the stub. These are the behaviors it can't tell you:
|
||||
|
||||
### gui
|
||||
|
||||
| Call | Returns |
|
||||
|---|---|
|
||||
| `gui.clear()` | – |
|
||||
| `gui.refresh([mode=REFRESH_FAST])` | – |
|
||||
| `gui.width()` / `gui.height()` | integer, orientation-aware |
|
||||
| `gui.setOrientation(mode)` | – ; `"landscape_cw"`, `"landscape_ccw"`, `"portrait_inv"`, anything else = portrait |
|
||||
| `gui.drawRect(x, y, w, h [, color])` | – |
|
||||
| `gui.fillRect(x, y, w, h [, color])` | – |
|
||||
| `gui.drawRoundedRect(x, y, w, h [, lineWidth=2] [, radius=10] [, color])` | – |
|
||||
| `gui.fillRoundedRect(x, y, w, h [, radius=10] [, color=COLOR_BLACK])` | – ; only call that renders true grayscale |
|
||||
| `gui.drawLine(x1, y1, x2, y2 [, lineWidth=1] [, color])` | – |
|
||||
| `gui.drawPixel(x, y [, color])` | – |
|
||||
| `gui.drawCircle(cx, cy, r [, width=1] [, color])` / `gui.fillCircle(cx, cy, r [, color])` | – |
|
||||
| `gui.fillPolygon(xTable, yTable [, color])` | – ; 1-based arrays, silently no-ops below 3 points |
|
||||
| `gui.drawText(fontId, x, y, text [, color] [, style])` | – |
|
||||
| `gui.drawCenteredText(fontId, y, text [, color] [, style])` | – |
|
||||
| `gui.getTextWidth(fontId, text [, style])` | px |
|
||||
| `gui.drawButtonHints([back] [, confirm] [, prev] [, next])` | – ; logical labels, remapped to physical positions |
|
||||
| `gui.drawBmp(path [, x] [, y] [, maxWidth] [, maxHeight])` | boolean; centered when x/y omitted |
|
||||
|
||||
Constants: `COLOR_CLEAR/WHITE/LIGHT_GRAY/DARK_GRAY/BLACK`, `FONT_UI_10`, `FONT_UI_12`, `FONT_SMALL`,
|
||||
`FONT_NOTOSANS_12/14/16`, `FONT_BOOKERLY_12/14/16`, `STYLE_REGULAR` (= `STYLE_NORMAL`), `STYLE_BOLD`,
|
||||
`gui.HINT_BACK/HINT_OK/HINT_PREV/HINT_NEXT`.
|
||||
|
||||
Color args accept a number, or a boolean (`true`=black, `false`=white). Except for
|
||||
`fillRoundedRect`, gray values collapse to black.
|
||||
|
||||
### input
|
||||
|
||||
`input.wasPressed(name)`, `input.wasReleased(name)`, `input.isPressed(name)`, `input.isAnyPressed()`
|
||||
→ boolean.
|
||||
|
||||
Names: `"back"`, `"confirm"`, `"left"`, `"right"`, `"up"`, `"down"`, `"page_back"`, `"page_forward"`.
|
||||
An unrecognized string silently resolves to `back`, so typos are invisible — spell them exactly.
|
||||
|
||||
### fs
|
||||
|
||||
| Call | Returns |
|
||||
|---|---|
|
||||
| `fs.exists(path)` | boolean |
|
||||
| `fs.listDirs(path)` / `fs.listFiles(path)` | sorted array table, dotfiles excluded, empty table if not a dir |
|
||||
| `fs.readFile(path)` | string (truncated at 50000 B), `nil` on failure |
|
||||
| `fs.fileSize(path)` | integer or `nil` |
|
||||
| `fs.readLineAt(path, offset)` | `line, nextOffset`; `nil, nextOffset` if the line exceeds 192 B |
|
||||
| `fs.writeFile(path, content)` | boolean only — no error string |
|
||||
| `fs.mkdir(path)` / `fs.rename(src, dst)` / `fs.remove(path)` / `fs.removeTree(path)` | `true` \| `nil, errString` |
|
||||
|
||||
Constraints that bite:
|
||||
- All globals; no `require`. Full Lua stdlib is open, but prefer `fs` over `io`/`os` — those bypass
|
||||
the firmware's SD mutex.
|
||||
- Color args accept a number or a boolean (`true`=black, `false`=white). `gui.fillRoundedRect` is the
|
||||
only call that renders true grayscale; elsewhere gray collapses to black.
|
||||
- `gui.fillPolygon` takes 1-based arrays and silently no-ops below 3 points.
|
||||
- `gui.drawButtonHints` labels are logical; the firmware remaps them to physical positions.
|
||||
- An unrecognized `input` button name silently resolves to `back`, so typos are invisible.
|
||||
- `fs.readLineAt` returns `nil, nextOffset` when the line exceeds 192 B; `fs.writeFile` returns a
|
||||
bare boolean, unlike the other mutators.
|
||||
- **`fs.rename` fails if the destination exists.** Replacing a directory means rename-away, rename-in,
|
||||
then delete the backup (see `install()` in `apps/AppStore/main.lua`).
|
||||
- Mutation calls require an absolute path with no `..` and reject the `/.apps` and `/.crosspoint`
|
||||
roots themselves; paths *under* them are fine.
|
||||
- Stream large files with `fs.readLineAt` rather than `fs.readFile` — 380 KB total RAM.
|
||||
|
||||
### net
|
||||
|
||||
| Call | Returns |
|
||||
|---|---|
|
||||
| `net.wifiConnect()` | – ; async, poll `wifiStatus()` |
|
||||
| `net.wifiStatus()` | `"idle"` \| `"connecting"` \| `"connected"` \| `"failed"` |
|
||||
| `net.wifiDisconnect()` | – |
|
||||
| `net.get/head/delete(url [, headers])` | `body \| nil, statusCode` |
|
||||
| `net.post/patch(url, body [, headers])` | `body \| nil, statusCode` |
|
||||
| `net.download(url, destPath, options)` | `bytesWritten \| nil, errString` |
|
||||
| `net.urlencode(s)` | string |
|
||||
|
||||
- Never block on Wi-Fi: enter a `"connecting"` screen and poll from `draw()`.
|
||||
- Response bodies are capped at 50000 B and returned only for 2xx; status is `-1` on client failure.
|
||||
- `net.download` **requires** an options table; keys are `maxBytes` (required), `expectedSize`,
|
||||
`sha256` (64 hex). An unknown key is an error. It verifies size and hash for you — use it for
|
||||
anything installed to disk.
|
||||
- TLS does not verify certificates. Treat downloaded content as untrusted; validate before use.
|
||||
|
||||
### sys / log
|
||||
|
||||
`sys.millis()`, `sys.uptime()`, `sys.delay(ms)`, `sys.exit()`.
|
||||
`log.debug(msg)`, `log.info(msg)`, `log.error(msg)` — single string argument, no format args.
|
||||
- `log.*` take a single string — no format args.
|
||||
|
||||
## Style
|
||||
|
||||
@@ -163,3 +105,11 @@ Constraints that bite:
|
||||
with no line numbers.
|
||||
- Collect garbage (`collectgarbage("collect")`) before large allocations such as a catalog parse.
|
||||
- Match the existing 4-space indentation and the `if cond then return end` single-line guard style.
|
||||
|
||||
## Keeping this file honest
|
||||
|
||||
When a change makes something here wrong — a workflow, a constraint, a gotcha that no longer bites —
|
||||
update it in the same commit. A stale rule is worse than no rule.
|
||||
|
||||
That obligation covers corrections only. Anything new (a convention you'd like to establish, a
|
||||
section you think is missing) gets proposed to the user first; this file stays short by default.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
CROSSPOINT ?= ../crosspoint-reader
|
||||
STUB := .luals/crosspoint.lua
|
||||
|
||||
.PHONY: manifests check stubs
|
||||
|
||||
manifests:
|
||||
scripts/update-manifests.py
|
||||
|
||||
check:
|
||||
scripts/update-manifests.py --check
|
||||
|
||||
# Regenerates the LuaLS stub from the firmware bindings in the sibling repo,
|
||||
# then rewrites .luarc.json globals from whatever the stub declares.
|
||||
stubs:
|
||||
@test -f $(CROSSPOINT)/scripts/gen_lua_stubs.py || { \
|
||||
echo "ERROR: firmware repo not found at $(CROSSPOINT)"; \
|
||||
echo " clone crosspoint-reader beside this repo, or run: make stubs CROSSPOINT=/path/to/crosspoint-reader"; \
|
||||
exit 1; }
|
||||
cd $(CROSSPOINT) && uv run scripts/gen_lua_stubs.py
|
||||
cp $(CROSSPOINT)/data/lua/crosspoint.lua $(STUB)
|
||||
@globals=$$(sed -nE 's/^([A-Za-z_][A-Za-z0-9_]*) = (\{\}|0)$$/\1/p; s/^function ([A-Za-z_][A-Za-z0-9_]*)\(.*/\1/p' $(STUB) | sort -u | sed 's/.*/"&"/' | paste -sd, -); \
|
||||
printf '{\n "runtime": { "version": "Lua 5.4" },\n "workspace": { "library": [".luals"] },\n "diagnostics": { "globals": [%s] }\n}\n' "$$globals" > .luarc.json
|
||||
@echo "Updated $(STUB) and .luarc.json"
|
||||
@@ -6,10 +6,15 @@
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { nixpkgs, flake-utils, ... }:
|
||||
outputs =
|
||||
{ nixpkgs, flake-utils, ... }:
|
||||
flake-utils.lib.eachDefaultSystem (system: {
|
||||
devShells.default = nixpkgs.legacyPackages.${system}.mkShell {
|
||||
packages = with nixpkgs.legacyPackages.${system}; [ git uv ];
|
||||
packages = with nixpkgs.legacyPackages.${system}; [
|
||||
git
|
||||
uv
|
||||
gnumake
|
||||
];
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user