Files
slate32/test/settings_calibration.lua
T
evan fce0dfb70a feat(ble): add a NimBLE provider and fold its controls into Settings
Bluedroid cannot initialize beside the Lua runtime on this heap, so the
provider is NimBLE-Arduino. Scans keep the six strongest devices in a fixed
buffer rather than growing one. The standalone BLE app is gone: it was a
screen of toggles that belongs next to the other device settings.
2026-08-04 13:11:17 -04:00

129 lines
5.0 KiB
Lua

-- Run: lua test/settings_calibration.lua
-- Drives the settings app on a desktop Lua, through the shared fake device. Controls are
-- pressed by label rather than by coordinate: where a row lands is layout, and layout is
-- asserted in test/ui_layout_test.cpp against the C++ the panel actually runs.
package.path = "sdcard/.lua/lib/?.lua;test/?.lua;" .. package.path
local device = require("fake_device").install()
dofile("sdcard/.lua/apps/Settings/main.lua")
local tapRow = device.tap
-- A perfectly linear panel spanning raw 200..3800 over 320x480 must round-trip
-- to those same extremes from the two inset samples.
local inset, w, h = 30, 320, 480
local function raw(pixel, size) return 200 + (3800 - 200) * pixel / size end
local s1 = {x = raw(inset, w), y = raw(inset, h)}
local s2 = {x = raw(w - inset, w), y = raw(h - inset, h)}
local x0, y0, x1, y1 = computeCalibration(s1, s2, w, h, inset)
assert(math.abs(x0 - 200) <= 1, "x0 " .. x0)
assert(math.abs(y0 - 200) <= 1, "y0 " .. y0)
assert(math.abs(x1 - 3800) <= 1, "x1 " .. x1)
assert(math.abs(y1 - 3800) <= 1, "y1 " .. y1)
-- A flipped panel (raw decreasing with pixel) must yield a descending range.
local s3 = {x = 3800 - raw(inset, w) + 200, y = s1.y}
local s4 = {x = 3800 - raw(w - inset, w) + 200, y = s2.y}
local fx0, _, fx1 = computeCalibration(s3, s4, w, h, inset)
assert(fx0 > fx1, "flipped axis should descend")
-- Timezone cycles through the picker list.
local zones = require("timezones")
init()
tapRow("Timezone")
assert(settings.getTimezone() == zones[2].tz, "timezone " .. settings.getTimezone())
-- The card's value is replaced in place rather than by rebuilding the screen, so the
-- other four cards keep the nodes they already had.
local beforeCycle = node.getCount()
assert(device.labelled(zones[2].name), "timezone card did not take its new value")
assert(node.getCount() == beforeCycle, "cycling the timezone rebuilt the screen")
tapRow("Timezone")
assert(settings.getTimezone() == zones[3].tz, "timezone " .. settings.getTimezone())
-- Rotation cycles through the four quarter turns and wraps back to 0.
init()
for _, expected in ipairs({90, 180, 270, 0}) do
tapRow("Rotation")
assert(settings.getRotation() == expected, "rotation " .. settings.getRotation())
end
-- Calibration collects one sample per target and saves on the second release.
init()
tapRow("Calibrate")
tick() -- release after the menu tap arms sampling
device.raw = {s1.x, s1.y}
tick()
device.raw = nil
tick()
device.raw = {s2.x, s2.y}
tick()
device.raw = nil
tick()
local saved = device.calibration
assert(saved, "calibration was not saved")
assert(math.abs(saved[1] - 200) <= 1, "saved x0 " .. saved[1])
-- WiFi controls reflect connection state without exposing configured network details on the card.
device.status = {state = "disconnected", ssid = "", ip = "", rssi = 0}
init()
tapRow("WiFi")
assert(not device.find("connect") and not device.find("disconnect"), "unconfigured wifi has no toggle")
device.status = {state = "disconnected", ssid = "saved", ip = "", rssi = 0}
init()
tapRow("WiFi")
assert(device.find("connect") and not device.find("disconnect"), "configured wifi can reconnect")
tapRow("connect")
assert(device.wifiReconnect, "wifi reconnect should use saved credentials")
device.status = {state = "connected", ssid = "saved", ip = "192.168.1.2", rssi = -40}
init()
tapRow("WiFi")
assert(device.find("disconnect") and not device.find("connect"), "connected wifi can disconnect")
tapRow("disconnect")
assert(device.status.state == "disconnected" and device.status.ssid == "saved", "disconnect preserves wifi intent")
-- Open networks connect directly from scan results.
init()
device.networks = {{ssid = "qemu", rssi = -25, secure = false}}
tapRow("WiFi")
tapRow("scan networks")
tick()
tapRow("qemu")
assert(device.connected, "open network should connect without a keyboard")
assert(device.connected[1] == "qemu" and device.connected[2] == "", "open wifi connect")
-- Secure networks route through the keyboard and preserve typed punctuation.
init()
device.networks = {{ssid = "secure", rssi = -40, secure = true}}
tapRow("WiFi")
tapRow("scan networks")
tick()
tapRow("secure")
tick()
-- The keyboard is one custom node with no child per key, so its keys are reached through
-- its own geometry rather than by label. getRect() answers the same box to the test and
-- to the widget, which is what makes the two agree on where "q" is.
local keyboard = require("keyboard")
local board = device.findKind("custom")
assert(board, "the secure network did not open a keyboard")
local boardRect = {x = 0, y = 0, w = 10000, h = 10000}
local function typeKey(label)
local pressed
keyboard.eachKey("lower", boardRect, function(_, keyLabel, x, y, width)
if keyLabel == label and not pressed then pressed = {x = x + width // 2, y = y + 15} end
end)
assert(pressed, "no key labelled " .. label)
device.press(board, pressed.x, pressed.y)
end
for key in ("qemuqemu"):gmatch(".") do typeKey(key) end
typeKey("OK")
assert(device.connected[1] == "secure" and device.connected[2] == "qemuqemu", "secure wifi password")
print("ok")