Skip to content

New LUA scripts & widgets - #3

Open
jurgelenas wants to merge 236 commits into
masterfrom
unified-lua-lsp
Open

New LUA scripts & widgets#3
jurgelenas wants to merge 236 commits into
masterfrom
unified-lua-lsp

Conversation

@jurgelenas

Copy link
Copy Markdown
Member

No description provided.

jurgelenas and others added 29 commits August 16, 2026 20:33
It sat directly under TX Power showing the same fact in another unit,
and it was not the module's own index: a linear scan over a hardcoded
power table that answered 7 for anything it did not recognise, so a
module with a power level outside the list reported the top index.

TX Power in milliwatts is the number that means something to a pilot.
getFieldInfo returns an ID that encodes a slot in the current model's
sensor list -- api_general.cpp resolves it as
g_model.telemetrySensors[(id - MIXSRC_FIRST_TELEM) / 3] -- but the
cache keyed on the sensor name lives in the CRSF singleton, which is a
global in the widget Lua state and so outlives every model change.

So "RQly" resolved at slot 3 under one model kept addressing slot 3
under the next, where it may be a different sensor entirely, for the
rest of the session. Nothing invalidated it, because until now nothing
in the widget observed the model-change edge.

The telemetry widget now has that edge, in resetModel(), so the cache
is dropped there. Pre-existing; only visible after switching models
with differently ordered sensor lists.
Four things that read as copy-paste divergence rather than intent:

borderPad = 0 on the 1/4 tier's rows was present on sd and hd and
absent on small and sd_tall, with nothing explaining why the same tier
should be padded differently on two screens out of four. Now on all
four. portrait keeps one, because its second row is a bare label rather
than a box.

small.lua read WidgetUI.fonts.<tier>.hero directly where its siblings
go through heroFont(), so a heroStatus added to its font table would
have been silently ignored -- the shrink-while-a-status-shows contract
would not have held on that screen alone.

hd.lua wrote a bare 0 for a font where every sibling writes the name.
It is STDSIZE; VTX Admin's hd.lua had the same wart next to a correct
STDSIZE in the same table, which is what makes it a slip rather than a
choice.

portrait.lua's appendDataRows said it was shared by the 1/2 and 1/1
tiers, but portrait has no halfH breakpoint and no buildHalf.

Verified: sd on TX16S and sd_tall on Jumper T15 both still render.
The architecture section tabled both tools and every library module but
said nothing about the widgets at all, and the README elided their ui/
trees behind a "...". The layering the widgets actually use -- entry
point, domain, wiring, read model, full-screen page, per-screen layouts
-- was only discoverable by reading the loadScript calls.

Records the two rules that hold across both widgets: the per-screen
files are self-contained so shared presentation belongs in
ui/display.lua, and no ui/ file receives the CRSF transport. Also
records what sharing the telemetry state costs, so the next person to
touch drain(), update() or a frame handler knows which of the three
invariants they are standing on.
The rate tables carry a literal 0 for the rates ExpressLRS publishes no
sensitivity figure for, and RfModes.floor returned it unchanged. Its own
docstring says otherwise, and so does every caller: telemetry.lua guards
with `or -128`, which 0 -- a truthy number -- walks straight past. The
range percentage then divided by (0 + 50) on nine of the v4 rates and two
of the v3 ones.
Range % answered a question nobody asks: it counted *down* from a full
bar as the link got better, and it was a percentage of a distance that
was never measured. What a pilot wants is how much signal is left before
the receiver stops hearing, and ExpressLRS publishes exactly that -- a
rated sensitivity floor per packet rate, already in rf_modes.lua.

So the smoother now produces headroomPct with the polarity the right way
up, the detail line prints the pair the comparison needs (-93 / -108 dBm)
instead of an invented percentage, and its warning colour is driven by
link margin in dB -- the unit the number is actually measured in -- on
the same thresholds range % used. The full-screen page gains Sensitivity
and Link Margin rows directly under the RSSI ones, sharing their unit so
the arithmetic between the three is visible.

Also samples TQly and TRSS, the downlink pair, and promotes the status
ladder out of Display.statusText() into Telemetry.statusLevel(), so the
words and the colours to come index one value rather than two copies of
the same if-chain.
TQly and TRSS are the RX->TX telemetry path, and nothing could exercise
them because no scenario served them. They sit a few dB behind the uplink
everywhere, which is the asymmetry worth showing: the receiver answers at
a fraction of the module's power.

The unrated_rate scenario reaches the other untested branch. Every
connected scenario ran RFMD 7, which is rated, so the fallback for a
packet rate ExpressLRS publishes no sensitivity figure for was
unreachable in the simulator -- which is how RfModes.floor came to return
0 for it unnoticed.
The minimized widget was four left-aligned labels on a flat rectangle,
with no bar, gauge, rule or indicator anywhere in src/WIDGETS. This gives
the 1/1 tier the instruments the data has always deserved: a status LED,
the uplink as a headline LQ over two full-width bars, a stepped TX power
meter, antenna cells, and a captioned row for the downlink and battery.

