-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f310772
Showing
3 changed files
with
353 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
### No, you can't use this widget to configure your TX module, don't ask! | ||
|
||
A simple widget to display ExpressLRS LinkStats telemetry as well as common Betaflight and iNav flight controller telemetry. | ||
|
||
![widget screenshot](docs/images/screen-2-1.png) | ||
|
||
# Installing | ||
* Copy the `src/WIDGETS/ELRST` folder to your handset's SD card in the `WIDGETS/` folder such that your SD card will end up with a file called `WIDGETS/ELRST/main.lua`. | ||
* Discover sensors | ||
* Power up your receiver and flight controller and wait for a connection to be established. | ||
* Press the MDL (model) key, then PAGE to get to the TELEMETRY page. | ||
* Use the "Discover new" button to start discovering sensors. Betaflight should have 17, iNav should have 23 with GPS. | ||
* Add the widget to the main screen | ||
* Press the TELEM button on the handset and navigate to the second page. | ||
* Tap "Setup widgets". | ||
* Tap an open space and add the "ELRS Telem" widget. | ||
* Use the RTN / EXIT button to go back until you're on the main screen again. | ||
* If you forgot to Discover sensors before adding the widget, discover them and restart the handset entirely. |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,335 @@ | ||
--196x170 right half | ||
--392x85 top half | ||
--196x56 1 + 3 | ||
--196x42 1 + 4 | ||
|
||
local TH = 18 | ||
|
||
local vcache | ||
local function getV(id) | ||
-- Return the getValue of ID or nil if it does not exist | ||
local cid = vcache[id] | ||
if cid == nil then | ||
local info = getFieldInfo(id) | ||
-- use 0 to prevent future lookups | ||
cid = info and info.id or 0 | ||
vcache[id] = cid | ||
end | ||
return cid ~= 0 and getValue(cid) or nil | ||
end | ||
|
||
local function create(zone, options) | ||
local widget = { | ||
zone = zone, | ||
cfg = options, | ||
} | ||
|
||
local _, rv = getVersion() | ||
widget.DEBUG = string.sub(rv, -5) == "-simu" | ||
|
||
vcache = {} | ||
return widget | ||
end | ||
|
||
local function update(widget, options) | ||
-- Runs if options are changed from the Widget Settings menu | ||
widget.cfg = options | ||
end | ||
|
||
local function pwrToIdx(powval) | ||
local POWERS = {10, 25, 50, 100, 250, 500, 1000, 2000} | ||
for k, v in ipairs(POWERS) do | ||
if powval == v then return k - 1 end | ||
end | ||
return 7 -- 2000 | ||
end | ||
|
||
local function drawPowerLvl(minp, cfgp, maxp, curp, zw, zh) | ||
local barW = 12 | ||
local itemH = (zh - 5) / (maxp - minp) | ||
lcd.drawRectangle(zw - barW - 1, 1, barW, zh - 2) | ||
--lcd.drawFilledRectangle(zw - barW, 2, barW - 2, itemH * (maxp - cfgp) - 1, COLOR_THEME_DISABLED) -- power beyond cfgpower | ||
curp = pwrToIdx(curp) | ||
for pwr = minp + 1, curp do | ||
--print(pwr, (zh - 2 - (pwr * itemH))) | ||
lcd.drawFilledRectangle(zw - barW + 1, zh - (pwr * itemH) - 1, barW - 4, itemH - 1, COLOR_THEME_PRIMARY3) | ||
end | ||
end | ||
|
||
local function drawDiverSym(x, y, ant) | ||
if ant ~= nil then | ||
lcd.drawFilledRectangle(x+(5*ant), y+6, 4, 10, COLOR_THEME_SECONDARY1) | ||
lcd.drawFilledRectangle(x+5-(5*ant), y+11, 4, 5, COLOR_THEME_SECONDARY1) | ||
end | ||
end | ||
|
||
local function drawDbms(x, y, rssi1, rssi2, ant) | ||
-- ant is nil if not diversity, else 0 or 1 | ||
local rssi1Str = tostring(rssi1) | ||
lcd.drawText(x+32, y, rssi1Str, ((ant ~= 1) and COLOR_THEME_SECONDARY1 or COLOR_THEME_DISABLED) + RIGHT) | ||
if ant ~= nil then | ||
drawDiverSym(x+34, y, ant) | ||
lcd.drawText(x+48, y, tostring(rssi2), (ant == 1) and COLOR_THEME_SECONDARY1 or COLOR_THEME_DISABLED) | ||
end | ||
end | ||
|
||
local function checkCellCount(ctx, v) | ||
-- once the cellCnt is the same X times in a row, stop updating | ||
if (ctx.cellCntCnt or 0) > 5 then | ||
return | ||
end | ||
|
||
-- try to lock on to the cell count, so as the voltage sags we don't change S | ||
local cellCnt = math.floor(v / 4.35) + 1 | ||
-- Prevent lock on when no voltage is present | ||
if (v / cellCnt) < 3.0 then | ||
return | ||
end | ||
|
||
if ctx.cellCnt ~= cellCnt then | ||
ctx.cellCnt = cellCnt | ||
ctx.cellCntCnt = 0 | ||
else | ||
-- The value has to change to count as an update | ||
if ctx.cellLastV == v then | ||
return | ||
end | ||
ctx.cellLastV = v | ||
ctx.cellCntCnt = ctx.cellCntCnt + 1 | ||
end | ||
end | ||
|
||
local function drawVBatt(widget, tlm) | ||
tlm.vbat = getV("RxBt") | ||
if tlm.vbat == nil then return end | ||
lcd.drawText(1, widget.zh - 30, "Battery", SMLSIZE + COLOR_THEME_SECONDARY2 + SHADOWED) | ||
checkCellCount(widget.ctx, tlm.vbat) | ||
|
||
local str | ||
if tlm.vbat > 0 then | ||
local cells = widget.ctx.cellCnt | ||
if cells then | ||
-- If fullscreen use the verbose, otherwise just be small to fit current | ||
if widget.size < 1 then | ||
str = string.format("%.2fV (%dS %.1fV)", tlm.vbat / cells, cells, tlm.vbat) | ||
else | ||
str = string.format("%dS %.2fV", cells, tlm.vbat / cells) | ||
end | ||
else | ||
str = tostring(tlm.vbat) .. "V" | ||
end | ||
else | ||
str = " --" | ||
end | ||
lcd.drawText(1, widget.zh - 18, str, COLOR_THEME_PRIMARY3) | ||
end | ||
|
||
local function drawCurrent(widget, tlm) | ||
tlm.curr = getV("Curr") | ||
if tlm.curr == nil then return end | ||
lcd.drawText(widget.zw / 2 - 6, widget.zh - 30, "Current", SMLSIZE + COLOR_THEME_SECONDARY2 + SHADOWED + CENTER) | ||
|
||
local str | ||
if tlm.curr > 0 then | ||
str = string.format("%.2fA", tlm.curr) | ||
else | ||
str = "--" | ||
end | ||
lcd.drawText(widget.zw / 2 - 6, widget.zh - 18, str, COLOR_THEME_PRIMARY3 + CENTER) | ||
end | ||
|
||
local function drawGps(widget, tlm, Y) | ||
tlm.sats = getV("Sats") | ||
if tlm.sats == nil then return end | ||
-- Number of sats | ||
lcd.drawText(widget.zw - 13, Y, tostring(tlm.sats) .. " sats", COLOR_THEME_SECONDARY1 + RIGHT) | ||
|
||
Y = Y + TH | ||
tlm.gspd = getV("GSpd") | ||
if tlm.gspd ~= nil then | ||
lcd.drawText(1, Y, string.format("Speed %.1f", tlm.gspd), COLOR_THEME_SECONDARY1) | ||
end | ||
tlm.alt = getV("Alt") | ||
if tlm.alt ~= nil then | ||
lcd.drawText(widget.zw - 13, Y, "Alt " .. tostring(tlm.alt), COLOR_THEME_SECONDARY1 + RIGHT) | ||
end | ||
end | ||
|
||
local function drawFcTelem(widget, tlm, Y) | ||
drawVBatt(widget, tlm) | ||
drawCurrent(widget, tlm) | ||
if widget.size == 0 then | ||
drawGps(widget, tlm, Y) | ||
end | ||
end | ||
|
||
local function drawRange(widget, tlm, Y) | ||
-- returns size of range bar drawn (0 if range pie) | ||
local rssi = (tlm.ant == 1) and tlm.rssi2 or tlm.rssi1 | ||
local RFRSSI = {-128, -123, -117, -117, -112, -112, -108, -105} | ||
local minrssi = RFRSSI[tlm.rfmd+1] -- note 50Hz uses 2.4 limit | ||
if rssi > -50 then rssi = -50 end | ||
local rangePct = 100 * (rssi + 50) / (minrssi + 50) | ||
local rangePctStr = string.format("%d%%", rangePct) | ||
local rangeClr = (rangePct > 80) and COLOR_THEME_WARNING or (rangePct > 40) and COLOR_THEME_SECONDARY2 or COLOR_THEME_SECONDARY1 | ||
|
||
-- Range Bar (for anyting except full height) | ||
if widget.size > 1 then | ||
lcd.drawGauge(1, Y, widget.zw - 15, 15, rangePct, 100, COLOR_THEME_SECONDARY1) | ||
lcd.drawText(widget.zw / 2 - 6, Y - 2, "Range " .. rangePctStr, SMLSIZE + CENTER + rangeClr) | ||
return Y + 14 | ||
end | ||
|
||
-- Range Pie (for full height only) | ||
local cx, cy, cr = widget.zw / 2 - 6, widget.zh / 2 + 10, widget.zw / 6 | ||
local firstH = (rangePct >= 50) and 180 or rangePct * 180 / 50 | ||
lcd.drawPie(cx, cy, cr, 180, 180 + firstH, COLOR_THEME_SECONDARY1) | ||
if rangePct > 50 then | ||
local secondH = (rangePct - 50) * 180 / 50 | ||
lcd.drawPie(cx, cy, cr, 0, secondH, COLOR_THEME_SECONDARY1) | ||
end | ||
|
||
lcd.drawText(cx + (cr * -0.70), cy + cr - 15, "Range", SMLSIZE + RIGHT + rangeClr + SHADOWED) | ||
lcd.drawText(cx + (cr * 0.85), cy + cr - 15, rangePctStr, SMLSIZE + rangeClr + SHADOWED) | ||
lcd.drawCircle(cx, cy, cr, COLOR_THEME_PRIMARY3) | ||
|
||
return Y | ||
end | ||
|
||
local function updateWidgetSize(widget, event) | ||
if event ~= nil then | ||
widget.size = 0 -- fullscreen | ||
widget.zw = LCD_W | ||
widget.zh = LCD_H | ||
return | ||
end | ||
|
||
widget.zw = widget.zone.w | ||
widget.zh = widget.zone.h | ||
if widget.zh >= 170 then | ||
widget.size = 1 -- 1x widget | ||
elseif widget.zh >= 85 then | ||
widget.size = 2 -- 2x widget | ||
elseif widget.zh >= 56 then | ||
widget.size = 3 -- 3x widget | ||
else -- 42 | ||
widget.size = 4 -- 4x widget | ||
end | ||
end | ||
|
||
local function drawRfModeText(widget, tlm, Y) | ||
local RFHZ = {"4", "25", "50", "100", "150", "200", "250", "500"} | ||
local modestr = RFHZ[tlm.rfmd+1] | ||
if widget.size < 3 then tlm.fmode = getV("FM") end | ||
|
||
-- For 3up/4up widgets, condense the LQ into the modestr | ||
if widget.size > 2 then | ||
modestr = modestr .. string.format("Hz LQ %d%%", tlm.rqly) | ||
else | ||
local fmodestr | ||
-- For 2up, flight mode goes in the Rf mode if available | ||
if widget.size == 2 and tlm.fmode ~= nil then | ||
fmodestr = "Hz " .. tlm.fmode .. " Mode" | ||
end | ||
modestr = modestr .. (fmodestr or "Hz Connected") | ||
end | ||
lcd.drawText(widget.zw / 2 - 6, Y, modestr, COLOR_THEME_PRIMARY1 + CENTER) | ||
|
||
return Y + TH | ||
end | ||
|
||
local function drawRssiLq(widget, tlm, Y) | ||
local rssi = (tlm.ant == 1) and tlm.rssi2 or tlm.rssi1 | ||
if widget.size > 3 then | ||
lcd.drawText(1, widget.zh - 15, tostring(rssi) .. "dBm", SMLSIZE + COLOR_THEME_PRIMARY3) | ||
elseif widget.size > 2 then | ||
lcd.drawText(1, widget.zh - 30, "RSSI", SMLSIZE + COLOR_THEME_SECONDARY2 + SHADOWED) | ||
lcd.drawText(1, widget.zh - 18, tostring(rssi) .. "dBm", COLOR_THEME_PRIMARY3) | ||
elseif widget.size > 1 then | ||
lcd.drawText(1, Y, "LQ " .. tostring(tlm.rqly) .. "%", COLOR_THEME_SECONDARY1) | ||
drawDiverSym(73, Y, widget.ctx.isDiversity and tlm.ant) | ||
lcd.drawText(83, Y, "Signal " .. tostring(rssi), COLOR_THEME_SECONDARY1) | ||
lcd.drawText(widget.zw - 13, Y+3, "dBm", SMLSIZE + COLOR_THEME_SECONDARY1 + RIGHT) | ||
else | ||
lcd.drawText(1, Y, "Signal", COLOR_THEME_SECONDARY1) | ||
lcd.drawText(44, Y+2, "(dBm)", SMLSIZE + COLOR_THEME_SECONDARY1) | ||
drawDbms(82, Y, tlm.rssi1, tlm.rssi2, widget.ctx.isDiversity and tlm.ant) | ||
Y = Y + TH | ||
-- LQ on separate line | ||
lcd.drawText(1, Y + 1, "LQ " .. tostring(tlm.rqly) .. "%", COLOR_THEME_SECONDARY1) | ||
-- FMode | ||
if tlm.fmode then | ||
-- 1up on the right, fullscreen in the center | ||
if widget.size == 1 then | ||
lcd.drawText(widget.zw - 13, Y + 1, tlm.fmode, COLOR_THEME_SECONDARY1 + RIGHT) | ||
else | ||
lcd.drawText(widget.zw / 2, Y + 1, tlm.fmode .. " Mode", COLOR_THEME_SECONDARY1 + CENTER) | ||
end | ||
end -- if fmode | ||
end | ||
|
||
return Y | ||
end | ||
|
||
local function refresh(widget, event, touchState) | ||
-- Runs periodically only when widget instance is visible | ||
-- If full screen, then event is 0 or event value, otherwise nil | ||
--print(tostring(widget.zone.w) .. "x" .. tostring(widget.zone.h)) | ||
updateWidgetSize(widget, event) | ||
lcd.drawFilledRectangle(0, 0, widget.zw, widget.zh, COLOR_THEME_PRIMARY2, 3 * widget.cfg.Transparency) | ||
local Y = 1 | ||
|
||
local tlm = { rssi1 = getV("1RSS") } | ||
if not widget.DEBUG and (tlm.rssi1 == nil or tlm.rssi1 == 0) then | ||
lcd.drawText(widget.zw / 2, Y, "No RX Connected", COLOR_THEME_PRIMARY1 + CENTER) | ||
lcd.drawText(widget.zw / 2, Y+TH, "or sensors discovered", COLOR_THEME_SECONDARY1 + CENTER) | ||
widget.ctx = nil | ||
return | ||
end | ||
|
||
if widget.DEBUG then | ||
tlm.rfmd = 7 tlm.rssi1 = -87 tlm.rssi2 = -93 tlm.rqly = 99 tlm.ant = 1 tlm.tpwr = 50 | ||
else | ||
tlm.rfmd = getV("RFMD") tlm.rssi2 = getV("2RSS") tlm.rqly = getV("RQly") tlm.ant = getV("ANT") tlm.tpwr = getV("TPWR") | ||
end | ||
if widget.ctx == nil then | ||
widget.ctx = {} | ||
end | ||
if tlm.ant ~= 0 then | ||
widget.ctx.isDiversity = true | ||
end | ||
|
||
-- Range | ||
Y = drawRange(widget, tlm, Y) | ||
-- Rf Mode + FMode | ||
Y = drawRfModeText(widget, tlm, Y) | ||
-- RSSI / LQ + FMode | ||
Y = drawRssiLq(widget, tlm, Y) | ||
|
||
-- TX Power | ||
if widget.size < 4 then | ||
lcd.drawText(widget.zw - 13, widget.zh - 30, "Power", SMLSIZE + RIGHT + COLOR_THEME_SECONDARY2 + SHADOWED) | ||
lcd.drawText(widget.zw - 13, widget.zh - 18, tostring(tlm.tpwr) .. "mW", RIGHT + COLOR_THEME_PRIMARY3) | ||
else | ||
lcd.drawText(widget.zw - 13, widget.zh - 15, tostring(tlm.tpwr) .. "mW", SMLSIZE + RIGHT + COLOR_THEME_PRIMARY3) | ||
end | ||
drawPowerLvl(0, 6, 6, tlm.tpwr, widget.zw, widget.zh) -- uses 1W as max | ||
|
||
-- Extended FC Telemetry | ||
if widget.size < 3 then | ||
drawFcTelem(widget, tlm, Y) | ||
end | ||
end | ||
|
||
return { | ||
name = "ELRS Telem", | ||
options = {}, | ||
create = create, | ||
update = update, | ||
refresh = refresh, | ||
background = nil, | ||
options = { | ||
{ "Transparency", VALUE, 2, 0, 5 }, | ||
} | ||
} | ||
|