diff --git a/tools/production/app/app.tcss b/tools/production/app/app.tcss index 969a812..621a2ae 100644 --- a/tools/production/app/app.tcss +++ b/tools/production/app/app.tcss @@ -15,8 +15,8 @@ Screen { AppFrame { width: 100%; height: 100%; - max-width: 160; - max-height: 50; + max-width: 112; + max-height: 35; border: heavy $primary; background: $surface; padding: 1 2; @@ -25,19 +25,31 @@ AppFrame { /* ── WaitingScreen ──────────────────────────────────────── */ #waiting-frame { - align: center middle; + align: center top; } -#waiting-logo { - width: 52; - height: 7; - border: round $primary; - padding: 1 2; - content-align: center middle; - color: $primary; +#waiting-logo-row { + width: 100%; + height: auto; + margin-top: 1; +} + +#waiting-logo-art { + width: auto; +} + +#waiting-version-row { + width: 100%; + height: auto; + margin-top: 1; +} + +#waiting-version { + color: $warning; text-style: bold; + text-align: center; + margin-bottom: 1; } - #waiting-reason { margin-top: 1; padding: 1 2; @@ -163,6 +175,11 @@ AppFrame { margin-right: 2; } +#post-flash-title-row, +#post-flash-instruction-row { + width: 100%; + height: auto; +} /* ── DiagScreen ─────────────────────────────────────────── */ #diag-frame { diff --git a/tools/production/app/boot_art.py b/tools/production/app/boot_art.py new file mode 100644 index 0000000..08d9e64 --- /dev/null +++ b/tools/production/app/boot_art.py @@ -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") diff --git a/tools/production/app/screens/post_flash.py b/tools/production/app/screens/post_flash.py index aaf103d..eaf6b87 100644 --- a/tools/production/app/screens/post_flash.py +++ b/tools/production/app/screens/post_flash.py @@ -19,7 +19,7 @@ post_flash.py — экран-промпт после успешной проши from __future__ import annotations from textual.app import ComposeResult -from textual.containers import Horizontal +from textual.containers import Center, Horizontal from textual.message import Message from textual.screen import Screen from textual.timer import Timer @@ -49,12 +49,14 @@ class PostFlashScreen(Screen): def compose(self) -> ComposeResult: 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( - "Переведите плату в нормальный режим:\nBOOT_MOD_1 → GND → Reset", - id="post-flash-instruction", - ) + with Center(id="post-flash-instruction-row"): + yield Static( + "Переведите плату в нормальный режим:\nBOOT_MOD_1 → GND → Reset", + id="post-flash-instruction", + ) yield Static("", id="post-flash-countdown") diff --git a/tools/production/app/screens/waiting.py b/tools/production/app/screens/waiting.py index 12818c4..e577de6 100644 --- a/tools/production/app/screens/waiting.py +++ b/tools/production/app/screens/waiting.py @@ -14,25 +14,53 @@ waiting.py — экран ожидания подключения платы. from __future__ import annotations +import logging +import tomllib +from pathlib import Path from typing import Optional from textual.app import ComposeResult +from textual.containers import Center from textual.css.query import NoMatches from textual.message import Message from textual.screen import Screen from textual.timer import Timer from textual.widgets import Static +from ..boot_art import LOGO_ART from ..flasher import Flasher from ..models import AppMode from ..widgets import AppFrame +logger = logging.getLogger(__name__) + _DETECT_INTERVAL_S = 1.5 _SPIN_INTERVAL_S = 0.1 _REASON_DISPLAY_S = 4.0 _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): """ Экран ожидания. @@ -58,7 +86,9 @@ class WaitingScreen(Screen): def compose(self) -> ComposeResult: 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("Подключите плату индикатора к USB...", id="waiting-hint") yield Static(_SPINNER_FRAMES[0], id="waiting-spinner") diff --git a/tools/production/pyproject.toml b/tools/production/pyproject.toml index 20b5d72..f319c69 100644 --- a/tools/production/pyproject.toml +++ b/tools/production/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "service-tui" -version = "0.1.0" +version = "0.1.1" description = "TUI сервисного инженера для диагностики платы MIMXRT1052" requires-python = ">=3.11" dependencies = [ diff --git a/tools/production/uv.lock b/tools/production/uv.lock index b041381..e4fccb2 100644 --- a/tools/production/uv.lock +++ b/tools/production/uv.lock @@ -200,7 +200,7 @@ wheels = [ [[package]] name = "service-tui" -version = "0.1.0" +version = "0.1.1" source = { virtual = "." } dependencies = [ { name = "pyinstaller" },