-------------------------------------------------------------------- -- 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