Home is a grid of arrangeable widgets (24 kinds, presets, a layout per orientation, Lua app widgets via wada.widget); Unread, Chat and Mentions get a large size. Theme > Background puts an aurora (still by default, or moving), a colour, a picture or nothing behind every screen, with glass cards and a smoked-glass top bar. A GPS icon in the bar, a GPS battery saver (u-blox GNSS stop/start where there is no power switch), and a battery saver that no longer waits for Bluetooth and Wi-Fi to be off. Also: navigation bar position, every settings page on the redesign's cards, a faster app drawer, slower screensaver drift for burn-in, text wrapping at spaces only, the V4-R8 Store Apps tab, and SDK Test marked extended-SDK only (keeps the 2 MB V4 at 99.8%). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
106 lines
3.8 KiB
Lua
106 lines
3.8 KiB
Lua
-- Airtime (Lua) — channel utilization over time: live + average bars with a %
|
|
-- axis, rx/tx air split, duty ceiling, tx budget and packet counters. While it
|
|
-- runs it also keeps its Home widget current (wada.widget, extended SDK boards).
|
|
local ui, sys, mesh, timer = wada.ui, wada.sys, wada.mesh, wada.timer
|
|
local widget = wada.widget -- nil on firmware or boards without it
|
|
-- Translate through the device's language table (wada.sys.tr, added for #257).
|
|
-- Older firmware has no sys.tr, so fall back to the English string rather than error.
|
|
local tr = sys.tr or function(x) return x end
|
|
local C = ui.colors
|
|
local app = {}
|
|
|
|
local W, narrow
|
|
local head, sub, legend, detail, chart
|
|
local base, t0, last
|
|
local peak_pct = 0
|
|
local spark, wlast = {}, 0 -- the Home widget's curve (one point per 5 s)
|
|
|
|
local function restart_window()
|
|
local st = mesh.stats()
|
|
base = { rx = st.rx_air_s, tx = st.tx_air_s }
|
|
t0 = sys.millis()
|
|
last = { ms = t0, rx = st.rx_air_s, tx = st.tx_air_s }
|
|
peak_pct = 0
|
|
chart:fill(1, 0)
|
|
chart:fill(2, 0)
|
|
head:set(tr("sampling..."))
|
|
head:color(C.accent)
|
|
sys.toast(tr("window restarted"), 900)
|
|
end
|
|
|
|
local function refresh()
|
|
local st = mesh.stats()
|
|
local now = sys.millis()
|
|
local wall = (now - t0) / 1000
|
|
if wall < 0.5 then return end
|
|
|
|
local rx_air, tx_air = st.rx_air_s - base.rx, st.tx_air_s - base.tx
|
|
local avg = math.min(100, (rx_air + tx_air) * 100 / wall)
|
|
|
|
local dw = (now - last.ms) / 1000
|
|
local live = 0
|
|
if dw > 0 then
|
|
live = math.min(100, ((st.rx_air_s - last.rx) + (st.tx_air_s - last.tx)) * 100 / dw)
|
|
end
|
|
last = { ms = now, rx = st.rx_air_s, tx = st.tx_air_s }
|
|
if live > peak_pct then peak_pct = live end
|
|
|
|
chart:push(1, math.floor(live))
|
|
chart:push(2, math.floor(avg))
|
|
|
|
head:set(string.format(tr("channel busy %.1f%%"), live))
|
|
head:color(live > 50 and C.bad or live > 20 and 0xE8A33D or C.good)
|
|
sub:set(string.format(tr("avg %.1f%% peak %.1f%% over %ds"), avg, peak_pct, math.floor(wall)))
|
|
if widget and now - wlast >= 5000 then
|
|
wlast = now
|
|
spark[#spark + 1] = math.floor(live * 10)
|
|
if #spark > 24 then table.remove(spark, 1) end
|
|
widget.set{ title = tr("Channel busy"), value = string.format("%.1f%%", live),
|
|
line = string.format(tr("avg %.1f%%, peak %.1f%%"), avg, peak_pct), spark = spark }
|
|
end
|
|
if narrow then
|
|
detail:set(string.format("air rx %ds / tx %ds\nduty %d%% budget %dms\ntx %d rx %d err %d",
|
|
rx_air, tx_air, st.duty_pct, st.tx_budget_ms, st.tx_pkts, st.rx_pkts, st.rx_err))
|
|
else
|
|
detail:set(string.format("air rx %ds / tx %ds duty %d%% budget %dms\ntx %d rx %d err %d drop %d SF%d",
|
|
rx_air, tx_air, st.duty_pct, st.tx_budget_ms, st.tx_pkts, st.rx_pkts, st.rx_err, st.rx_dropped, st.sf))
|
|
end
|
|
end
|
|
|
|
function app.on_open(w, h)
|
|
W, narrow = w, w < 280
|
|
local LH12, LH16 = ui.text_h(12), ui.text_h(16)
|
|
|
|
local y = 2
|
|
head = ui.label(tr("sampling..."), 4, y, 16, C.accent); head:width(w - 86)
|
|
ui.button(tr("Reset"), w - 76, y, 70, 30, function() restart_window() end) -- top-right
|
|
y = y + LH16 + 4
|
|
sub = ui.label("", 4, y, 12, C.sub); sub:width(w - 10)
|
|
y = y + LH12 + 5
|
|
|
|
local AX = 34
|
|
local det_lines = narrow and 3 or 2
|
|
local ch_h = h - y - (det_lines * LH12) - LH12 - 14
|
|
if ch_h < 48 then ch_h = 48 end
|
|
if ch_h > 110 then ch_h = 110 end
|
|
chart = ui.chart(w - AX - 8, ch_h, 60, C.accent, 0x4F9DF7, true)
|
|
chart:pos(AX, y)
|
|
chart:range(0, 100)
|
|
chart:axis(3, AX)
|
|
y = y + ch_h + 4
|
|
|
|
legend = ui.label(tr("teal = now blue = average"), 4, y, 12, C.sub); legend:width(w - 10)
|
|
y = y + LH12 + 2
|
|
detail = ui.label("", 4, y, 12, C.text); detail:width(w - 10)
|
|
|
|
local st = mesh.stats()
|
|
base = { rx = st.rx_air_s, tx = st.tx_air_s }
|
|
t0 = sys.millis()
|
|
last = { ms = t0, rx = st.rx_air_s, tx = st.tx_air_s }
|
|
timer.every(1000)
|
|
end
|
|
|
|
function app.on_tick(dt) refresh() end
|
|
|
|
return app
|