Skip to content

Commit f39b601

Browse files
committed
Merge branch 'unified-lua-lsp-strings' into unified-lua-lsp
2 parents 10d5bfb + 770c5f9 commit f39b601

4 files changed

Lines changed: 165 additions & 38 deletions

File tree

src/SCRIPTS/TOOLS/ExpressLRS/protocol.lua

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ function Protocol.fieldFloatLoad(field, data, offset)
420420
field.prec = 3
421421
end
422422
field.step = Protocol.fieldGetValue(data, offset + 17, 4)
423-
field.fmt = shim.tableConcat({ "%.", tostring(field.prec), "f", field.unit or "" })
423+
field.fmt = shim.tableConcat({ "%.", tostring(field.prec), "f" })
424424
field.prec = 10 ^ field.prec
425425
end
426426

@@ -483,6 +483,23 @@ function Protocol.fieldIntSave(field)
483483
Protocol.push(Protocol.CRSF.FRAMETYPE_PARAMETER_WRITE, frame)
484484
end
485485

486+
function Protocol.fieldStringSave(field)
487+
local frame = { Protocol.deviceId, Protocol.handsetId, field.id }
488+
local val = field.value or ""
489+
local maxlen = field.maxlen or 32
490+
if #val > maxlen then
491+
val = string.sub(val, 1, maxlen)
492+
end
493+
for i = 1, #val do
494+
local b = string.byte(val, i)
495+
if b ~= 0 then
496+
frame[#frame + 1] = b
497+
end
498+
end
499+
frame[#frame + 1] = 0
500+
Protocol.push(Protocol.CRSF.FRAMETYPE_PARAMETER_WRITE, frame)
501+
end
502+
486503
-- ============================================================================
487504
-- Related fields reload (for value changes)
488505
-- ============================================================================
@@ -575,7 +592,7 @@ Protocol.handlers = {
575592
[Protocol.CRSF.INT64 + 1] = nil,
576593
[Protocol.CRSF.FLOAT + 1] = { load = Protocol.fieldFloatLoad, save = Protocol.fieldIntSave },
577594
[Protocol.CRSF.TEXT_SELECTION + 1] = { load = Protocol.fieldTextSelLoad, save = Protocol.fieldIntSave },
578-
[Protocol.CRSF.STRING + 1] = { load = Protocol.fieldStringLoad, save = nil },
595+
[Protocol.CRSF.STRING + 1] = { load = Protocol.fieldStringLoad, save = Protocol.fieldStringSave },
579596
[Protocol.CRSF.FOLDER + 1] = { load = Protocol.fieldFolderLoad, save = nil },
580597
[Protocol.CRSF.INFO + 1] = { load = Protocol.fieldStringLoad, save = nil },
581598
[Protocol.CRSF.COMMAND + 1] = { load = Protocol.fieldCommandLoad, save = Protocol.handleCommandSave },

src/SCRIPTS/TOOLS/ExpressLRS/ui/lcd.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ local function fieldIntDisplay(field, y, attr)
378378
end
379379

380380
local function fieldFloatDisplay(field, y, attr)
381-
lcd.drawText(UI.COL2, y, string.format(field.fmt, field.value / field.prec), attr)
381+
lcd.drawText(UI.COL2, y, string.format(field.fmt, field.value / field.prec) .. (field.unit or ""), attr)
382382
end
383383

384384
local function fieldTextSelDisplay(field, y, attr)

src/SCRIPTS/TOOLS/ExpressLRS/ui/lvgl.lua

Lines changed: 86 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,7 @@ end
730730

731731
local IS_NARROW = LCD_W < 400
732732
local LABEL_PCT = lvgl.PERCENT_SIZE + (IS_NARROW and 42 or 50)
733+
local VALUE_PCT = lvgl.PERCENT_SIZE + (IS_NARROW and 58 or 50)
733734

734735
function UI.createToggleRow(pg, field)
735736
pg:setting({
@@ -830,54 +831,108 @@ function UI.createChoiceRow(pg, field)
830831
end
831832

832833
function UI.createNumberRow(pg, field)
834+
local isFloat = field.type == Protocol.CRSF.FLOAT
835+
local numberEdit = {
836+
type = lvgl.NUMBER_EDIT,
837+
min = field.min or 0,
838+
max = field.max or 255,
839+
get = function()
840+
return field.value or 0
841+
end,
842+
set = function(val)
843+
field.value = val
844+
end,
845+
edited = function(val)
846+
field.value = val
847+
Protocol.fieldIntSave(field)
848+
Protocol.reloadParentFolder(field)
849+
end,
850+
display = function(val)
851+
if isFloat then
852+
return string.format(field.fmt or "%.0f", val / (field.prec or 1))
853+
end
854+
return tostring(val)
855+
end,
856+
active = function()
857+
return not field.disabled
858+
end,
859+
}
860+
861+
local children
862+
if field.unit then
863+
children = {
864+
{
865+
type = lvgl.BOX,
866+
x = LABEL_PCT,
867+
flexFlow = lvgl.FLOW_ROW,
868+
flexPad = lvgl.PAD_MEDIUM,
869+
align = LEFT,
870+
children = {
871+
numberEdit,
872+
{
873+
type = lvgl.BOX,
874+
h = lvgl.UI_ELEMENT_HEIGHT,
875+
children = {
876+
{
877+
type = lvgl.LABEL,
878+
y = lvgl.PAD_MEDIUM,
879+
text = field.unit,
880+
},
881+
},
882+
},
883+
},
884+
},
885+
}
886+
else
887+
numberEdit.x = LABEL_PCT
888+
children = { numberEdit }
889+
end
890+
891+
pg:setting({
892+
w = lvgl.PERCENT_SIZE + 100,
893+
title = field.name,
894+
children = children,
895+
})
896+
end
897+
898+
function UI.createInfoRow(pg, field)
833899
pg:build({
834900
{
835901
type = lvgl.SETTING,
836902
w = lvgl.PERCENT_SIZE + 100,
837903
title = field.name,
838904
children = {
839905
{
840-
type = lvgl.NUMBER_EDIT,
906+
type = lvgl.LABEL,
841907
x = LABEL_PCT,
842-
min = field.min or 0,
843-
max = field.max or 255,
844-
get = function()
845-
return field.value or 0
846-
end,
847-
set = function(val)
848-
field.value = val
849-
end,
850-
edited = function(val)
851-
field.value = val
852-
Protocol.fieldIntSave(field)
853-
Protocol.reloadParentFolder(field)
854-
end,
855-
display = function(val)
856-
if field.type == Protocol.CRSF.FLOAT then
857-
return string.format(field.fmt or "%.0f", val / (field.prec or 1))
858-
end
859-
return table.concat({ tostring(val), field.unit or "" })
860-
end,
861-
active = function()
862-
return not field.disabled
863-
end,
908+
text = field.value,
864909
},
865910
},
866911
},
867912
})
868913
end
869914

870-
function UI.createInfoRow(pg, field)
915+
function UI.createStringRow(pg, field)
871916
pg:build({
872917
{
873918
type = lvgl.SETTING,
874919
w = lvgl.PERCENT_SIZE + 100,
875920
title = field.name,
876921
children = {
877922
{
878-
type = lvgl.LABEL,
923+
type = lvgl.TEXT_EDIT,
879924
x = LABEL_PCT,
880-
text = field.value,
925+
w = VALUE_PCT,
926+
value = field.value or "",
927+
length = math.min(math.max(field.maxlen or 32, 32), 128),
928+
set = function(val)
929+
field.value = val
930+
Protocol.fieldStringSave(field)
931+
Protocol.reloadParentFolder(field)
932+
end,
933+
active = function()
934+
return not field.disabled
935+
end,
881936
},
882937
},
883938
},
@@ -932,7 +987,11 @@ function UI.buildFieldWidget(pg, field, folderWidth)
932987
end
933988
end
934989

935-
if fieldType == Protocol.CRSF.STRING or fieldType == Protocol.CRSF.INFO then
990+
if fieldType == Protocol.CRSF.STRING then
991+
return UI.createStringRow(pg, field)
992+
end
993+
994+
if fieldType == Protocol.CRSF.INFO then
936995
return UI.createInfoRow(pg, field)
937996
end
938997
end

test/SCRIPTS/CRSFSimulator/csrfsimulator.lua

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -296,9 +296,11 @@ local function encodeParameterEntry(device, param, _chunk, destAddr)
296296
end
297297
end
298298
data[#data + 1] = 0xFF -- terminator
299-
elseif t == CRSF.INFO or t == CRSF.STRING then
300-
-- INFO (read-only) and STRING (editable) both encode as null-terminated string
299+
elseif t == CRSF.INFO then
301300
appendString(data, param.value or "")
301+
elseif t == CRSF.STRING then
302+
appendString(data, param.value or "")
303+
data[#data + 1] = param.maxlen or 32
302304
elseif t == CRSF.UINT8 then
303305
-- value, min, max (1 byte each)
304306
data[#data + 1] = param.value or 0
@@ -381,7 +383,7 @@ local txDevice = {
381383
serialNo = CRSF.ELRS_SERIAL_ID,
382384
hwVer = 0,
383385
swVer = 0x00030500, -- 3.5.0
384-
fieldCount = 21, -- total parameter count
386+
fieldCount = 23, -- total parameter count
385387
params = {
386388
{
387389
id = 1,
@@ -533,11 +535,36 @@ local txDevice = {
533535
info = "",
534536
},
535537

538+
-- Editable string field
539+
{
540+
id = 20,
541+
parent = 0,
542+
type = CRSF.STRING,
543+
name = "Bind Phrase",
544+
value = "default",
545+
maxlen = 16,
546+
},
547+
548+
-- Float field (scaled integer with precision)
549+
{
550+
id = 21,
551+
parent = 0,
552+
type = CRSF.FLOAT,
553+
name = "Freq Offset",
554+
value = 0,
555+
min = -5000,
556+
max = 5000,
557+
default = 0,
558+
prec = 2,
559+
step = 1,
560+
units = "kHz",
561+
},
562+
536563
-- Bad/Good (hidden from ELRS Lua, visible to other UIs)
537-
{ id = 20, parent = 0, type = CRSF.INFO, name = "Bad/Good", value = "0/250", hidden = true },
564+
{ id = 22, parent = 0, type = CRSF.INFO, name = "Bad/Good", value = "0/250", hidden = true },
538565

539566
-- Version + regulatory domain (name = version+domain, value = commit hash)
540-
{ id = 21, parent = 0, type = CRSF.INFO, name = "3.5.0 ISM2G4", value = "825ed8" },
567+
{ id = 23, parent = 0, type = CRSF.INFO, name = "3.5.0 ISM2G4", value = "825ed8" },
541568
},
542569
}
543570

@@ -1100,9 +1127,33 @@ local function mockPush(command, data)
11001127
local destAddr = data[2] or CRSF.ADDRESS_RADIO_TRANSMITTER
11011128
queuePush(CRSF.FRAMETYPE_PARAMETER_SETTINGS_ENTRY, encodeParameterEntry(device, param, 0, destAddr))
11021129
else
1103-
-- Value write: update the stored value immediately (matches
1104-
-- firmware config.Set*() which stores in RAM right away).
1105-
param.value = writeValue
1130+
-- Value write: decode based on field type
1131+
if t == CRSF.STRING then
1132+
local chars = {}
1133+
local i = 4
1134+
while data[i] and data[i] ~= 0 do
1135+
chars[#chars + 1] = data[i]
1136+
i = i + 1
1137+
end
1138+
param.value = (#chars > 0) and string.char(table.unpack(chars)) or ""
1139+
elseif t == CRSF.FLOAT then
1140+
local v = bit32.lshift(data[4] or 0, 24)
1141+
+ bit32.lshift(data[5] or 0, 16)
1142+
+ bit32.lshift(data[6] or 0, 8)
1143+
+ (data[7] or 0)
1144+
if v >= 0x80000000 then
1145+
v = v - 0x100000000
1146+
end
1147+
param.value = v
1148+
elseif t == CRSF.UINT16 or t == CRSF.INT16 then
1149+
local v = bit32.lshift(data[4] or 0, 8) + (data[5] or 0)
1150+
if t == CRSF.INT16 and v >= 0x8000 then
1151+
v = v - 0x10000
1152+
end
1153+
param.value = v
1154+
else
1155+
param.value = writeValue
1156+
end
11061157
-- Defer folder name and bandwidth updates to the next poll cycle.
11071158
-- Real firmware runs updateFolderNames() in the event loop, not
11081159
-- in the PARAMETER_WRITE handler. No auto-send of parent folder

0 commit comments

Comments
 (0)