375 lines
13 KiB
Lua
375 lines
13 KiB
Lua
--------------------------------------------------------------------
|
|
-- lib/ecran.lua : affichages.
|
|
-- Moniteur tactile: echelle auto, onglets MODE / CALIB / PARAM /
|
|
-- JOURNAL, art du quadricoptere, choix de role interactif.
|
|
-- Display conduite (2x22): ALT reelle -> consigne, VIT globale,
|
|
-- messages en segments de mots entiers.
|
|
-- Display carburant (2x4): pourcentage.
|
|
--------------------------------------------------------------------
|
|
local Ecran = {}
|
|
|
|
local function couleurRpm(suivi, vmax)
|
|
if not suivi then return colors.lightGray end
|
|
if os.clock() - suivi.tChange < 1.0 then return colors.yellow end
|
|
if suivi.v == 0 then return colors.white end
|
|
if math.abs(suivi.v) >= vmax then return colors.red end
|
|
return colors.green
|
|
end
|
|
|
|
function Ecran.nouveau(conf, materiel, journal)
|
|
local e = {}
|
|
local moniteur = materiel.moniteur
|
|
local zones = {}
|
|
local page = "mode"
|
|
local defilParams = 0
|
|
local L, H = 0, 0
|
|
|
|
-- la PLUS GRANDE echelle offrant au moins 38x14
|
|
for _, s in ipairs({ 5, 4.5, 4, 3.5, 3, 2.5, 2, 1.5, 1, 0.5 }) do
|
|
moniteur.setTextScale(s)
|
|
L, H = moniteur.getSize()
|
|
if L >= 38 and H >= 14 then break end
|
|
end
|
|
|
|
------------------------------------------------------------------
|
|
-- PRIMITIVES
|
|
------------------------------------------------------------------
|
|
local function texte(x, y, contenu, couleur)
|
|
moniteur.setTextColor(couleur or colors.white)
|
|
moniteur.setCursorPos(x, y)
|
|
moniteur.write(contenu)
|
|
moniteur.setTextColor(colors.white)
|
|
end
|
|
|
|
local function bouton(x, y, libelle, action, couleur, couleurTexte)
|
|
moniteur.setCursorPos(x, y)
|
|
moniteur.setBackgroundColor(couleur or colors.gray)
|
|
moniteur.setTextColor(couleurTexte or colors.white)
|
|
moniteur.write(" " .. libelle .. " ")
|
|
moniteur.setBackgroundColor(colors.black)
|
|
moniteur.setTextColor(colors.white)
|
|
table.insert(zones, { x1 = x, y1 = y, x2 = x + #libelle + 1,
|
|
y2 = y, action = action })
|
|
end
|
|
|
|
------------------------------------------------------------------
|
|
-- CONDUITE (messages en segments de mots entiers)
|
|
------------------------------------------------------------------
|
|
local segments, segmentFin = nil, 0
|
|
|
|
function e.message(contenu, duree)
|
|
if not contenu or contenu == "" then segments = nil return end
|
|
segments = {}
|
|
local courant = ""
|
|
for mot in contenu:gmatch("%S+") do
|
|
if #courant == 0 then courant = mot
|
|
elseif #courant + 1 + #mot <= 22 then
|
|
courant = courant .. " " .. mot
|
|
else table.insert(segments, courant) courant = mot end
|
|
end
|
|
if #courant > 0 then table.insert(segments, courant) end
|
|
segmentFin = os.clock() + (duree or math.max(5, #segments * 2))
|
|
end
|
|
|
|
local function segmentCourant()
|
|
if not segments or os.clock() > segmentFin then return nil end
|
|
local i = 1 + math.floor((os.clock() % (#segments * 2)) / 2)
|
|
i = math.min(i, #segments)
|
|
return segments[i]
|
|
.. (#segments > 1 and (" " .. i .. "/" .. #segments) or "")
|
|
end
|
|
|
|
-- ctx: { mode, consigneY }
|
|
function e.rafraichirConduite(ctx)
|
|
local d = materiel.ecranConduite
|
|
if not d then return end
|
|
d.clear()
|
|
d.setCursorPos(1, 1)
|
|
local seg = segmentCourant()
|
|
if seg then
|
|
d.write(seg:sub(1, 22))
|
|
elseif ctx.mode == "vol" or ctx.mode == "atterrissage" then
|
|
d.write(("ALT %.1f > %.0f"):format(materiel.lireAltitude(),
|
|
ctx.consigneY or 0):sub(1, 22))
|
|
else
|
|
d.write(("MODE %s"):format(ctx.mode:upper()):sub(1, 22))
|
|
end
|
|
d.setCursorPos(1, 2)
|
|
d.write(("VIT %.2f b/s"):format(materiel.vitesseGlobale())
|
|
:sub(1, 22))
|
|
d.update()
|
|
end
|
|
|
|
function e.rafraichirCarburant(pct)
|
|
local d = materiel.ecranCarburant
|
|
if not d then return end
|
|
local largeur = d.getSize()
|
|
d.clear()
|
|
d.setCursorPos(1, 1)
|
|
if pct == nil then d.write(("n/a"):sub(1, largeur))
|
|
else
|
|
d.write(("%d%%"):format(math.floor(pct * 100 + 0.5))
|
|
:sub(1, largeur))
|
|
end
|
|
d.update()
|
|
end
|
|
|
|
------------------------------------------------------------------
|
|
-- ART DU QUADRICOPTERE (7 lignes)
|
|
------------------------------------------------------------------
|
|
local function rpmDe(role)
|
|
local suivi = materiel.rpm[role]
|
|
return suivi and tostring(suivi.v) or "?"
|
|
end
|
|
|
|
local function couleurDe(role, ctx)
|
|
if ctx.rscViolet == role then return colors.purple end
|
|
return couleurRpm(materiel.rpm[role], conf.VMAX)
|
|
end
|
|
|
|
local function dessinerArt(x, y, ctx)
|
|
local function coin(cx, cy, role, aGauche)
|
|
local couleur = couleurDe(role, ctx)
|
|
texte(cx, cy, "O", couleur)
|
|
local r = rpmDe(role)
|
|
if aGauche then texte(cx - #r - 1, cy, r, couleur)
|
|
else texte(cx + 2, cy, r, couleur) end
|
|
end
|
|
local g, d = x + 6, x + 19
|
|
texte(x + 10, y, "AVANT", colors.lightGray)
|
|
coin(g, y + 1, "lf", true)
|
|
coin(d, y + 1, "rf", false)
|
|
texte(g + 1, y + 1, ("-"):rep(d - g - 1))
|
|
for i = 2, 4 do
|
|
texte(g, y + i, "|")
|
|
texte(d, y + i, "|")
|
|
end
|
|
texte(x, y + 3, rpmDe("prop_l"), couleurDe("prop_l", ctx))
|
|
texte(d + 2, y + 3, rpmDe("prop_r"), couleurDe("prop_r", ctx))
|
|
coin(g, y + 5, "lb", true)
|
|
coin(d, y + 5, "rb", false)
|
|
texte(g + 1, y + 5, ("-"):rep(d - g - 1))
|
|
return y + 7
|
|
end
|
|
|
|
------------------------------------------------------------------
|
|
-- PAGES
|
|
------------------------------------------------------------------
|
|
local function dessinerPageMode(ctx)
|
|
local x, y = 1, 2
|
|
for _, m in ipairs({ "off", "sol", "stationnement", "vol",
|
|
"atterrissage" }) do
|
|
local couleur = colors.gray
|
|
if m == ctx.mode then couleur = colors.green
|
|
elseif ctx.verrous[m] then couleur = colors.red end
|
|
if x + #m + 2 > L then x, y = 1, y + 1 end
|
|
bouton(x, y, m, { type = "mode", valeur = m,
|
|
verrou = ctx.verrous[m] }, couleur)
|
|
x = x + #m + 3
|
|
end
|
|
y = y + 2
|
|
|
|
texte(1, y, ("carburant %s autonomie %s"):format(
|
|
ctx.carburantPct
|
|
and ("%.0f%%"):format(ctx.carburantPct * 100) or "n/a",
|
|
ctx.minutes and ("%dmin"):format(math.floor(ctx.minutes))
|
|
or "--"))
|
|
y = y + 1
|
|
if ctx.mode ~= "off" then
|
|
local stress, capacite = materiel.lireStressBrut()
|
|
texte(1, y, ("stress %s/%s niv %d moteur %s"):format(
|
|
stress and ("%d"):format(stress) or "?",
|
|
capacite and ("%d"):format(capacite) or "?",
|
|
materiel.niveauMoteur(),
|
|
ctx.moteurOk and "ok" or "ARRETE"),
|
|
ctx.moteurOk and colors.white or colors.red)
|
|
y = y + 1
|
|
end
|
|
-- choix du mode moteur depuis le moniteur
|
|
texte(1, y, "regime moteur:")
|
|
bouton(16, y, "on/off", { type = "modemoteur", valeur = "onoff" },
|
|
ctx.modeMoteur == "onoff" and colors.green or colors.gray)
|
|
bouton(25, y, "adaptatif",
|
|
{ type = "modemoteur", valeur = "adaptatif" },
|
|
ctx.modeMoteur == "adaptatif" and colors.green or colors.gray)
|
|
y = y + 2
|
|
|
|
if ctx.mode == "stationnement" or ctx.mode == "vol"
|
|
or ctx.mode == "atterrissage" then
|
|
y = dessinerArt(1, y, ctx)
|
|
end
|
|
|
|
if ctx.mode == "vol" then
|
|
texte(1, y, ("vit %.2f b/s alt %.1f -> %.0f boite %s")
|
|
:format(materiel.vitesseGlobale(), materiel.lireAltitude(),
|
|
ctx.consigneY or 0, ctx.rapportBoite or "1/1"))
|
|
y = y + 1
|
|
texte(1, y, "consigne:")
|
|
bouton(11, y, "-10", { type = "delta", valeur = -10 })
|
|
bouton(17, y, "-1", { type = "delta", valeur = -1 })
|
|
bouton(22, y, "+1", { type = "delta", valeur = 1 })
|
|
bouton(27, y, "+10", { type = "delta", valeur = 10 })
|
|
elseif ctx.mode == "atterrissage" then
|
|
texte(1, y, ("distance sol %.1f (sol posable: au pilote)")
|
|
:format(materiel.lireDistanceSol()))
|
|
end
|
|
end
|
|
|
|
local function dessinerPageCalib(ctx)
|
|
texte(1, 2, "gris: a faire jaune: en cours vert: fait",
|
|
colors.lightGray)
|
|
local x, y = 1, 3
|
|
for _, c in ipairs({ "gimbal", "joystick", "rsc", "boite",
|
|
"altitude", "inclinaison" }) do
|
|
local statut = ctx.calibStatuts[c]
|
|
local couleur = colors.gray
|
|
if statut == "encours" then couleur = colors.yellow
|
|
elseif statut == "fait" then couleur = colors.green end
|
|
if x + #c + 2 > L then x, y = 1, y + 1 end
|
|
bouton(x, y, c, { type = "calib", valeur = c }, couleur,
|
|
statut == "encours" and colors.black or colors.white)
|
|
x = x + #c + 3
|
|
end
|
|
y = y + 2
|
|
if ctx.calibTexte then
|
|
texte(1, y, ctx.calibTexte:sub(1, L), colors.yellow)
|
|
y = y + 1
|
|
end
|
|
if ctx.calibProgres then
|
|
local largeur = L - 2
|
|
local rempli = math.floor(ctx.calibProgres * largeur + 0.5)
|
|
moniteur.setCursorPos(1, y)
|
|
moniteur.setBackgroundColor(colors.green)
|
|
moniteur.write((" "):rep(rempli))
|
|
moniteur.setBackgroundColor(colors.gray)
|
|
moniteur.write((" "):rep(largeur - rempli))
|
|
moniteur.setBackgroundColor(colors.black)
|
|
y = y + 2
|
|
end
|
|
if ctx.rscViolet then
|
|
y = dessinerArt(1, y, ctx)
|
|
end
|
|
texte(1, H - 1, "sol: gimbal joystick rsc boite altitude",
|
|
colors.lightGray)
|
|
texte(1, H, "vol stationnaire: inclinaison", colors.lightGray)
|
|
end
|
|
|
|
local function dessinerPageParams(ctx)
|
|
local params = ctx.params or {}
|
|
local parPage = H - 3
|
|
local maxDefil = math.max(0, #params - parPage)
|
|
if defilParams > maxDefil then defilParams = maxDefil end
|
|
texte(1, 2, ("surcharges dans drone.etat (%d-%d/%d)"):format(
|
|
defilParams + 1,
|
|
math.min(defilParams + parPage, #params), #params),
|
|
colors.lightGray)
|
|
bouton(L - 9, 2, "^", { type = "defil", sens = -1 })
|
|
bouton(L - 4, 2, "v", { type = "defil", sens = 1 })
|
|
local y = 3
|
|
for i = defilParams + 1,
|
|
math.min(defilParams + parPage, #params) do
|
|
local p = params[i]
|
|
texte(1, y, ("%-22s"):format(p.label))
|
|
texte(24, y, tostring(p.valeur), colors.yellow)
|
|
bouton(L - 9, y, "-", { type = "param", idx = i, sens = -1 })
|
|
bouton(L - 4, y, "+", { type = "param", idx = i, sens = 1 })
|
|
y = y + 1
|
|
end
|
|
end
|
|
|
|
local function dessinerPageJournal()
|
|
for i, ligne in ipairs(journal.recents(H - 2)) do
|
|
texte(1, 1 + i, ligne:sub(1, L),
|
|
ligne:find("ERREUR") and colors.red
|
|
or ligne:find("ALERTE") and colors.orange or colors.white)
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------------------
|
|
-- RENDU ET TOUCHERS
|
|
------------------------------------------------------------------
|
|
function e.rafraichirMoniteur(ctx)
|
|
zones = {}
|
|
moniteur.setBackgroundColor(colors.black)
|
|
moniteur.clear()
|
|
local onglets = { mode = "MODE", calib = "CALIB",
|
|
params = "PARAM", journal = "JOURNAL" }
|
|
local x = 1
|
|
for _, id in ipairs({ "mode", "calib", "params", "journal" }) do
|
|
bouton(x, 1, onglets[id], { type = "page", valeur = id },
|
|
page == id and colors.green or colors.gray)
|
|
x = x + #onglets[id] + 3
|
|
end
|
|
if page == "mode" then dessinerPageMode(ctx)
|
|
elseif page == "calib" then dessinerPageCalib(ctx)
|
|
elseif page == "params" then dessinerPageParams(ctx)
|
|
else dessinerPageJournal() end
|
|
end
|
|
|
|
function e.traiterToucher(tx, ty)
|
|
for _, z in ipairs(zones) do
|
|
if tx >= z.x1 and tx <= z.x2 and ty >= z.y1 and ty <= z.y2 then
|
|
if z.action.type == "page" then
|
|
page = z.action.valeur
|
|
return { type = "redessiner" }
|
|
elseif z.action.type == "defil" then
|
|
defilParams = math.max(0, defilParams + z.action.sens * 4)
|
|
return { type = "redessiner" }
|
|
end
|
|
return z.action
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function e.reglerPage(p) page = p end
|
|
|
|
function e.attendreToucher()
|
|
os.pullEvent("monitor_touch")
|
|
end
|
|
|
|
-- art interactif plein ecran pour la calib rsc
|
|
function e.choisirRole(titre, affectes)
|
|
local locales = {}
|
|
local function boutonLocal(x, y, libelle, role, couleur)
|
|
moniteur.setCursorPos(x, y)
|
|
moniteur.setBackgroundColor(couleur)
|
|
moniteur.write(" " .. libelle .. " ")
|
|
moniteur.setBackgroundColor(colors.black)
|
|
table.insert(locales, { x1 = x, y1 = y, x2 = x + #libelle + 1,
|
|
y2 = y, role = role })
|
|
end
|
|
moniteur.setBackgroundColor(colors.black)
|
|
moniteur.clear()
|
|
texte(1, 1, titre:sub(1, L), colors.yellow)
|
|
texte(1, 2, "Touchez la position qui TOURNE", colors.lightGray)
|
|
local function couleurChoix(role)
|
|
return affectes[role] and colors.gray or colors.blue
|
|
end
|
|
local g, d = 8, L - 12
|
|
texte(math.floor(L / 2) - 2, 4, "AVANT", colors.lightGray)
|
|
boutonLocal(g, 5, "lf", "lf", couleurChoix("lf"))
|
|
boutonLocal(d, 5, "rf", "rf", couleurChoix("rf"))
|
|
boutonLocal(1, 7, "prop_l", "prop_l", couleurChoix("prop_l"))
|
|
boutonLocal(L - 8, 7, "prop_r", "prop_r", couleurChoix("prop_r"))
|
|
boutonLocal(g, 9, "lb", "lb", couleurChoix("lb"))
|
|
boutonLocal(d, 9, "rb", "rb", couleurChoix("rb"))
|
|
boutonLocal(1, 11, "ANNULER", nil, colors.red)
|
|
while true do
|
|
local _, _, tx, ty = os.pullEvent("monitor_touch")
|
|
for _, z in ipairs(locales) do
|
|
if tx >= z.x1 and tx <= z.x2 and ty >= z.y1
|
|
and ty <= z.y2 then
|
|
if z.role == nil then return nil end
|
|
if not affectes[z.role] then return z.role end
|
|
texte(1, 12, "deja affecte, choisir une autre position",
|
|
colors.orange)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return e
|
|
end
|
|
|
|
return Ecran
|