cctweaked_drone/lib/ecran.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