The uplink is deliberately unheadered. It is what you fly on -- a
degraded uplink loses control where a degraded downlink only makes the
numbers stale -- so it takes the upper body in a register nothing else
competes with, and the downlink is one captioned row underneath.

ui/components.lua carries the drawing and the composition, so each screen
file's buildFull is the four lines that differ: the hero font and the
width gate. Everything is label, rectangle and circle, the only types
lvgl.build() will accept in a widget zone, and nothing calls build()
until the tree is complete.

Two things measured rather than assumed. Font line heights come from
lcd.sizeText, which still works under LVGL, because the 800x480 target
ships different fonts and a scaled 480x272 number would be wrong there.
And whether the downlink and battery groups share a row is decided by
their measured widths, not a breakpoint -- on the same TX16S the answer
differs between a 396px zone and a 198px one, and the ladder takes the
dBm unit off first, then the pack total, then splits the rows, and only
then drops the DOWNLINK header, which it can do at all because TQly and
TRSS name themselves.
GREEN and RED are raw primaries -- RGB(0,255,0) and RGB(255,0,0),
colors.cpp:51-54 -- so a full-width LQ bar in GREEN was the loudest thing
on the screen, and it stayed that way whatever theme the pilot had
chosen. The theme carries the same three meanings in colours picked to
sit together: EDIT, ACTIVE and WARNING. Bars, the status LED and the LQ
headline now take those and repaint with the theme.

Text keeps a separate ramp. COLOR_THEME_ACTIVE is a bright yellow, which
is fine as a bar fill and illegible as a word on the light themes'
near-white panel, so warnings in text stay ORANGE.

Bar tracks are the disabled grey at part opacity. Solid it read as a
second bar competing with its own fill; the theme's soft secondary
disappeared into the panel entirely, which left the fill a floating stub
with nothing to measure it against.

The 1/2 tier keeps both bars, which is the point of the layout -- it is
the size the widget is usually placed at, and the mirrored-blocks draft
this replaced could not keep even one there. The battery row drops before
either bar is squeezed below the height at which it stops being a bar.
TQly sits beside TRSS on the same row in percent, so a bare -94 there
invites reading it as one too. The unit is not something the width ladder
gets to trade away.

It was the first thing to go, which left the narrowest rung showing
"TRSS -94". Now the pack total goes first, then the row splits, and the
DOWNLINK header is what drops last -- the right order, because TQly and
TRSS name themselves and a bare number does not name its unit.

Costs nothing at the sizes that matter: the 396px zone still fits the row
under its header, and the 198px one reaches the headerless rung it was
already using.
Both were COLOR_THEME_PRIMARY1, which is the text colour -- RGB(0,0,0) on
the light themes. As a glyph that is correct and as a filled block it is a
black bar dropped onto a blue panel, which is what they looked like.

COLOR_THEME_FOCUS is what EdgeTX uses everywhere else to mean "this one is
active", which is exactly what a lit power cell and the live antenna cell
are saying, and it follows the theme.

Power deliberately stays off the green/amber/red ramp: 500 mW is not
worse than 50, so a health colour there would be claiming something the
number does not mean.

The mismatch banners move from RED to COLOR_THEME_WARNING with them, so
the whole widget now draws from the theme. The single exception is ORANGE
for warning text, because COLOR_THEME_ACTIVE is a yellow that cannot be
read on a near-white panel.
Four adjustments from looking at it on the radio rather than in the plan.

The status LED was centred on the strip height, which is the text's line
box plus padding, so it sat below the RF mode beside it -- near enough to
read as a mistake rather than a choice. It now centres on the line box,
landing on the same centre as the antenna cells, and it is a third
smaller: at half the row height an indicator stops being a dot and starts
competing with the text. It also gets a fixed lane, so its radius can be
tuned without the RF mode moving sideways.

TX POWER's meter was absorbing the whole gap between the RF mode and the
antenna cells, which dragged its own reading to the far side of the strip
with nothing tying the two together. The meter now has a natural width
measured off the row, the reading sits a fixed gap after it, and the slack
stays to their right. Whether the meter fits at all is now measured
against the antenna cells' position instead of assumed from the width
gate.

The panel is inset from the zone rather than filling it, so two widgets
side by side no longer share one edge, and the content is inset again from
the panel's own border -- full-width bars starting and ending on the
border read as spilling out of the panel.

And at half width TX POWER moves above the uplink panel instead of below
the group rows. It is link configuration, like the RF mode and the antenna
it now sits with; last place filed it under the flight controller's
readings, which is not what it is.
Remove the ELRS handset address distinction
At 400K baud the module splits a command answer into 27-byte chunks and
serves the continuation only on CMD_QUERY. The session hurried the read
deadline instead of the query one, so every extra chunk waited for the
2 s keep-alive: a confirm dialog took 2 s to appear, and again after
Confirm. Query the next chunk on the next tick, as the legacy script did.

Route every command step through one send path that retries a push the
radio's single output slot refused and resets chunk state only after a
successful send, so a one-chunk idle answer after a chunked executing
one is no longer swallowed and Bind dismisses on its own. The click guard
keys on the live popup, not on the field's stale status.

