mirror of
https://github.com/ALLFATHER-BV/wadamesh.git
synced 2026-10-08 20:27:26 +00:00
* #305 the home Wi-Fi status was decided from WiFi.getMode(), not from what the user asked for, so anything leaving the radio in STA made it announce "Starting…" indefinitely with Wi-Fi switched off. Gate on wifiConfigGetRadioEnabled(). * #304 the Wi-Fi and Bluetooth status strings were raw literals and stayed English everywhere. Wrapped in TR(). Most already had translations sitting unused in the language files, so this costs translators nothing for "Connecting…", "SSID not found", "Off" and "Built-in"; only "Starting…", "Auth failed", "Link lost" and "Init…" are new keys. * #257 "Built-in" had two code paths and only the one you do NOT see went through TR(), which is why it looked inconsistent to the reporter. * #307 the theme-colour buttons were fixed at 124/84 px, measured against "Save & restart" / "Reset". Hungarian's "Mentés és újraindítás" ran off both edges. They size to their label now, in a wrapping row, so an over-long pair drops to two lines rather than clipping. * #306 the contact action sheet decided scrollability from a hand-maintained grid_items tally that the location-sharing button (#266) was never added to. When the tally is short the body is left unscrollable and the overflow is unreachable. Decide from the height the buttons actually reached instead, so the tally can drift harmlessly. SDK: wada.ui.text_w / text_lines. An app could ask how tall a line is but not how wide, so anything laying out its own rows had to guess whether a string would wrap; guessing wrong draws the next row on top. That is exactly what happened to the SDK Test app (fixed separately as store version 1.4, with Nearby hardened the same way). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
210 lines
8.7 KiB
Lua
210 lines
8.7 KiB
Lua
-- SDK self-test. Exercises the extended SDK so the results can be read off the
|
|
-- screen instead of inferred from a build log. Published to the store as a
|
|
-- developer/bench tool.
|
|
--
|
|
-- 1.2 adds wada.crypto (checked against published RFC vectors, so a PASS here is
|
|
-- real evidence and not just "it returned something"), wada.mesh.channels, and
|
|
-- wada.mesh.send_dm.
|
|
-- 1.3 adds the discovery surface (wada.mesh.discover / discovered), packet
|
|
-- identity in rx_log, exact micro-degree coordinates, wada.ui.input and the
|
|
-- windowed wada.fs.read.
|
|
-- 1.4 fixes rows drawing on top of one another. label:width() turns wrapping
|
|
-- on, so any line wider than the screen became two lines while the caller still
|
|
-- advanced by one, and the next row landed on top. Reported with a photo of the
|
|
-- channels line sitting across the contacts line. row() now owns the cursor and
|
|
-- advances by the height the text actually needs.
|
|
local ui, sys, store, timer = wada.ui, wada.sys, wada.store, wada.timer
|
|
local C = ui.colors
|
|
|
|
local app = {}
|
|
local rows, keyline, sendline, dmline, msgline = {}, nil, nil, nil, nil
|
|
local discline, inputline = nil, nil
|
|
local W = 300
|
|
|
|
-- row() owns the vertical cursor. Every caller used to pass a y and then add a
|
|
-- hand-picked constant, which is only correct while the text fits on one line.
|
|
-- An app cannot measure rendered text, so the wrapped line count is estimated
|
|
-- from a deliberately pessimistic character width: over-estimating costs a
|
|
-- little whitespace, under-estimating overlaps the next row.
|
|
local LH, GAP = 14, 3
|
|
local cy = 4
|
|
local function row(text, color, extra_gap)
|
|
local l = ui.label(text, 6, cy, 12, color or C.text)
|
|
l:width(W - 12)
|
|
rows[#rows + 1] = l
|
|
local cpl = math.max(16, (W - 12) // 7)
|
|
local n = 0
|
|
for seg in (tostring(text) .. "\n"):gmatch("(.-)\n") do
|
|
n = n + math.max(1, math.ceil(#seg / cpl))
|
|
end
|
|
cy = cy + math.max(1, n) * LH + GAP + (extra_gap or 0)
|
|
return l
|
|
end
|
|
local function yn(v) return v and "yes" or "NO" end
|
|
|
|
function app.on_open(w, h)
|
|
W = w or 300
|
|
ui.scroll(true)
|
|
cy = 4
|
|
local y
|
|
|
|
local c = sys.caps()
|
|
row("caps: sdk_ext=" .. yn(c.sdk_ext) .. " kbd=" .. yn(c.keyboard) ..
|
|
" touch=" .. yn(c.touch) .. " sd=" .. yn(c.sd), C.accent)
|
|
row("caps: discover=" .. yn(c.discover) .. " input=" .. yn(c.input) ..
|
|
" rx_identity=" .. yn(c.rx_identity), C.accent)
|
|
-- crypto: published test vectors, so this is checkable rather than merely alive
|
|
if wada.crypto then
|
|
local hex = wada.crypto.hex
|
|
local k20 = string.rep(string.char(0x0b), 20)
|
|
local checks = {
|
|
{ "sha256('abc')", hex(wada.crypto.sha256("abc")),
|
|
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" },
|
|
{ "sha1('abc')", hex(wada.crypto.sha1("abc")),
|
|
"a9993e364706816aba3e25717850c26c9cd0d89d" },
|
|
{ "hmac_sha1 RFC2202#1", hex(wada.crypto.hmac_sha1(k20, "Hi There")),
|
|
"b617318655057264e28bc0b6fb378c8ef146be00" },
|
|
{ "hmac_sha256 RFC4231#1", hex(wada.crypto.hmac_sha256(k20, "Hi There")),
|
|
"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7" },
|
|
}
|
|
local allok = true
|
|
for _, t in ipairs(checks) do
|
|
local ok = (t[2] == t[3])
|
|
if not ok then allok = false end
|
|
row("crypto " .. t[1] .. ": " .. (ok and "PASS" or ("FAIL got " .. tostring(t[2]):sub(1, 16))),
|
|
ok and C.good or C.bad)
|
|
end
|
|
-- The point of having it in C: this loop would blow the instruction budget in Lua.
|
|
local t0 = sys.millis()
|
|
for _ = 1, 200 do wada.crypto.hmac_sha1(k20, "Hi There") end
|
|
row(string.format("crypto: 200 x hmac_sha1 in %d ms%s", sys.millis() - t0,
|
|
allok and "" or " (VECTORS FAILED)"), allok and C.good or C.bad)
|
|
else
|
|
row("wada.crypto: MISSING", C.bad)
|
|
end
|
|
|
|
-- channel discovery
|
|
local chans = wada.mesh.channels and wada.mesh.channels() or nil
|
|
if chans then
|
|
local names = table.concat(chans, ", ")
|
|
row("channels(): " .. #chans .. " " .. names:sub(1, 60), C.text)
|
|
else
|
|
row("wada.mesh.channels: MISSING", C.bad)
|
|
end
|
|
|
|
if not c.sdk_ext then
|
|
row("extended SDK is OFF on this board - stopping here.", C.sub)
|
|
return
|
|
end
|
|
|
|
-- contacts, so send_dm has a target to name. 1.3 also checks the pubkey field.
|
|
local cts = wada.mesh.contacts()
|
|
local first = cts[1] and cts[1].name or nil
|
|
row("contacts: " .. #cts .. (first and (" first=" .. first) or ""), C.text)
|
|
if cts[1] then
|
|
local pk = cts[1].pubkey
|
|
row("contacts[1].pubkey: " .. tostring(pk) ..
|
|
(type(pk) == "string" and #pk == 8 and " PASS" or " FAIL"),
|
|
(type(pk) == "string" and #pk == 8) and C.good or C.bad)
|
|
end
|
|
|
|
local me = wada.mesh.self()
|
|
row("self: " .. tostring(me.name) .. " pubkey=" .. tostring(me.pubkey), C.text)
|
|
-- Exact coordinates. Lua's floats here are 32-bit, so an app that logs a track
|
|
-- must use the _e6 integers; this proves they are present and consistent.
|
|
local fix = sys.gps()
|
|
if fix then
|
|
local drift = math.abs(fix.lat_e6 / 1e6 - fix.lat)
|
|
row(string.format("gps: %d,%d e6 alt %dm %d sats drift %.6f %s",
|
|
fix.lat_e6, fix.lon_e6, fix.alt_m or 0, fix.sats, drift,
|
|
drift < 0.001 and "PASS" or "FAIL"), drift < 0.001 and C.good or C.bad)
|
|
else
|
|
row("gps: no fix (normal indoors)", C.sub)
|
|
end
|
|
|
|
-- rx_log identity: adverts carry a real public key, addressed frames carry
|
|
-- one-byte hashes, everything else carries nothing. All three are correct.
|
|
local log = wada.mesh.rx_log()
|
|
local withpk, withsrc = 0, 0
|
|
for _, r in ipairs(log) do
|
|
if r.pubkey then withpk = withpk + 1 end
|
|
if r.src then withsrc = withsrc + 1 end
|
|
end
|
|
row(string.format("rx_log: %d frames, %d with a pubkey (adverts), %d with src/dst",
|
|
#log, withpk, withsrc), #log > 0 and C.text or C.sub)
|
|
-- fs: windowed read. Writes once (the 1/sec limit means one write per open).
|
|
if wada.fs then
|
|
local probe = "0123456789abcdef"
|
|
wada.fs.write("sdktest.bin", probe)
|
|
local part, total = wada.fs.read("sdktest.bin", 4, 4)
|
|
local ok = (part == "4567" and total == #probe)
|
|
row("fs.read(name,4,4): " .. tostring(part) .. " total=" .. tostring(total) ..
|
|
(ok and " PASS" or " FAIL"), ok and C.good or C.bad)
|
|
end
|
|
|
|
sendline = row("mesh.send: not tried yet", C.sub)
|
|
dmline = row("mesh.send_dm: not tried yet", C.sub)
|
|
msgline = row("on_message: waiting (needs the read permissions)", C.sub)
|
|
y = cy + 4
|
|
ui.button("Send to Public", 6, y, 120, 30, function()
|
|
local ok, err = wada.mesh.send("Public", "wadamesh SDK self-test")
|
|
sendline:set("mesh.send: " .. tostring(ok) .. " " .. tostring(err))
|
|
sendline:color(ok and C.good or C.bad)
|
|
end)
|
|
ui.button("DM first contact", 132, y, 130, 30, function()
|
|
if not first then dmline:set("mesh.send_dm: no contacts to target"); dmline:color(C.bad); return end
|
|
local ok, err = wada.mesh.send_dm(first, "wadamesh SDK self-test (DM)")
|
|
dmline:set("mesh.send_dm -> " .. first .. ": " .. tostring(ok) .. " " .. tostring(err))
|
|
dmline:color(ok and C.good or C.bad)
|
|
end)
|
|
cy = y + 38
|
|
discline = row("mesh.discover: not tried yet", C.sub)
|
|
inputline = row("ui.input: not tried yet", C.sub)
|
|
-- Probing TRANSMITS and makes every neighbour reply, so it is a button, not
|
|
-- something this app does on open.
|
|
y = cy + 4
|
|
ui.button("Probe", 6, y, 90, 30, function()
|
|
local tag, err = wada.mesh.discover()
|
|
if not tag then
|
|
discline:set("mesh.discover: " .. tostring(err)); discline:color(C.bad); return
|
|
end
|
|
discline:set("mesh.discover: sent, waiting for replies..."); discline:color(C.text)
|
|
end)
|
|
ui.button("Results", 102, y, 90, 30, function()
|
|
local hits = wada.mesh.discovered()
|
|
if #hits == 0 then
|
|
discline:set("discovered(): nothing yet - probe, then wait a few seconds")
|
|
discline:color(C.sub); return
|
|
end
|
|
local h = hits[1]
|
|
discline:set(string.format("discovered(): %d first %s snr %.1f/%.1f %s",
|
|
#hits, h.name or h.pubkey, h.snr, h.their_snr, h.direct and "direct" or (h.hops .. "h")))
|
|
discline:color(C.good)
|
|
end)
|
|
y = y + 34
|
|
ui.button("Beep", 6, y, 70, 30, function() sys.beep() end)
|
|
ui.button("Input", 82, y, 90, 30, function()
|
|
ui.input("Type anything", "hello", function(text)
|
|
inputline:set("ui.input -> " .. (text and ("'" .. text .. "'") or "cancelled"))
|
|
inputline:color(text and C.good or C.sub)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
function app.on_input(ev)
|
|
if ev.type == "key" and keyline then
|
|
keyline:set("keys: got '" .. tostring(ev.key) .. "'")
|
|
keyline:color(C.good)
|
|
end
|
|
end
|
|
|
|
-- Proves the kind field and that DMs/rooms reach an app at all, not just channels.
|
|
function app.on_message(m)
|
|
if not msgline then return end
|
|
msgline:set("on_message: kind=" .. tostring(m.kind) .. " from=" .. tostring(m.sender) ..
|
|
" ch=" .. tostring(m.channel) .. " text=" .. tostring(m.text):sub(1, 20))
|
|
msgline:color(C.good)
|
|
end
|
|
|
|
return app
|