Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
CapnBry committed Dec 18, 2021
0 parents commit f310772
Show file tree
Hide file tree
Showing 3 changed files with 353 additions and 0 deletions.
18 changes: 18 additions & 0 deletions README.md
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.
Binary file added docs/images/screen-2-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
335 changes: 335 additions & 0 deletions src/WIDGETS/ELRST/main.lua
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 },
}
}

0 comments on commit f310772

Please sign in to comment.