Both UIs show "Sending..." while a click or confirm is unanswered, after
a 200 ms grace so healthy links go straight to the dialog. The B&W popup
repaints the page after a cancel.

The simulator now chunks command answers and serves continuation chunks
only on CMD_QUERY like the firmware, with the firmware's 2 s timeout;
maxPacketBytes = 35 reproduces the 400K/500 Hz case.
@CapnBry

CapnBry commented Sep 8, 2026

Copy link
Copy Markdown
Member

Whew. A lot to get through but almost all these are small tweaks. I really like the visual redo you did on the VTX Admin, it looks great and the widget is a lot easier to glance at now. This took the better part of 2 days to get through all the code again with so many different scripts on so many platforms, but here's the abridged review notes!

  • Deleting elrs.lua? Why did we add it to this repo if it wasn't going to live here? I'm fine with however we think we should handle it but this is the Lua repo so I think we should move it to a legacy folder, merge the main repo's version in here, remove it from the main repo. For this PR I'd just leave it where it is currently and then a separate PR to deal with it.
  • Remove edgetx-lua-stdlib
  • Bind Tool - UID_MAX_ATTEMPTS is too low, I got MANY "no response" while the module rebooted. Maybe 10-12 instead of 6?
  • B&W Bind - Title should be "ExpressLRS Bind" and version number goes in the EXIT string for consistency with main tools lua (use same format too?)
  • Why do the B&W tools scripts require an EdgeTX version? Isn't the version gating just for proper lvgl support?
  • "alert" is the same thing as "dialog" except one is B&W and the other is lvgl. Should they be named the same?
  • Why aren't the version and nomodule messages in alert instead of multiple copies in the UI? Telemtry/VTX admin widgets fullscreen UI also duplicates the nomodule message.
  • The Dialogs.showNoModule minumum bauds are incorrect. These are suggested bauds. Also Baud should not be caps. It also does not include the bit about setting the "internal RF type" in sys either and perhaps F1000 should be 1000Hz because there are many 1000Hz modes.
  • TextEdit - No way to select next character or back up?
    if event == EVT_VIRTUAL_PREV_PAGE then
      self.cur = math.max(self.cur - 1, 1)
    elseif event == EVT_VIRTUAL_NEXT_PAGE then
      self.cur = math.min(self.cur + 1, math.min(self.maxLen, #self.value + 1))
    end
    
  • TextEdit - Maybe also move the things that return true to the top and everything else that returns nil just fall through to the return nil? Also return nil is unneeded
  • VTX Admin top bar UI - Can it look more like a "Value" type builtin widget? Same font sizes as the builtin does, with small "VTX" and large band/channel, left aligned to match the same visual style
  • Telem Widget - Align values vertically for PWR/BATT and TQly/TRSS.
  • Telem Widget - PWR reads 0 mW when not connected but others show --
  • Telem Widget - No last GPS position on disconnect? If not in your design plans that's fine, something for later.
  • Telem Widget - Fullscreen - Maybe merge RSSI 1 / RSSI 2 onto one line so the RSNR can be added?
  • Telem Widget - 1x4 layout TX16S with all EdgeTX bars (trim, pots, etc) still on, LQ wraps to 2 lines? Honestly it is impressive that this is the only visual glitch I ran into when testing every shape. I bet this was a ton of work to make each one look right!

AIness

  • README.md is loaded with AI bloat about directory structure. This is user-facing documentation and this does not help the user. I mean this whole thing is bloated with describing what every button does and what screen resolutions are supported by one widget and bullshit like that. Please just do this by hand and don't ask AI to do it. I can also do it if you'd prefer
    • ExpressLRS Lua Scripts - I think we can change this to just one line about what the repo is. No need to list the things that are in it, they are listed below
    • Features - remove
    • Installation section - First line is good, the full directory structure needs to go
    • Section for each tool / widget is good, but remove all the AI explanation of how it works, just a single sentence about what each thing is and screenshots are good
    • Compatibility. Good, but the bit about the bind manager "The bind phrase manager additionally requires ExpressLRS 4.1+" (delete the rest)
  • You gotta get stop letting the AI generate novels about everything, Like what is all the garbage at the top of every file that instead of just saying what the file is, it goes on about minor architecture features it seems to be proud of. Even the function comments read like a 4 year old who just read a book on design patterns and obfuscate the meaning. Comments are only supposed to say as much as they need to, not describe the history of code. Sometimes it wants to add the license, sometimes it wants to tell you what EdgeTX version was important when the header was added but is no longer correct, sometimes it goes on rambling story. You don't have to change these but in the future please try to reduce comments to only meaningful important things that aren't obvious.

ELRS Version requirement

We know what the ELRS version is from the deviceinfo header, and we know what version the script requires, but these scripts just sort of fail to work properly if the ELRS version isn't high enough. It could display a message about it.

*** DO NOT MAKE THAT CHANGE, I DO NOT WANT TO REVIEW NEW FEATURE CODE AGAIN *** this could be done in a separate PR if you want. Somebody should though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants