Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ The simulator supports multiple test scenarios, configurable via the `config.sce
| `model_mismatch` | TX + RX connected with Model ID mismatch flag. Triggers warning dialog. |
| `armed` | TX + RX connected with "is Armed" warning flag. `ANT` is pinned to 0. |
| `single_antenna` | TX + RX connected on a receiver with one RF path. `2RSS` is pinned to 0, so the telemetry widget reports no diversity. |
| `flrc` | TX + RX connected at F1000. FLRC carries no SNR, so `RSNR` is a permanent 0 and the full-screen SNR row reads "n/a". |
| `slow_loading` | Parameter reads delayed by ~2 seconds each. Tests loading UI states. |
| `no_module` | No CRSF module found. Triggers "No Module Found" error dialog. |
| `critical_error` | TX + RX connected with a critical baud-rate error flag. Triggers the warning screen; the suppress write clears it. |
Expand Down
Binary file modified screenshots/widget_telemetry_fullscren.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
139 changes: 101 additions & 38 deletions src/SCRIPTS/CRSFSimulator/csrfsimulator.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ local shim = loadScript("/SCRIPTS/CRSFSimulator/shim.lua")()
-- Shows armed warning in subtitle.
-- "single_antenna" RX with a single RF path: 2RSS pinned to 0, so the
-- widgets report no diversity.
-- "flrc" TX + RX connected at F1000. FLRC carries no SNR, so
-- RSNR is a permanent 0 and an SNR reading must say so
-- rather than print 0 dB.
-- "unrated_rate" TX + RX connected on a packet rate ExpressLRS publishes
-- no receiver sensitivity for. There is no floor to
-- measure RSSI against, so anything drawn against one has
Expand Down Expand Up @@ -1210,6 +1213,7 @@ local function getElrsFlags()
or config.scenario == "single_antenna"
or config.scenario == "weak_link"
or config.scenario == "unrated_rate"
or config.scenario == "flrc"
then
flags = 0x01 -- connected
else
Expand Down Expand Up @@ -1685,8 +1689,6 @@ local moduleFound = (config.scenario ~= "no_module")
-- no_module scenario has no entry -> mockTelemetry returns nil.
-- ============================================================================

local txModuleTelemetry = { TPWR = 50 }

-- TQly/TRSS are the downlink pair: the RX->TX telemetry path, reported by the
-- handset's own receiver. They run a few dB behind the uplink in every
-- scenario because the receiver transmits at a fraction of the module's power,
Expand All @@ -1698,6 +1700,7 @@ local scenarioTelemetry = {
["1RSS"] = -87,
["2RSS"] = -93,
RQly = 99,
RSNR = 8,
ANT = 1,
TQly = 100,
TRSS = -95,
Expand All @@ -1717,6 +1720,7 @@ local scenarioTelemetry = {
["1RSS"] = -84,
["2RSS"] = 0,
RQly = 97,
RSNR = 8,
ANT = 0,
TQly = 100,
TRSS = -91,
Expand All @@ -1734,6 +1738,7 @@ local scenarioTelemetry = {
["1RSS"] = -78,
["2RSS"] = -82,
RQly = 100,
RSNR = 8,
ANT = 0,
TQly = 100,
TRSS = -83,
Expand All @@ -1745,6 +1750,26 @@ local scenarioTelemetry = {
Alt = 85,
GPS = { lat = 54.7050, lon = 25.3100 },
},
-- TX + RX connected at F1000 (v3 index 13). FLRC carries no SNR, so the
-- receiver reports 0 for as long as the rate runs; pinned in sensorToggle.
flrc = {
TPWR = 250,
RFMD = 13,
["1RSS"] = -70,
["2RSS"] = -74,
RQly = 100,
RSNR = 0,
ANT = 0,
TQly = 100,
TRSS = -80,
RxBt = 16.4,
Curr = 18.0,
FM = "ACRO",
Sats = 10,
GSpd = 45.0,
Alt = 30,
GPS = { lat = 54.6872, lon = 25.2797 },
},
-- A packet rate ExpressLRS publishes no sensitivity figure for (v3 index 18,
-- "9K1000", carried in the tables as 0). There is no floor to measure
-- against, so everything derived from one has to fall back rather than draw
Expand All @@ -1755,6 +1780,7 @@ local scenarioTelemetry = {
["1RSS"] = -79,
["2RSS"] = -84,
RQly = 98,
RSNR = 8,
ANT = 0,
TQly = 100,
TRSS = -88,
Expand All @@ -1774,6 +1800,7 @@ local scenarioTelemetry = {
["1RSS"] = -55,
["2RSS"] = -58,
RQly = 95,
RSNR = 12,
ANT = 1,
TQly = 100,
TRSS = -61,
Expand All @@ -1788,11 +1815,17 @@ local scenarioTelemetry = {
["1RSS"] = -55,
["2RSS"] = -58,
RQly = 95,
RSNR = 12,
ANT = 1,
TQly = 100,
TRSS = -61,
RxBt = 15.8,
Curr = 0.5,
FM = "ACRO",
Sats = 12,
GSpd = 25.3,
Alt = 142,
GPS = { lat = 54.6872, lon = 25.2797 },
},
-- Same signal as model_mismatch, and deliberately no sensorToggle entry: the
-- mismatch bit is the only thing that may move, so RQly stays at 95 and the
Expand All @@ -1803,6 +1836,7 @@ local scenarioTelemetry = {
["1RSS"] = -55,
["2RSS"] = -58,
RQly = 95,
RSNR = 12,
ANT = 1,
TQly = 100,
TRSS = -61,
Expand All @@ -1817,6 +1851,7 @@ local scenarioTelemetry = {
["1RSS"] = -85,
["2RSS"] = -88,
RQly = 60,
RSNR = 2,
ANT = 0,
TQly = 62,
TRSS = -97,
Expand All @@ -1828,13 +1863,35 @@ local scenarioTelemetry = {
Alt = 210,
GPS = { lat = 54.6600, lon = 25.2400 },
},
no_telemetry = {
-- The sensor list a previous flight left on the model. The RX never
-- answers, so every value is served as 0 the way EdgeTX does; only the
-- key set matters.
TPWR = 50,
RFMD = 7,
["1RSS"] = -87,
["2RSS"] = -93,
RQly = 99,
RSNR = 8,
ANT = 1,
TQly = 100,
TRSS = -95,
RxBt = 15.2,
Curr = 12.5,
FM = "ACRO",
Sats = 12,
GSpd = 25.3,
Alt = 142,
GPS = { lat = 54.6872, lon = 25.2797 },
},
reconnect = {
-- Same as normal; only served when isRxAvailable() is true
-- Same as normal; zeroed until isRxAvailable() turns true
TPWR = 50,
RFMD = 7,
["1RSS"] = -87,
["2RSS"] = -93,
RQly = 99,
RSNR = 8,
ANT = 1,
TQly = 100,
TRSS = -95,
Expand All @@ -1848,6 +1905,7 @@ local scenarioTelemetry = {
["1RSS"] = -87,
["2RSS"] = -93,
RQly = 99,
RSNR = 8,
ANT = 1,
TQly = 100,
TRSS = -95,
Expand All @@ -1866,6 +1924,7 @@ local scenarioTelemetry = {
["1RSS"] = -87,
["2RSS"] = -93,
RQly = 99,
RSNR = 8,
ANT = 1,
TQly = 100,
TRSS = -95,
Expand All @@ -1887,6 +1946,7 @@ local sensorJitter = {
TRSS = 3,
RQly = 2, -- +/- 2%
TQly = 2,
RSNR = 2, -- +/- 2 dB
RxBt = 0.05, -- +/- 0.05V
Curr = 2.0, -- +/- 2A
GSpd = 3.0,
Expand Down Expand Up @@ -1914,6 +1974,10 @@ local sensorToggle = {
-- Must stay exactly 0: that is what marks the second RF path as absent.
["2RSS"] = { 0 },
},
flrc = {
-- FLRC reports no SNR: exactly 0, never jittered.
RSNR = { 0 },
},
mismatch_cycle = {
-- ~10 s connected, ~5 s down, repeating. Exactly 95 or 0 so the
-- RQly-derived connection state flips cleanly on each phase change.
Expand All @@ -1935,54 +1999,53 @@ local function updateTelemetryCache()
end
lastTelemetryUpdate = now

if not isRxAvailable() then
-- TX module still reports RFMD/TPWR via link stats even without RX.
-- Only provide these when a module is present (not no_module).
telemetryCache = {}
if moduleFound then
for k, v in pairs(txModuleTelemetry) do
telemetryCache[k] = v
end
end
return
end
local t = scenarioTelemetry[config.scenario]
telemetryCache = {}
if not t then
telemetryCache = {}
return
end

telemetryCache = {}
toggleStep = toggleStep + 1
local toggles = sensorToggle[config.scenario]
for sensorId, base in pairs(t) do
local seq = toggles and toggles[sensorId]
local jit = sensorJitter[sensorId]
if seq then
telemetryCache[sensorId] = seq[(toggleStep % #seq) + 1]
elseif jit then
local val = base + (math.random() * 2 - 1) * jit
if jit == math.floor(jit) then
val = math.floor(val + 0.5)
end
-- A link quality is a percentage of packets received, so it cannot
-- exceed 100. Jittering a base of 99 was handing the widgets 101, which
-- is not a reading any receiver can produce.
local ceiling = sensorCeiling[sensorId]
if ceiling and val > ceiling then
val = ceiling
if isRxAvailable() then
toggleStep = toggleStep + 1
local toggles = sensorToggle[config.scenario]
for sensorId, base in pairs(t) do
local seq = toggles and toggles[sensorId]
local jit = sensorJitter[sensorId]
if seq then
telemetryCache[sensorId] = seq[(toggleStep % #seq) + 1]
elseif jit then
local val = base + (math.random() * 2 - 1) * jit
if jit == math.floor(jit) then
val = math.floor(val + 0.5)
end
-- A link quality is a percentage of packets received, so it cannot
-- exceed 100. Jittering a base of 99 was handing the widgets 101, which
-- is not a reading any receiver can produce.
local ceiling = sensorCeiling[sensorId]
if ceiling and val > ceiling then
val = ceiling
end
telemetryCache[sensorId] = val
else
telemetryCache[sensorId] = base
end
telemetryCache[sensorId] = val
else
telemetryCache[sensorId] = base
end
end

-- EdgeTX serves 0 for every sensor while RQly is 0 (luaGetValueAndPush),
-- the module's own TPWR/RFMD included: Lua never sees the link stats frame.
if (telemetryCache.RQly or 0) == 0 then
for sensorId in pairs(t) do
telemetryCache[sensorId] = 0
end
end
end

--- Return a mock telemetry sensor value for the current scenario.
-- Called at ~10 Hz by the widget via crsf.getSensorValue(). Values are
-- regenerated only once per second; intermediate calls return cached data.
-- Returns nil when disconnected or the sensor is not defined.
-- Returns 0 for every sensor while the link is down, as EdgeTX does, and nil
-- for a sensor the scenario does not define.
local function mockGetSensorValue(sensorId)
updateTelemetryCache()
return telemetryCache[sensorId]
Expand Down
12 changes: 12 additions & 0 deletions src/WIDGETS/ELRSTelemetry/rf_modes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,18 @@ function RfModes.floor(rfmd)
return dbm
end

-- The FLRC rates. The radio reports no SNR for FLRC packets, so RSNR sits at
-- 0 for as long as one of these runs. Keyed by name: both majors spell them
-- the same, and the name is what a reader can check against the RF Mode row.
local NO_SNR = { D250 = true, D500 = true, F500 = true, F1000 = true }

--- Whether an RFMD sensor value names a rate that carries an SNR reading.
function RfModes.hasSnr(rfmd)
local names = RfModes._names
local name = names and names[rfmd + 1]
return not (name and NO_SNR[name])
end

-- ============================================================================
-- Return module
-- ============================================================================
Expand Down
8 changes: 8 additions & 0 deletions src/WIDGETS/ELRSTelemetry/telemetry.lua
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,13 @@ function Telemetry.rfModeName()
return RfModes.name(rfmd)
end

--- Whether the current rate carries an SNR reading at all. FLRC rates do
--- not, so their RSNR is a placeholder 0 rather than a measurement.
function Telemetry.hasSnr()
local rfmd = Telemetry.link.rfmd
return rfmd == nil or RfModes.hasSnr(rfmd)
end

-- ============================================================================
-- Derived state
-- ============================================================================
Expand Down Expand Up @@ -410,6 +417,7 @@ function Telemetry.update()
link.rssi1 = crsf.getSensorValue("1RSS")
link.rssi2 = crsf.getSensorValue("2RSS")
link.rqly = crsf.getSensorValue("RQly")
link.rsnr = crsf.getSensorValue("RSNR")
link.ant = crsf.getSensorValue("ANT")
link.tqly = crsf.getSensorValue("TQly")
link.trss = crsf.getSensorValue("TRSS")
Expand Down
4 changes: 2 additions & 2 deletions src/WIDGETS/ELRSTelemetry/ui/display.lua
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,10 @@ function Display.trssText()
return table.concat({ tostring(trss), " dBm" })
end

--- TX power on its own, e.g. "100 mW", or "--" while unknown.
--- TX power on its own, e.g. "100 mW", or "--" while disconnected.
function Display.powerText()
local tpwr = Telemetry.link.tpwr
if tpwr == nil then
if not Telemetry.isConnected() or tpwr == nil then
return "--"
end
return table.concat({ tostring(tpwr), " mW" })
Expand Down
Loading