# firmware_test: TUI app screens styling & app_logo

This commit is contained in:
Dmitry Akimov 2026-07-01 11:11:35 +03:00
parent 86df36fab7
commit 0132d8056d
6 changed files with 102 additions and 20 deletions

View file

@ -15,8 +15,8 @@ Screen {
AppFrame { AppFrame {
width: 100%; width: 100%;
height: 100%; height: 100%;
max-width: 160; max-width: 112;
max-height: 50; max-height: 35;
border: heavy $primary; border: heavy $primary;
background: $surface; background: $surface;
padding: 1 2; padding: 1 2;
@ -25,19 +25,31 @@ AppFrame {
/* ── WaitingScreen ──────────────────────────────────────── */ /* ── WaitingScreen ──────────────────────────────────────── */
#waiting-frame { #waiting-frame {
align: center middle; align: center top;
} }
#waiting-logo { #waiting-logo-row {
width: 52; width: 100%;
height: 7; height: auto;
border: round $primary; margin-top: 1;
padding: 1 2; }
content-align: center middle;
color: $primary; #waiting-logo-art {
width: auto;
}
#waiting-version-row {
width: 100%;
height: auto;
margin-top: 1;
}
#waiting-version {
color: $warning;
text-style: bold; text-style: bold;
text-align: center;
margin-bottom: 1;
} }
#waiting-reason { #waiting-reason {
margin-top: 1; margin-top: 1;
padding: 1 2; padding: 1 2;
@ -163,6 +175,11 @@ AppFrame {
margin-right: 2; margin-right: 2;
} }
#post-flash-title-row,
#post-flash-instruction-row {
width: 100%;
height: auto;
}
/* ── DiagScreen ─────────────────────────────────────────── */ /* ── DiagScreen ─────────────────────────────────────────── */
#diag-frame { #diag-frame {

View file

@ -0,0 +1,33 @@
"""
boot_art.py ASCII-логотип компании для WaitingScreen.
Сгенерирован программно (coverage-based растеризация logo.png в
символы плотности + разделение по цвету blue/dark), затем зафиксирован
как статичная строка рантайм-зависимости на Pillow/etc. не требуется.
"""
from __future__ import annotations
LOGO_ART = """
[grey37]........[/grey37][#3ca0dc].**:+*::*+.[/#3ca0dc][grey37]..........[/grey37]
[grey37]........[/grey37][#3ca0dc].##:*#++#*.[/#3ca0dc][grey37]..........[/grey37]
[grey37].........[/grey37][#3ca0dc]*#[/#3ca0dc][grey37].[/grey37][#3ca0dc]:#..#+[/#3ca0dc][grey37]...........[/grey37]
[grey37].......[/grey37][#3ca0dc].+#:[/#3ca0dc][grey37].[/grey37][#3ca0dc]:#..#+[/#3ca0dc][grey37]...........[/grey37]
[grey37].....[/grey37][#3ca0dc].+#+..:#*..#+[/#3ca0dc][grey37]...........[/grey37]
[grey37]...[/grey37][#3ca0dc].+#+..:**:..*#.[/#3ca0dc][white].:.[/white][grey37]........[/grey37]
[grey37].[/grey37][#3ca0dc].+#+..:#*:..*#+.[/#3ca0dc][white]+@@+[/white][grey37]........[/grey37]
[grey37].[/grey37][#3ca0dc]##..:#*.[/#3ca0dc][grey37].[/grey37][#3ca0dc].*#+.[/#3ca0dc][white]%@@@@%[/white][grey37].[/grey37][white]::.[/white][grey37]....[/grey37]
[grey37].[/grey37][#3ca0dc]#*[/#3ca0dc][grey37].[/grey37][#3ca0dc]:#:[/#3ca0dc][grey37].[/grey37][#3ca0dc].*#+.[/#3ca0dc][white]+@@@@@@%[/white][grey37].[/grey37][white]+@...[/white][grey37]..[/grey37]
[grey37].[/grey37][#3ca0dc]#*[/#3ca0dc][grey37].[/grey37][#3ca0dc]+#..#+.[/#3ca0dc][grey37]........[/grey37][white]%%[/white][grey37].[/grey37][white]+@..%.[/white][grey37].[/grey37]
[grey37].[/grey37][#3ca0dc]#*[/#3ca0dc][grey37].[/grey37][#3ca0dc]+#.:#:[/#3ca0dc][grey37].........[/grey37][white]%%[/white][grey37].[/grey37][white]+@..@:[/white][grey37].[/grey37]
[grey37].[/grey37][#3ca0dc]**[/#3ca0dc][grey37].[/grey37][#3ca0dc]+#.:#:[/#3ca0dc][grey37]........[/grey37][white].%%[/white][grey37].[/grey37][white]+@..@:[/white][grey37].[/grey37]
[grey37].[/grey37][#3ca0dc]..[/#3ca0dc][grey37].[/grey37][#3ca0dc]+#.:#*+++++.[/#3ca0dc][white].:@%.[/white][grey37].[/grey37][white]+@..@:[/white][grey37].[/grey37]
[grey37]....[/grey37][#3ca0dc].*.:#####+.[/#3ca0dc][white]+@%.[/white][grey37].[/grey37][white]:%%:[/white][grey37].[/grey37][white]:@:[/white][grey37].[/grey37]
[grey37]......[/grey37][#3ca0dc].:###+.[/#3ca0dc][white]+@%.[/white][grey37].[/grey37][white].%%:..+@+.[/white][grey37].[/grey37]
[grey37].......[/grey37][#3ca0dc].:+.[/#3ca0dc][white]:@%.[/white][grey37].[/grey37][white].%@:..+@+.[/white][grey37]...[/grey37]
[grey37]...........[/grey37][white]@%[/white][grey37].[/grey37][white].%@:..+@+.[/white][grey37].....[/grey37]
[grey37]...........[/grey37][white]@%[/white][grey37].[/grey37][white]%@..+@+.[/white][grey37].......[/grey37]
[grey37]...........[/grey37][white]@%[/white][grey37].[/grey37][white]%@[/white][grey37].[/grey37][white]:@:[/white][grey37].........[/grey37]
[grey37]..........[/grey37][white].@%.%@.+@+[/white][grey37].........[/grey37]
[grey37]..........[/grey37][white]:@@:@@+%@%.[/white][grey37]........[/grey37]
""".strip("\n")

View file

@ -19,7 +19,7 @@ post_flash.py — экран-промпт после успешной проши
from __future__ import annotations from __future__ import annotations
from textual.app import ComposeResult from textual.app import ComposeResult
from textual.containers import Horizontal from textual.containers import Center, Horizontal
from textual.message import Message from textual.message import Message
from textual.screen import Screen from textual.screen import Screen
from textual.timer import Timer from textual.timer import Timer
@ -49,12 +49,14 @@ class PostFlashScreen(Screen):
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
with AppFrame(id="post-flash-frame"): with AppFrame(id="post-flash-frame"):
yield Label("✅ firmware_test успешно записан", id="post-flash-title") with Center(id="post-flash-title-row"):
yield Label("✅ firmware_test успешно записан", id="post-flash-title")
yield Static( with Center(id="post-flash-instruction-row"):
"Переведите плату в нормальный режим:\nBOOT_MOD_1 → GND → Reset", yield Static(
id="post-flash-instruction", "Переведите плату в нормальный режим:\nBOOT_MOD_1 → GND → Reset",
) id="post-flash-instruction",
)
yield Static("", id="post-flash-countdown") yield Static("", id="post-flash-countdown")

View file

@ -14,25 +14,53 @@ waiting.py — экран ожидания подключения платы.
from __future__ import annotations from __future__ import annotations
import logging
import tomllib
from pathlib import Path
from typing import Optional from typing import Optional
from textual.app import ComposeResult from textual.app import ComposeResult
from textual.containers import Center
from textual.css.query import NoMatches from textual.css.query import NoMatches
from textual.message import Message from textual.message import Message
from textual.screen import Screen from textual.screen import Screen
from textual.timer import Timer from textual.timer import Timer
from textual.widgets import Static from textual.widgets import Static
from ..boot_art import LOGO_ART
from ..flasher import Flasher from ..flasher import Flasher
from ..models import AppMode from ..models import AppMode
from ..widgets import AppFrame from ..widgets import AppFrame
logger = logging.getLogger(__name__)
_DETECT_INTERVAL_S = 1.5 _DETECT_INTERVAL_S = 1.5
_SPIN_INTERVAL_S = 0.1 _SPIN_INTERVAL_S = 0.1
_REASON_DISPLAY_S = 4.0 _REASON_DISPLAY_S = 4.0
_SPINNER_FRAMES = ["", "", "", "", "", "", "", "", "", ""] _SPINNER_FRAMES = ["", "", "", "", "", "", "", "", "", ""]
def _read_app_version() -> str:
"""
Прочитать версию service-tui из pyproject.toml.
Не используем importlib.metadata проект не устанавливается как пакет
(tool.uv.package = false), метаданные могут отсутствовать. Читаем файл
напрямую через tomllib (stdlib, requires-python >= 3.11 уже задан).
"""
pyproject_path = Path(__file__).resolve().parents[2] / "pyproject.toml"
try:
with pyproject_path.open("rb") as f:
data = tomllib.load(f)
return data.get("project", {}).get("version", "0.0.0")
except Exception as exc:
logger.warning("Не удалось прочитать версию из %s: %s", pyproject_path, exc)
return "0.0.0"
_APP_VERSION = _read_app_version()
class WaitingScreen(Screen): class WaitingScreen(Screen):
""" """
Экран ожидания. Экран ожидания.
@ -58,7 +86,9 @@ class WaitingScreen(Screen):
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
with AppFrame(id="waiting-frame"): with AppFrame(id="waiting-frame"):
yield Static("TFT Indicator Board\nService Tool", id="waiting-logo") yield Static(f"service_tool v{_APP_VERSION}", id="waiting-version")
with Center(id="waiting-logo-row"):
yield Static(LOGO_ART, id="waiting-logo-art")
yield Static("", id="waiting-reason", classes="hidden") yield Static("", id="waiting-reason", classes="hidden")
yield Static("Подключите плату индикатора к USB...", id="waiting-hint") yield Static("Подключите плату индикатора к USB...", id="waiting-hint")
yield Static(_SPINNER_FRAMES[0], id="waiting-spinner") yield Static(_SPINNER_FRAMES[0], id="waiting-spinner")

View file

@ -1,6 +1,6 @@
[project] [project]
name = "service-tui" name = "service-tui"
version = "0.1.0" version = "0.1.1"
description = "TUI сервисного инженера для диагностики платы MIMXRT1052" description = "TUI сервисного инженера для диагностики платы MIMXRT1052"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [

View file

@ -200,7 +200,7 @@ wheels = [
[[package]] [[package]]
name = "service-tui" name = "service-tui"
version = "0.1.0" version = "0.1.1"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "pyinstaller" }, { name = "pyinstaller" },